drixio 1.1.9 → 1.2.0

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.
@@ -1,2288 +0,0 @@
1
- (function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e=`
2
- <header>
3
- <div id="current-path" aria-label="Breadcrumbs">
4
- <div class="breadcrumb-item db-crumb">
5
- <span class="material-symbols-outlined breadcrumb-icon">database</span>
6
- <span id="db-name">database</span>
7
- </div>
8
- <span id="slash" class="material-symbols-outlined breadcrumb-separator">chevron_right</span>
9
- <div id="current-table" class="breadcrumb-item table-crumb">
10
- <span class="material-symbols-outlined" id="table-icon">table_chart</span>
11
- <span id="table-name">users</span>
12
- </div>
13
- </div>
14
-
15
- <div class="header-actions">
16
- <!-- Import Dropdown -->
17
- <div class="header-dropdown-wrap" style="position: relative;">
18
- <button id="import-btn" class="header-btn secondary" title="Import data, ERD layout or database">
19
- <span id="import-icon" class="material-symbols-outlined">upload</span>
20
- <span>Import</span>
21
- <span id="import-arrow-icon" class="material-symbols-outlined">arrow_drop_down</span>
22
- </button>
23
-
24
- <ul id="import-dropdown" class="dropdown-menu hidden" role="menu">
25
- <li class="dropdown-submenu">
26
- <a class="dropdown-item">
27
- <span class="material-symbols-outlined">table_chart</span>
28
- <span class="dropdown-item-label">Data</span>
29
- <span class="caret material-symbols-outlined">chevron_right</span>
30
- </a>
31
- <ul class="dropdown-menu sub-menu">
32
- <li>
33
- <button id="import-data-csv-btn" class="dropdown-item">
34
- <span class="material-symbols-outlined">description</span>
35
- <span class="dropdown-item-label">Table Records</span>
36
- <span class="dropdown-badge">CSV</span>
37
- </button>
38
- </li>
39
- <li>
40
- <button id="import-data-json-btn" class="dropdown-item">
41
- <span class="material-symbols-outlined">data_object</span>
42
- <span class="dropdown-item-label">Table Records</span>
43
- <span class="dropdown-badge">JSON</span>
44
- </button>
45
- </li>
46
- </ul>
47
- </li>
48
-
49
- <li class="dropdown-submenu">
50
- <a class="dropdown-item">
51
- <span class="material-symbols-outlined">account_tree</span>
52
- <span class="dropdown-item-label">ERD</span>
53
- <span class="caret material-symbols-outlined">chevron_right</span>
54
- </a>
55
- <ul class="dropdown-menu sub-menu">
56
- <li>
57
- <button id="import-erd-json-btn" class="dropdown-item">
58
- <span class="material-symbols-outlined">layers</span>
59
- <span class="dropdown-item-label">Canvas Layout</span>
60
- <span class="dropdown-badge">JSON</span>
61
- </button>
62
- </li>
63
- </ul>
64
- </li>
65
-
66
- <li class="dropdown-divider"></li>
67
-
68
- <li>
69
- <button id="import-db-sql-btn" class="dropdown-item danger">
70
- <span class="material-symbols-outlined">database</span>
71
- <span class="dropdown-item-label">Restore Database</span>
72
- <span class="dropdown-badge danger">SQL / JSON</span>
73
- </button>
74
- </li>
75
- </ul>
76
- </div>
77
-
78
- <!-- Export Dropdown -->
79
- <div class="header-dropdown-wrap" style="position: relative;">
80
- <button id="export-btn" class="header-btn primary" title="Export database or table data">
81
- <span id="export-icon" class="material-symbols-outlined">download</span>
82
- <span>Export</span>
83
- <span id="export-arrow-icon" class="material-symbols-outlined">arrow_drop_down</span>
84
- </button>
85
-
86
- <ul id="export-dropdown" class="dropdown-menu hidden" role="menu">
87
- <li class="dropdown-submenu">
88
- <a class="dropdown-item">
89
- <span class="material-symbols-outlined">table_chart</span>
90
- <span class="dropdown-item-label">Data</span>
91
- <span class="caret material-symbols-outlined">chevron_right</span>
92
- </a>
93
- <ul class="dropdown-menu sub-menu">
94
- <li>
95
- <button id="export-data-csv-btn" class="dropdown-item">
96
- <span class="material-symbols-outlined">description</span>
97
- <span class="dropdown-item-label">Table Records</span>
98
- <span class="dropdown-badge">CSV</span>
99
- </button>
100
- </li>
101
- <li>
102
- <button id="export-data-json-btn" class="dropdown-item">
103
- <span class="material-symbols-outlined">data_object</span>
104
- <span class="dropdown-item-label">Table Records</span>
105
- <span class="dropdown-badge">JSON</span>
106
- </button>
107
- </li>
108
- <li class="data-tab-only" style="display: none;">
109
- <button id="export-data-filtered-csv-btn" class="dropdown-item" style="color: var(--color-primary);">
110
- <span class="material-symbols-outlined" style="color: var(--color-primary);">filter_alt</span>
111
- <span class="dropdown-item-label">Filtered Records</span>
112
- <span class="dropdown-badge" style="color: var(--color-primary); border-color: rgba(59, 130, 246, 0.3);">CSV</span>
113
- </button>
114
- </li>
115
- <li class="data-tab-only" style="display: none;">
116
- <button id="export-data-filtered-json-btn" class="dropdown-item" style="color: var(--color-primary);">
117
- <span class="material-symbols-outlined" style="color: var(--color-primary);">filter_alt</span>
118
- <span class="dropdown-item-label">Filtered Records</span>
119
- <span class="dropdown-badge" style="color: var(--color-primary); border-color: rgba(59, 130, 246, 0.3);">JSON</span>
120
- </button>
121
- </li>
122
- </ul>
123
- </li>
124
-
125
- <li class="dropdown-submenu">
126
- <a class="dropdown-item">
127
- <span class="material-symbols-outlined">schema</span>
128
- <span class="dropdown-item-label">Schema</span>
129
- <span class="caret material-symbols-outlined">chevron_right</span>
130
- </a>
131
- <ul class="dropdown-menu sub-menu">
132
- <li>
133
- <button id="export-schema-dict-btn" class="dropdown-item">
134
- <span class="material-symbols-outlined">menu_book</span>
135
- <span class="dropdown-item-label">Data Dictionary</span>
136
- <span class="dropdown-badge">MD</span>
137
- </button>
138
- </li>
139
- <li>
140
- <button id="export-schema-sql-btn" class="dropdown-item">
141
- <span class="material-symbols-outlined">code</span>
142
- <span class="dropdown-item-label">DDL Schema Only</span>
143
- <span class="dropdown-badge">SQL</span>
144
- </button>
145
- </li>
146
- <li>
147
- <button id="export-schema-snapshot-btn" class="dropdown-item">
148
- <span class="material-symbols-outlined">data_object</span>
149
- <span class="dropdown-item-label">Schema Snapshot</span>
150
- <span class="dropdown-badge">JSON</span>
151
- </button>
152
- </li>
153
- </ul>
154
- </li>
155
-
156
- <li class="dropdown-submenu console-tab-only" style="display: none;">
157
- <a class="dropdown-item">
158
- <span class="material-symbols-outlined">terminal</span>
159
- <span class="dropdown-item-label">SQL Console</span>
160
- <span class="caret material-symbols-outlined">chevron_right</span>
161
- </a>
162
- <ul class="dropdown-menu sub-menu">
163
- <li>
164
- <button id="export-console-csv-btn" class="dropdown-item">
165
- <span class="material-symbols-outlined">description</span>
166
- <span class="dropdown-item-label">Query Result</span>
167
- <span class="dropdown-badge">CSV</span>
168
- </button>
169
- </li>
170
- <li>
171
- <button id="export-console-json-btn" class="dropdown-item">
172
- <span class="material-symbols-outlined">data_object</span>
173
- <span class="dropdown-item-label">Query Result</span>
174
- <span class="dropdown-badge">JSON</span>
175
- </button>
176
- </li>
177
- </ul>
178
- </li>
179
-
180
- <li class="dropdown-submenu">
181
- <a class="dropdown-item">
182
- <span class="material-symbols-outlined">account_tree</span>
183
- <span class="dropdown-item-label">ERD</span>
184
- <span class="caret material-symbols-outlined">chevron_right</span>
185
- </a>
186
- <ul class="dropdown-menu sub-menu">
187
- <li>
188
- <button id="export-erd-json-btn" class="dropdown-item">
189
- <span class="material-symbols-outlined">layers</span>
190
- <span class="dropdown-item-label">Canvas Layout</span>
191
- <span class="dropdown-badge">JSON</span>
192
- </button>
193
- </li>
194
- </ul>
195
- </li>
196
-
197
- <li class="dropdown-divider"></li>
198
-
199
- <li>
200
- <button id="export-db-sql-btn" class="dropdown-item">
201
- <span class="material-symbols-outlined">database</span>
202
- <span class="dropdown-item-label">Full Database</span>
203
- <span class="dropdown-badge">SQL</span>
204
- </button>
205
- </li>
206
- </ul>
207
- </div>
208
- </div>
209
- </header>
210
- `,t=`
211
- <aside id="sidebar-panel">
212
- <div id="sidebar-header">
213
- <div class="brand-left">
214
- <div class="brand-logo-badge">
215
- <span class="material-symbols-outlined" id="database-icon">database</span>
216
- </div>
217
- <div class="brand-container">
218
- <div class="brand-name">
219
- <h1 id="brand-title">DRIXIO</h1>
220
- <span id="brand-subtitle">STUDIO</span>
221
- </div>
222
- <span id="brand-by">By Horizora</span>
223
- </div>
224
- </div>
225
- <button id="sidebar-collapse-btn" class="sidebar-icon-btn" title="Toggle Sidebar Width" aria-label="Toggle Sidebar Width">
226
- <span class="material-symbols-outlined" id="sidebar-collapse-icon">menu_open</span>
227
- </button>
228
- </div>
229
-
230
- <div id="sidebar-content">
231
- <div id="search-bar" role="search">
232
- <span class="material-symbols-outlined" id="search-icon">search</span>
233
- <input type="text" placeholder="Filter tables..." id="search-input" aria-label="Search tables" autocomplete="off" spellcheck="false" />
234
- <button id="search-clear-btn" class="search-clear-btn hidden" title="Clear filter" aria-label="Clear filter">✕</button>
235
- <kbd class="search-kbd" title="Shortcut">/</kbd>
236
- </div>
237
-
238
- <div class="sidebar-section-header">
239
- <div class="section-title-wrap">
240
- <span class="section-title">TABLES</span>
241
- <span id="table-count-badge" class="section-count">0</span>
242
- </div>
243
- <div class="section-actions">
244
- <button id="add-table-btn" class="sidebar-icon-btn" title="Create New Table" aria-label="Create New Table">
245
- <span class="material-symbols-outlined">add</span>
246
- </button>
247
- <button id="refresh-tables-btn" class="sidebar-icon-btn" title="Refresh Table List" aria-label="Refresh Table List">
248
- <span class="material-symbols-outlined">refresh</span>
249
- </button>
250
- </div>
251
- </div>
252
-
253
- <nav id="table-nav" aria-label="Database Tables">
254
- <!-- Dynamically populated tables -->
255
- </nav>
256
- </div>
257
-
258
- <div id="sidebar-footer">
259
- <div class="sidebar-db-status" title="Database Connection Status">
260
- <span class="status-indicator-dot" id="sidebar-db-status-dot"></span>
261
- <span id="brand-db-type">CONNECTING...</span>
262
- </div>
263
-
264
- <div class="sidebar-footer-actions">
265
- <button id="theme-toggle" class="theme-toggle-btn" title="Toggle Theme" aria-label="Toggle Theme">
266
- <span class="material-symbols-outlined" id="theme-icon">dark_mode</span>
267
- <span id="theme-text">Theme</span>
268
- </button>
269
- <span class="sidebar-version-tag" title="Drixio Studio Edition">v1.1.9</span>
270
- </div>
271
- </div>
272
- </aside>
273
- `,n=`
274
- <nav id="tab-nav" role="tablist" aria-label="Views and tools">
275
- <!-- Table Scoped Underline Tabs -->
276
- <div class="table-tabs" role="tablist">
277
- <button
278
- type="button"
279
- class="tab-btn isCurrentTab"
280
- onclick="handleSwitchTab('data-btn')"
281
- id="data-btn"
282
- role="tab"
283
- title="View table data records"
284
- >
285
- <span class="material-symbols-outlined tab-icon" id="data-icon">table_chart</span>
286
- <span>Data</span>
287
- </button>
288
- <button
289
- type="button"
290
- class="tab-btn"
291
- onclick="handleSwitchTab('schema-btn')"
292
- id="schema-btn"
293
- role="tab"
294
- title="View and edit table schema"
295
- >
296
- <span class="material-symbols-outlined tab-icon" id="schema-icon">schema</span>
297
- <span>Schema</span>
298
- </button>
299
- <button
300
- type="button"
301
- class="tab-btn hidden"
302
- onclick="handleSwitchTab('connect-btn')"
303
- id="connect-btn"
304
- role="tab"
305
- title="Connect or create database"
306
- >
307
- <span class="material-symbols-outlined tab-icon" id="connect-icon">cable</span>
308
- <span>Connect</span>
309
- </button>
310
- </div>
311
-
312
- <div class="flex-1"></div>
313
-
314
- <!-- Global Database Tools (Clean flat buttons) -->
315
- <div class="global-tools" role="toolbar" aria-label="Global tools">
316
- <button
317
- type="button"
318
- class="tab-btn tool-btn"
319
- onclick="handleSwitchTab('status-btn')"
320
- id="status-btn"
321
- role="tab"
322
- title="Database metrics and health"
323
- >
324
- <span class="material-symbols-outlined tab-icon" id="status-icon">monitoring</span>
325
- <span>Status</span>
326
- </button>
327
- <button
328
- type="button"
329
- class="tab-btn tool-btn"
330
- onclick="handleSwitchTab('erd-btn')"
331
- id="erd-btn"
332
- role="tab"
333
- title="Entity relationship diagram"
334
- >
335
- <span class="material-symbols-outlined tab-icon" id="erd-icon">account_tree</span>
336
- <span>ERD</span>
337
- </button>
338
- <button
339
- type="button"
340
- class="tab-btn tool-btn"
341
- onclick="handleSwitchTab('sql-btn')"
342
- id="sql-btn"
343
- role="tab"
344
- title="Execute SQL queries"
345
- >
346
- <span class="material-symbols-outlined tab-icon" id="sql-icon">terminal</span>
347
- <span>Console</span>
348
- </button>
349
- </div>
350
- </nav>
351
- `;async function r(){return await(await fetch(`/api/tables`)).json()}async function i(e,t){return await(await fetch(`/api/tables`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({tableName:e,columns:t})})).json()}async function a(){return await(await fetch(`/api/config`)).json()}async function o(){return await(await fetch(`/api/tables/stats`)).json()}async function s(e,t={}){let{where:n=``,limit:r=50,offset:i=0,orderCol:a,orderAsc:o}=t,s=new URLSearchParams;n&&s.append(`where`,n),s.append(`limit`,r.toString()),s.append(`offset`,i.toString()),a&&(s.append(`orderCol`,a),s.append(`orderAsc`,o===!1?`false`:`true`));let c=`/api/tables/${e}/data?${s.toString()}`;return await(await fetch(c)).json()}async function c(e){return await(await fetch(`/api/tables/${e}/schema`)).json()}async function l(e){return await(await fetch(`/api/tables/${e}/indexes`)).json()}async function u(e){return await(await fetch(`/api/query`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({sql:e})})).json()}async function d(e,t){return await(await fetch(`/api/tables/${encodeURIComponent(e)}/records`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(t)})).json()}async function f(e,t){return await(await fetch(`/api/tables/${encodeURIComponent(e)}/schema`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(t)})).json()}async function p(e){return await(await fetch(`/api/tables/${encodeURIComponent(e)}/truncate`,{method:`POST`})).json()}async function m(e,t=`csv`){let n=await(await fetch(`/api/query/export?format=${t}`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(e)})).blob(),r=URL.createObjectURL(n),i=document.createElement(`a`);i.href=r,i.download=`query_result_${new Date().getTime()}.${t}`,document.body.appendChild(i),i.click(),document.body.removeChild(i),URL.revokeObjectURL(r)}async function h(e){return await(await fetch(`/api/schema/diff`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(e)})).json()}async function g(e){return await(await fetch(`/api/schema/apply-migration`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({sql:e})})).json()}function _(){window.location.href=`/api/schema/snapshot`}async function v(){return await(await fetch(`/api/snippets`)).json()}async function y(e){return await(await fetch(`/api/snippets`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(e)})).json()}async function b(e,t){return await(await fetch(`/api/snippets/${encodeURIComponent(e)}`,{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify(t)})).json()}async function x(e){return await(await fetch(`/api/snippets/${encodeURIComponent(e)}`,{method:`DELETE`})).json()}var S=null;function C(){S||(S=document.createElement(`div`),S.className=`drixio-context-menu`,document.body.appendChild(S),document.addEventListener(`click`,e=>{S.classList.contains(`visible`)&&!S.contains(e.target)&&T()}),document.addEventListener(`keydown`,e=>{e.key===`Escape`&&S.classList.contains(`visible`)&&T()}),window.addEventListener(`scroll`,e=>{S.classList.contains(`visible`)&&(S.contains(e.target)||T())},!0))}function w(e,t){e.preventDefault&&e.preventDefault(),e.stopPropagation&&e.stopPropagation(),C(),S.innerHTML=``,S.classList.remove(`visible`),t.forEach(e=>{if(e.type===`divider`||e===`divider`){let e=document.createElement(`div`);e.className=`drixio-context-menu-divider`,S.appendChild(e);return}let t=document.createElement(`div`);t.className=`drixio-context-menu-item`,e.danger&&t.classList.add(`danger`),e.disabled&&t.classList.add(`disabled`),t.innerHTML=`
352
- ${e.icon?`<span class="material-symbols-outlined drixio-menu-icon">${e.icon}</span>`:``}
353
- <span class="drixio-menu-label">${e.label}</span>
354
- ${e.shortcut?`<span class="drixio-menu-shortcut">${e.shortcut}</span>`:``}
355
- `,t.addEventListener(`click`,t=>{e.disabled||(t.stopPropagation(),T(),e.action&&e.action())}),S.appendChild(t)}),requestAnimationFrame(()=>{let t=S.getBoundingClientRect(),n=e.clientX,r=e.clientY,i=`left`,a=`top`;n+t.width>window.innerWidth-8&&(n=Math.max(8,window.innerWidth-t.width-8),i=`right`),r+t.height>window.innerHeight-8&&(r=Math.max(8,window.innerHeight-t.height-8),a=`bottom`),S.style.transformOrigin=`${a} ${i}`,S.style.left=`${n}px`,S.style.top=`${r}px`,S.classList.add(`visible`)})}function T(){S&&S.classList.remove(`visible`)}async function E(e,t,n){let r=document.getElementById(`mock-data-modal-overlay`);r||(r=document.createElement(`div`),r.id=`mock-data-modal-overlay`,r.className=`mock-modal-overlay`,document.body.appendChild(r)),r.innerHTML=`
356
- <div class="mock-modal-container">
357
- <div class="mock-modal-header">
358
- <div class="mock-modal-title">
359
- <span class="material-symbols-outlined" style="color: var(--color-primary);">auto_fix_high</span>
360
- <h3>Generate Mock Data</h3>
361
- <span class="mock-target-badge">${e}</span>
362
- </div>
363
- <button type="button" class="mock-modal-close" id="btn-mock-close">
364
- <span class="material-symbols-outlined">close</span>
365
- </button>
366
- </div>
367
- <div class="mock-modal-body" style="padding: 40px; text-align: center;">
368
- <div style="display: inline-flex; align-items: center; gap: 8px; color: var(--color-text-soft);">
369
- <span class="material-symbols-outlined animate-spin icon-18">progress_activity</span>
370
- <span>Analyzing schema and preparing preview...</span>
371
- </div>
372
- </div>
373
- </div>
374
- `,r.querySelector(`#btn-mock-close`)?.addEventListener(`click`,i),r.classList.add(`visible`);function i(){r.classList.remove(`visible`)}let a=10,o=null;try{let t=await(await fetch(`/api/tables/${encodeURIComponent(e)}/mock/preview`)).json();if(!t.success||!t.data)throw Error(t.error||`Failed to load preview`);o=t.data}catch(e){r.innerHTML=`
375
- <div class="mock-modal-container">
376
- <div class="mock-modal-header">
377
- <div class="mock-modal-title">
378
- <span class="material-symbols-outlined" style="color: #ef4444;">error</span>
379
- <h3>Failed to Load Mock Generator</h3>
380
- </div>
381
- <button type="button" class="mock-modal-close" id="btn-mock-close">
382
- <span class="material-symbols-outlined">close</span>
383
- </button>
384
- </div>
385
- <div class="mock-modal-body" style="padding: 24px;">
386
- <p style="color: var(--color-text-soft); font-size: 13px;">${e.message}</p>
387
- </div>
388
- </div>
389
- `,r.querySelector(`#btn-mock-close`)?.addEventListener(`click`,i);return}function s(){let{rules:t,previewRows:c}=o,l=t.filter(e=>e.strategy.type!==`pk_auto`),u=l.map(e=>`<th>${e.column.name}</th>`).join(``),d=c.map(e=>`<tr>${l.map(t=>`<td title="${String(e[t.column.name]??``)}">${String(e[t.column.name]??``)}</td>`).join(``)}</tr>`).join(``),f=t.map(({column:e,strategy:t})=>`
390
- <div class="mock-rule-item">
391
- <div class="mock-rule-col">
392
- <span class="mock-col-name">${e.name}</span>
393
- <span class="mock-col-type">${e.type||`TEXT`}</span>
394
- </div>
395
- <span class="mock-rule-strategy ${t.type===`fk`?`is-fk`:``}">
396
- ${t.label}
397
- </span>
398
- </div>
399
- `).join(``);r.innerHTML=`
400
- <div class="mock-modal-container">
401
- <div class="mock-modal-header">
402
- <div class="mock-modal-title">
403
- <span class="material-symbols-outlined" style="color: var(--color-primary);">auto_fix_high</span>
404
- <h3>Generate Mock Data</h3>
405
- <span class="mock-target-badge">${e}</span>
406
- </div>
407
- <button type="button" class="mock-modal-close" id="btn-mock-close">
408
- <span class="material-symbols-outlined">close</span>
409
- </button>
410
- </div>
411
-
412
- <div class="mock-modal-body">
413
- <!-- Row Count Selector -->
414
- <div class="mock-section">
415
- <label class="mock-section-label">Number of Rows</label>
416
- <div class="mock-count-pills">
417
- <button type="button" class="mock-pill ${a===10?`active`:``}" data-count="10">10 Rows</button>
418
- <button type="button" class="mock-pill ${a===50?`active`:``}" data-count="50">50 Rows</button>
419
- <button type="button" class="mock-pill ${a===100?`active`:``}" data-count="100">100 Rows</button>
420
- <div class="mock-custom-input-wrap">
421
- <input type="number" id="mock-custom-count" min="1" max="500" placeholder="Custom" value="${[10,50,100].includes(a)?``:a}" />
422
- </div>
423
- </div>
424
- </div>
425
-
426
- <!-- Column Strategies -->
427
- <div class="mock-section">
428
- <label class="mock-section-label">Inferred Column Rules (${t.length} columns)</label>
429
- <div class="mock-rules-list">
430
- ${f}
431
- </div>
432
- </div>
433
-
434
- <!-- Sample Preview -->
435
- <div class="mock-section">
436
- <label class="mock-section-label">Sample Preview (First 3 Rows)</label>
437
- <div class="mock-preview-table-wrap">
438
- <table class="mock-preview-table">
439
- <thead><tr>${u}</tr></thead>
440
- <tbody>${d}</tbody>
441
- </table>
442
- </div>
443
- </div>
444
- </div>
445
-
446
- <div class="mock-modal-footer">
447
- <button type="button" class="mock-btn-secondary" id="btn-mock-cancel">Cancel</button>
448
- <button type="button" class="mock-btn-primary" id="btn-mock-generate">
449
- <span class="material-symbols-outlined icon-16">auto_fix_high</span>
450
- <span>Generate & Insert ${a} Rows</span>
451
- </button>
452
- </div>
453
- </div>
454
- `,r.querySelector(`#btn-mock-close`)?.addEventListener(`click`,i),r.querySelector(`#btn-mock-cancel`)?.addEventListener(`click`,i),r.querySelectorAll(`.mock-pill`).forEach(e=>{e.addEventListener(`click`,()=>{a=parseInt(e.dataset.count,10),s()})});let p=r.querySelector(`#mock-custom-count`);p&&p.addEventListener(`change`,e=>{let t=parseInt(e.target.value,10);t&&t>0&&t<=500&&(a=t,s())}),r.querySelector(`#btn-mock-generate`)?.addEventListener(`click`,async()=>{let t=r.querySelector(`#btn-mock-generate`);if(t){t.disabled=!0,t.innerHTML=`<span class="material-symbols-outlined animate-spin icon-16">progress_activity</span> Inserting...`;try{let t=await(await fetch(`/api/tables/${encodeURIComponent(e)}/mock`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({count:a})})).json();if(!t.success)throw Error(t.error||`Failed to insert mock data`);i(),window.loadTableStats?.(),window.updateSidebarActiveTable?.(e),window.showToast&&window.showToast(`Successfully generated and inserted ${t.count||a} rows into "${e}"!`,`success`),n&&n()}catch(e){alert(`Failed to insert mock data: ${e.message}`),t.disabled=!1,t.innerHTML=`<span>Try Again</span>`}}})}s()}function D(e){let t=document.getElementById(`create-table-modal`);t&&t.remove();let n=(window.AppState?.dbType||`sqlite`).toLowerCase(),a=[`INTEGER`,`VARCHAR(255)`,`TEXT`,`BOOLEAN`,`TIMESTAMP`,`NUMERIC`,`JSON`,`BLOB`],o=[{name:`id`,type:`INTEGER`,primaryKey:!0,nullable:!1,isUnique:!1,defaultValue:``,fkTarget:null},{name:`name`,type:`VARCHAR(255)`,primaryKey:!1,nullable:!0,isUnique:!1,defaultValue:``,fkTarget:null}],s=[],l={},u=null;r().then(e=>{Array.isArray(e)&&(s=e)}).catch(e=>console.warn(`Failed to preload tables for FK:`,e)),t=document.createElement(`div`),t.id=`create-table-modal`,t.className=`modal-overlay`,t.innerHTML=`
455
- <div class="modal-container create-table-modal-container">
456
- <div class="modal-header">
457
- <div class="flex items-center gap-2">
458
- <span class="material-symbols-outlined text-primary" style="font-size: 20px;">add_circle</span>
459
- <h3 class="m-0 text-15 font-semibold">Create New Table</h3>
460
- <span class="snippet-param-badge">${n}</span>
461
- </div>
462
- <button type="button" id="close-create-table-modal-btn" class="modal-close-btn" title="Close">
463
- <span class="material-symbols-outlined">close</span>
464
- </button>
465
- </div>
466
-
467
- <div class="create-table-modal-body" id="create-table-modal-body">
468
- <!-- Table Name Field -->
469
- <div class="table-form-group">
470
- <label class="table-form-label">
471
- <span>Table Name <span style="color: var(--color-error);">*</span></span>
472
- </label>
473
- <input type="text" id="new-table-name-input" class="table-name-input" placeholder="e.g. users, products, orders" autofocus />
474
- <span id="table-name-error" style="display: none; font-size: 11px; color: var(--color-error); margin-top: 4px;"></span>
475
- </div>
476
-
477
- <!-- Quick Presets -->
478
- <div class="table-presets-bar">
479
- <span class="table-preset-label">Quick Add:</span>
480
- <button type="button" id="preset-add-timestamps" class="table-preset-btn" title="Add created_at and updated_at timestamp columns">
481
- <span class="material-symbols-outlined" style="font-size: 13px;">schedule</span>
482
- <span>+ Timestamps</span>
483
- </button>
484
- <button type="button" id="preset-add-softdelete" class="table-preset-btn" title="Add deleted_at nullable timestamp column">
485
- <span class="material-symbols-outlined" style="font-size: 13px;">delete_sweep</span>
486
- <span>+ Soft Delete</span>
487
- </button>
488
- <button type="button" id="preset-add-uuid-pk" class="table-preset-btn" title="Change primary key to UUID">
489
- <span class="material-symbols-outlined" style="font-size: 13px;">fingerprint</span>
490
- <span>+ UUID PK</span>
491
- </button>
492
- </div>
493
-
494
- <!-- Columns Section -->
495
- <div class="table-form-group">
496
- <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 2px;">
497
- <label class="table-form-label">
498
- <span>Columns Definition (<span id="column-count-label">${o.length}</span>)</span>
499
- </label>
500
- <button type="button" id="btn-add-column-row" class="btn-secondary" style="display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px; font-size: 11.5px; border-radius: 6px; cursor: pointer;">
501
- <span class="material-symbols-outlined" style="font-size: 14px;">add</span>
502
- <span>Add Column</span>
503
- </button>
504
- </div>
505
-
506
- <!-- Column Header Grid -->
507
- <div class="table-columns-header-grid">
508
- <div>Column Name</div>
509
- <div>Type</div>
510
- <div style="text-align: center;">PK</div>
511
- <div style="text-align: center;">Null</div>
512
- <div style="text-align: center;">Unique</div>
513
- <div>Default</div>
514
- <div>Foreign Key</div>
515
- <div style="text-align: center;">Actions</div>
516
- </div>
517
-
518
- <!-- Column Rows Container -->
519
- <div id="create-table-columns-container" class="table-columns-body-container">
520
- <!-- Rendered dynamically -->
521
- </div>
522
- </div>
523
-
524
- <!-- Live SQL Preview Section -->
525
- <div class="table-sql-preview-section" id="table-sql-preview-section">
526
- <div class="table-sql-preview-header" id="table-sql-preview-toggle">
527
- <div class="table-sql-preview-title">
528
- <span class="material-symbols-outlined text-primary">terminal</span>
529
- <span>SQL Preview (DDL)</span>
530
- </div>
531
- <div style="display: flex; align-items: center; gap: 6px;">
532
- <span class="material-symbols-outlined" id="table-sql-preview-arrow" style="font-size: 16px; color: var(--color-text-soft);">expand_less</span>
533
- </div>
534
- </div>
535
- <div class="table-sql-code-container">
536
- <button type="button" id="copy-table-sql-btn" class="table-sql-copy-btn">
537
- <span class="material-symbols-outlined" style="font-size: 13px;">content_copy</span>
538
- <span>Copy</span>
539
- </button>
540
- <pre id="table-sql-preview-code" class="table-sql-code-block"></pre>
541
- </div>
542
- </div>
543
- </div>
544
-
545
- <div class="modal-footer">
546
- <button type="button" id="cancel-create-table-btn" class="btn-secondary">Cancel</button>
547
- <button type="button" id="submit-create-table-btn" class="btn-primary" style="display: inline-flex; align-items: center; gap: 6px;">
548
- <span class="material-symbols-outlined" style="font-size: 16px;">check</span>
549
- <span id="submit-create-table-text">Create Table</span>
550
- </button>
551
- </div>
552
- </div>
553
- `,document.body.appendChild(t);let d=document.getElementById(`create-table-columns-container`),f=document.getElementById(`new-table-name-input`),p=document.getElementById(`column-count-label`),m=document.getElementById(`submit-create-table-btn`),h=document.getElementById(`submit-create-table-text`),g=document.getElementById(`table-name-error`),_=document.getElementById(`table-sql-preview-code`),v=document.getElementById(`table-sql-preview-section`),y=document.getElementById(`table-sql-preview-toggle`),b=document.getElementById(`table-sql-preview-arrow`),x=document.getElementById(`copy-table-sql-btn`);y.onclick=()=>{let e=v.classList.toggle(`collapsed`);b.textContent=e?`expand_more`:`expand_less`},x.onclick=()=>{_.textContent&&(navigator.clipboard.writeText(_.textContent),window.showToast?.(`SQL copied to clipboard!`,`success`))};function S(e){return n===`mysql`?`\`${e}\``:`"${e}"`}function C(){let e=(f.value.trim()||`table_name`).trim(),t=[];o.forEach(e=>{let r=e.name?S(e.name):`"column"`,i=(e.type||`VARCHAR(255)`).toLowerCase(),a=e.type||`VARCHAR(255)`;n===`sqlite`?(i===`integer`||i===`int`?a=`INTEGER`:i===`text`||i===`string`?a=`TEXT`:i===`boolean`||i===`bool`?a=`BOOLEAN`:[`decimal`,`numeric`,`float`,`double`].includes(i)?a=`REAL`:(i===`datetime`||i===`timestamp`)&&(a=`DATETIME`),e.primaryKey&&(i.includes(`int`)||a===`INTEGER`)?a=`INTEGER PRIMARY KEY AUTOINCREMENT`:e.primaryKey?a+=` PRIMARY KEY`:(e.nullable||(a+=` NOT NULL`),e.isUnique&&(a+=` UNIQUE`))):n===`postgres`?e.primaryKey&&(i===`integer`||i===`int`)?a=`SERIAL PRIMARY KEY`:(i===`integer`||i===`int`?a=`INTEGER`:i===`text`||i===`string`?a=`TEXT`:i===`boolean`||i===`bool`?a=`BOOLEAN`:i===`decimal`||i===`numeric`?a=`NUMERIC`:(i===`datetime`||i===`timestamp`)&&(a=`TIMESTAMP`),e.primaryKey&&(a+=` PRIMARY KEY`),!e.nullable&&!e.primaryKey&&(a+=` NOT NULL`),e.isUnique&&!e.primaryKey&&(a+=` UNIQUE`)):(i===`integer`||i===`int`?a=`INT`:i===`text`||i===`string`?a=`VARCHAR(255)`:i===`boolean`||i===`bool`?a=`BOOLEAN`:i===`decimal`||i===`numeric`?a=`DOUBLE`:(i===`datetime`||i===`timestamp`)&&(a=`DATETIME`),e.primaryKey&&(i.includes(`int`)||a===`INT`)?a+=` AUTO_INCREMENT PRIMARY KEY`:e.primaryKey&&(a+=` PRIMARY KEY`),!e.nullable&&!e.primaryKey&&(a+=` NOT NULL`),e.isUnique&&!e.primaryKey&&(a+=` UNIQUE`)),e.defaultValue&&!e.defaultValue.startsWith(`FK ->`)&&(e.defaultValue.toUpperCase()===`CURRENT_TIMESTAMP`||e.defaultValue===`Timestamp`?a+=` DEFAULT CURRENT_TIMESTAMP`:a+=` DEFAULT ${e.defaultValue}`),t.push(` ${r} ${a}`)}),o.forEach(e=>{if(e.fkTarget&&e.fkTarget.table&&e.fkTarget.column){let n=` FOREIGN KEY (${S(e.name||`col`)}) REFERENCES ${S(e.fkTarget.table)}(${S(e.fkTarget.column)})`;e.fkTarget.onDelete&&e.fkTarget.onDelete!==`NO ACTION`&&(n+=` ON DELETE ${e.fkTarget.onDelete}`),e.fkTarget.onUpdate&&e.fkTarget.onUpdate!==`NO ACTION`&&(n+=` ON UPDATE ${e.fkTarget.onUpdate}`),t.push(n)}}),_.textContent=`CREATE TABLE ${S(e)} (\n${t.join(`,
554
- `)}\n);`}function w(){d.innerHTML=``,p.textContent=o.length.toString(),o.forEach((e,t)=>{let n=document.createElement(`div`);n.className=`table-column-row`;let r=!!(e.fkTarget&&e.fkTarget.table&&e.fkTarget.column),i=r&&e.fkTarget.onDelete&&e.fkTarget.onDelete!==`NO ACTION`?` (${e.fkTarget.onDelete})`:``,f=r?`${e.fkTarget.table}.${e.fkTarget.column}${i}`:`+ FK`;n.innerHTML=`
555
- <div>
556
- <input type="text" class="table-col-input col-name-input" data-index="${t}" value="${e.name}" placeholder="column_name" />
557
- </div>
558
- <div>
559
- <div class="table-col-select-wrap">
560
- <select class="table-col-select col-type-select" data-index="${t}">
561
- ${a.map(t=>`<option value="${t}" ${t===e.type.toUpperCase()?`selected`:``}>${t}</option>`).join(``)}
562
- </select>
563
- <span class="material-symbols-outlined table-col-select-arrow">expand_more</span>
564
- </div>
565
- </div>
566
- <div style="display: flex; justify-content: center; align-items: center;">
567
- <input type="checkbox" class="table-col-checkbox col-pk-check" data-index="${t}" ${e.primaryKey?`checked`:``} title="Primary Key" />
568
- </div>
569
- <div style="display: flex; justify-content: center; align-items: center;">
570
- <input type="checkbox" class="table-col-checkbox col-null-check" data-index="${t}" ${e.nullable?`checked`:``} title="Nullable" ${e.primaryKey?`disabled`:``} />
571
- </div>
572
- <div style="display: flex; justify-content: center; align-items: center;">
573
- <input type="checkbox" class="table-col-checkbox col-unique-check" data-index="${t}" ${e.isUnique?`checked`:``} title="Unique" ${e.primaryKey?`disabled`:``} />
574
- </div>
575
- <div>
576
- <input type="text" class="table-col-input col-default-input" data-index="${t}" value="${e.defaultValue||``}" placeholder="NULL, 'val'..." />
577
- </div>
578
- <div class="table-col-fk-wrap" data-index="${t}">
579
- <button type="button" class="table-col-fk-btn col-fk-btn ${r?`is-active`:``}" data-index="${t}" title="${r?`Foreign Key: ${f}`:`Configure Foreign Key`}">
580
- <span class="material-symbols-outlined">${r?`link`:`add_link`}</span>
581
- <span style="overflow: hidden; text-overflow: ellipsis;">${f}</span>
582
- </button>
583
- <div class="table-fk-popover" id="fk-popover-${t}" style="display: ${u===t?`flex`:`none`};">
584
- <div class="table-fk-popover-title">
585
- <span class="flex items-center gap-1">
586
- <span class="material-symbols-outlined text-primary" style="font-size: 15px;">link</span>
587
- <span>Foreign Key Target</span>
588
- </span>
589
- <button type="button" class="modal-close-btn fk-popover-close" style="width: 20px; height: 20px;" title="Close">
590
- <span class="material-symbols-outlined" style="font-size: 14px;">close</span>
591
- </button>
592
- </div>
593
- <div class="table-fk-popover-field">
594
- <label>Target Table</label>
595
- <div class="table-col-select-wrap">
596
- <select class="table-col-select fk-target-table-select">
597
- <option value="">-- Select Table --</option>
598
- ${s.map(t=>`<option value="${t}" ${e.fkTarget?.table===t?`selected`:``}>${t}</option>`).join(``)}
599
- </select>
600
- <span class="material-symbols-outlined table-col-select-arrow">expand_more</span>
601
- </div>
602
- </div>
603
- <div class="table-fk-popover-field">
604
- <label>Target Column</label>
605
- <div class="table-col-select-wrap">
606
- <select class="table-col-select fk-target-column-select" ${e.fkTarget?.table?``:`disabled`}>
607
- <option value="">-- Select Column --</option>
608
- </select>
609
- <span class="material-symbols-outlined table-col-select-arrow">expand_more</span>
610
- </div>
611
- </div>
612
- <div class="table-fk-popover-grid-2">
613
- <div class="table-fk-popover-field">
614
- <label>On Delete</label>
615
- <div class="table-col-select-wrap">
616
- <select class="table-col-select fk-on-delete-select">
617
- <option value="NO ACTION" ${!e.fkTarget?.onDelete||e.fkTarget?.onDelete===`NO ACTION`?`selected`:``}>NO ACTION</option>
618
- <option value="CASCADE" ${e.fkTarget?.onDelete===`CASCADE`?`selected`:``}>CASCADE</option>
619
- <option value="SET NULL" ${e.fkTarget?.onDelete===`SET NULL`?`selected`:``}>SET NULL</option>
620
- <option value="RESTRICT" ${e.fkTarget?.onDelete===`RESTRICT`?`selected`:``}>RESTRICT</option>
621
- </select>
622
- <span class="material-symbols-outlined table-col-select-arrow">expand_more</span>
623
- </div>
624
- </div>
625
- <div class="table-fk-popover-field">
626
- <label>On Update</label>
627
- <div class="table-col-select-wrap">
628
- <select class="table-col-select fk-on-update-select">
629
- <option value="NO ACTION" ${!e.fkTarget?.onUpdate||e.fkTarget?.onUpdate===`NO ACTION`?`selected`:``}>NO ACTION</option>
630
- <option value="CASCADE" ${e.fkTarget?.onUpdate===`CASCADE`?`selected`:``}>CASCADE</option>
631
- <option value="RESTRICT" ${e.fkTarget?.onUpdate===`RESTRICT`?`selected`:``}>RESTRICT</option>
632
- <option value="SET NULL" ${e.fkTarget?.onUpdate===`SET NULL`?`selected`:``}>SET NULL</option>
633
- </select>
634
- <span class="material-symbols-outlined table-col-select-arrow">expand_more</span>
635
- </div>
636
- </div>
637
- </div>
638
- <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 4px;">
639
- <button type="button" class="btn-secondary fk-clear-btn" style="padding: 3px 8px; font-size: 11px; color: var(--color-error);" ${r?``:`disabled`}>Clear FK</button>
640
- <button type="button" class="btn-primary fk-done-btn" style="padding: 3px 10px; font-size: 11px;">Done</button>
641
- </div>
642
- </div>
643
- </div>
644
- <div style="display: flex; justify-content: center; align-items: center; gap: 3px;">
645
- <button type="button" class="table-col-action-btn btn-move-up" data-index="${t}" title="Move Column Up" ${t===0?`disabled`:``}>
646
- <span class="material-symbols-outlined">arrow_upward</span>
647
- </button>
648
- <button type="button" class="table-col-action-btn btn-move-down" data-index="${t}" title="Move Column Down" ${t===o.length-1?`disabled`:``}>
649
- <span class="material-symbols-outlined">arrow_downward</span>
650
- </button>
651
- <button type="button" class="table-col-action-btn btn-del-col" data-index="${t}" title="Remove Column" ${o.length<=1?`disabled`:``}>
652
- <span class="material-symbols-outlined">delete</span>
653
- </button>
654
- </div>
655
- `;let p=n.querySelector(`.col-name-input`);p.oninput=e=>{o[t].name=e.target.value.trim(),C()};let m=n.querySelector(`.col-type-select`);m.onchange=e=>{o[t].type=e.target.value,C()};let h=n.querySelector(`.col-pk-check`);h.onchange=e=>{let n=e.target.checked;o[t].primaryKey=n,n&&(o[t].nullable=!1,o[t].isUnique=!1),w(),C()};let g=n.querySelector(`.col-null-check`);g.onchange=e=>{o[t].nullable=e.target.checked,C()};let _=n.querySelector(`.col-unique-check`);_.onchange=e=>{o[t].isUnique=e.target.checked,C()};let v=n.querySelector(`.col-default-input`);v.oninput=e=>{o[t].defaultValue=e.target.value.trim(),C()};let y=n.querySelector(`.col-fk-btn`),b=n.querySelector(`.table-fk-popover`),x=n.querySelector(`.fk-target-table-select`),S=n.querySelector(`.fk-target-column-select`),T=n.querySelector(`.fk-on-delete-select`),E=n.querySelector(`.fk-on-update-select`),D=n.querySelector(`.fk-popover-close`),O=n.querySelector(`.fk-clear-btn`),k=n.querySelector(`.fk-done-btn`);async function A(e,t){if(!e){S.innerHTML=`<option value="">-- Select Column --</option>`,S.disabled=!0;return}if(S.disabled=!1,S.innerHTML=`<option value="">Loading columns...</option>`,l[e]){j(l[e],t);return}try{let n=await c(e);Array.isArray(n)?l[e]=n.map(e=>e.name):n&&Array.isArray(n.columns)?l[e]=n.columns.map(e=>e.name):l[e]=[`id`],j(l[e],t)}catch{S.innerHTML=`<option value="id">id</option>`}}function j(e,t){S.innerHTML=`<option value="">-- Select Column --</option>`,e.forEach(e=>{let n=document.createElement(`option`);n.value=e,n.textContent=e,(t&&t===e||!t&&e.toLowerCase()===`id`)&&(n.selected=!0),S.appendChild(n)})}y.onclick=n=>{if(n.stopPropagation(),u===t)u=null,b.style.display=`none`;else{u=t,w();let n=document.getElementById(`fk-popover-${t}`);if(n){let r=e.fkTarget?.table||``,i=e.fkTarget?.column||``;r&&A(r,i);let a=document.querySelector(`.col-fk-btn[data-index="${t}"]`);if(a){let e=a.getBoundingClientRect();n.style.top=`${e.bottom+4}px`,n.style.left=`${Math.max(10,Math.min(e.right-270,window.innerWidth-280))}px`}}}},D.onclick=e=>{e.stopPropagation(),u=null,b.style.display=`none`},x.onchange=e=>{let t=e.target.value;A(t,null)},O.onclick=e=>{e.stopPropagation(),o[t].fkTarget=null,u=null,w(),C()},k.onclick=e=>{e.stopPropagation();let n=x.value,r=S.value,i=T?.value,a=E?.value;n&&r?o[t].fkTarget={table:n,column:r,onDelete:i===`NO ACTION`?void 0:i,onUpdate:a===`NO ACTION`?void 0:a}:o[t].fkTarget=null,u=null,w(),C()};let M=n.querySelector(`.btn-move-up`);M&&t>0&&(M.onclick=e=>{e.stopPropagation();let n=o[t];o[t]=o[t-1],o[t-1]=n,u!==null&&(u=null),w(),C()});let N=n.querySelector(`.btn-move-down`);N&&t<o.length-1&&(N.onclick=e=>{e.stopPropagation();let n=o[t];o[t]=o[t+1],o[t+1]=n,u!==null&&(u=null),w(),C()});let P=n.querySelector(`.btn-del-col`);P&&o.length>1&&(P.onclick=e=>{e.stopPropagation(),o.splice(t,1),u===t&&(u=null),w(),C()}),d.appendChild(n)})}w(),C(),f.oninput=()=>{C()},d.onscroll=()=>{u!==null&&(u=null,w())},document.getElementById(`btn-add-column-row`).onclick=()=>{o.push({name:`column_${o.length+1}`,type:`VARCHAR(255)`,primaryKey:!1,nullable:!0,isUnique:!1,defaultValue:``,fkTarget:null}),w(),C(),setTimeout(()=>{d.scrollTop=d.scrollHeight},20)},document.getElementById(`preset-add-timestamps`).onclick=()=>{let e=o.map(e=>e.name.toLowerCase());e.includes(`created_at`)||o.push({name:`created_at`,type:`TIMESTAMP`,primaryKey:!1,nullable:!1,isUnique:!1,defaultValue:`CURRENT_TIMESTAMP`,fkTarget:null}),e.includes(`updated_at`)||o.push({name:`updated_at`,type:`TIMESTAMP`,primaryKey:!1,nullable:!1,isUnique:!1,defaultValue:`CURRENT_TIMESTAMP`,fkTarget:null}),w(),C(),setTimeout(()=>{d.scrollTop=d.scrollHeight},20)},document.getElementById(`preset-add-softdelete`).onclick=()=>{o.map(e=>e.name.toLowerCase()).includes(`deleted_at`)||o.push({name:`deleted_at`,type:`TIMESTAMP`,primaryKey:!1,nullable:!0,isUnique:!1,defaultValue:``,fkTarget:null}),w(),C(),setTimeout(()=>{d.scrollTop=d.scrollHeight},20)},document.getElementById(`preset-add-uuid-pk`).onclick=()=>{let e=o.findIndex(e=>e.primaryKey);e===-1?o.unshift({name:`id`,type:`VARCHAR(36)`,primaryKey:!0,nullable:!1,isUnique:!1,defaultValue:``,fkTarget:null}):(o[e].name=`id`,o[e].type=`VARCHAR(36)`),w(),C()};let T=e=>{u!==null&&!e.target.closest(`.table-col-fk-wrap`)&&(u=null,w())};document.addEventListener(`click`,T);let E=()=>{document.removeEventListener(`keydown`,D),document.removeEventListener(`click`,T),t.remove()},D=e=>{e.key===`Escape`&&E()};document.addEventListener(`keydown`,D),document.getElementById(`close-create-table-modal-btn`).onclick=E,document.getElementById(`cancel-create-table-btn`).onclick=E,m.onclick=async()=>{g.style.display=`none`;let t=f.value.trim();if(!t){g.textContent=`Table name cannot be empty.`,g.style.display=`block`,f.focus();return}if(!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(t)){g.textContent=`Table name must start with a letter/underscore and contain only alphanumeric characters.`,g.style.display=`block`,f.focus();return}let n=new Set;for(let e of o){if(!e.name){alert(`All column names must be non-empty.`);return}let t=e.name.toLowerCase();if(n.has(t)){alert(`Duplicate column name "${e.name}".`);return}n.add(t)}m.disabled=!0,h.textContent=`Creating...`;try{let n=await i(t,o.map(e=>({name:e.name,type:e.type,isPk:!!e.primaryKey,primaryKey:!!e.primaryKey,nullable:!e.primaryKey&&e.nullable!==!1,isUnique:!e.primaryKey&&!!e.isUnique,defaultValue:e.defaultValue||void 0,fkTarget:e.fkTarget&&e.fkTarget.table&&e.fkTarget.column?{table:e.fkTarget.table,column:e.fkTarget.column,onDelete:e.fkTarget.onDelete,onUpdate:e.fkTarget.onUpdate}:void 0})));n.success?(E(),window.showToast?.(`Table "${t}" created successfully!`,`success`),window.refreshTableList&&(await window.refreshTableList(!0),setTimeout(()=>{let e=document.querySelector(`.table-btn[data-table="${t}"]`);e?e.click():(window.AppState.currentTable=t,window.handleSwitchTab(`schema-btn`))},100)),e&&e(t)):alert(`Failed to create table: ${n.error||`Unknown error`}`)}catch(e){alert(`Error: ${e.message}`)}finally{m.disabled=!1,h.textContent=`Create Table`}}}window.openCreateTableModal=D,window.refreshTableList=k;var O=!1;async function k(e=!1){try{let t=document.getElementById(`table-nav`);if(!t)return;e&&(t.innerHTML=`
656
- <div class="sidebar-loading-state">
657
- <span class="material-symbols-outlined animate-spin icon-18">progress_activity</span>
658
- <span>Refreshing tables...</span>
659
- </div>
660
- `);let n=await r();if(t.innerHTML=``,await M(),n.success&&n.data&&n.data.length>0){let r=n.data,i=document.getElementById(`table-count-badge`);if(i&&(i.textContent=r.length.toString()),r.forEach(e=>{let n=document.createElement(`button`);n.type=`button`,n.className=`table-btn`,n.dataset.table=e,n.title=e,n.innerHTML=`
661
- <div class="table-btn-label">
662
- <i class="material-symbols-outlined table-item-icon">table_chart</i>
663
- <span class="table-name-text">${e}</span>
664
- <span class="dirty-indicator-dot hidden" title="Unsaved changes"></span>
665
- </div>
666
- <span class="table-btn-badge hidden" id="badge-${e}"></span>
667
- `,n.onclick=()=>{window.fkNavHistory&&window.fkNavHistory.toTable!==e&&(window.fkNavHistory=null),window.AppState.currentTable=e,window.AppState.currentTableBtnElement=n,document.querySelectorAll(`.table-btn`).forEach(e=>e.classList.remove(`active`)),n.classList.add(`active`),window.AppState.currentTab===`erd-btn`||window.AppState.currentTab===`sql-btn`||window.AppState.currentTab===`status-btn`?window.handleSwitchTab(`data-btn`):window.renderCurrentView()},n.oncontextmenu=t=>{w(t,[{icon:`content_copy`,label:`Copy Table Name`,action:async()=>{try{await navigator.clipboard.writeText(e)}catch{let t=document.createElement(`textarea`);t.value=e,document.body.appendChild(t),t.select(),document.execCommand(`copy`),t.remove()}window.showToast&&window.showToast(`Copied "${e}"`,`success`)}},{icon:`auto_fix_high`,label:`Generate Mock Data...`,action:async()=>{let t=await c(e);E(e,t.success?t.data:[],()=>{window.AppState.currentTable===e&&window.renderCurrentView()})}},{icon:`table_chart`,label:`View Data`,action:()=>{n.click(),window.handleSwitchTab(`data-btn`)}},{icon:`schema`,label:`View Schema`,action:()=>{n.click(),window.handleSwitchTab(`schema-btn`)}},{type:`divider`},{icon:`download`,label:`Export as CSV`,action:()=>{window.open(`/api/tables/${e}/export?format=csv`,`_blank`)}},{icon:`download`,label:`Export as JSON`,action:()=>{window.open(`/api/tables/${e}/export?format=json`,`_blank`)}},{type:`divider`},{icon:`mop`,label:`Truncate Table (Clear Rows)`,danger:!0,action:async()=>{if(confirm(`Are you sure you want to delete ALL data from table "${e}"? This cannot be undone!`)){let t=await p(e);t.success?(window.showToast&&window.showToast(`Cleared all rows from "${e}"`,`info`),window.AppState.currentTable===e&&window.renderCurrentView(),A()):alert(`Failed to clear table: ${t.error}`)}}}])},t.appendChild(n)}),window.AppState.currentTable){let e=document.querySelector(`.table-btn[data-table="${window.AppState.currentTable}"]`);e&&(e.classList.add(`active`),window.AppState.currentTableBtnElement=e)}else e||window.handleSwitchTab(`data-btn`);A();let a=document.getElementById(`search-input`);a&&a.value.trim()&&N(a.value.trim())}else{let n=document.getElementById(`table-count-badge`);n&&(n.textContent=`0`),t.innerHTML=`
668
- <div id="not-found-msg">
669
- <span class="material-symbols-outlined icon-20 text-soft">inventory_2</span>
670
- <span>No Tables Found</span>
671
- </div>
672
- `,e||(window.AppState.dbType===`none`?window.handleSwitchTab(`connect-btn`):window.handleSwitchTab(`sql-btn`))}O||=(P(),!0)}catch(e){console.error(`Failed to fetch tables:`,e);let t=document.getElementById(`table-nav`);t&&(t.innerHTML=`
673
- <div id="not-found-msg" class="text-error">
674
- <span class="material-symbols-outlined icon-20">warning</span>
675
- <span>Failed to load tables</span>
676
- </div>
677
- `);let n=document.getElementById(`sidebar-db-status-dot`),r=document.getElementById(`brand-db-type`);n&&(n.classList.remove(`status-connected`),n.classList.add(`status-error`)),r&&(r.textContent=`ERROR`)}}async function A(){try{let e=await o();e.success&&e.data&&Object.entries(e.data).forEach(([e,t])=>{let n=document.getElementById(`badge-${e}`);n&&(n.textContent=Number(t).toLocaleString(),n.classList.remove(`hidden`),n.style.display=`inline-flex`)})}catch(e){console.error(`Failed to load table stats:`,e)}}window.loadTableStats=A;function j(e){let t=e||window.AppState?.currentTable;if(!t)return;document.querySelectorAll(`.table-btn`).forEach(e=>e.classList.remove(`active`));let n=document.querySelector(`.table-btn[data-table="${t}"]`);n&&(n.classList.add(`active`),window.AppState&&(window.AppState.currentTableBtnElement=n))}window.updateSidebarActiveTable=j,window.initSidebar=k;async function M(){let e=document.getElementById(`sidebar-db-status-dot`),t=document.getElementById(`brand-db-type`);try{if(!t)return;let n=await a();n?.success&&n.data?.connected&&n.data?.dbType&&n.data.dbType!==`none`?(window.AppState.dbType=n.data.dbType,t.textContent=n.data.dbType.toUpperCase(),e&&(e.classList.remove(`status-error`,`status-warning`),e.classList.add(`status-connected`)),window.setStudioConnectionMode?.(!0)):(window.AppState.dbType=`none`,t.textContent=`NO DATABASE`,e&&(e.classList.remove(`status-connected`,`status-error`),e.classList.add(`status-warning`)),window.setStudioConnectionMode?.(!1))}catch{e&&(e.classList.remove(`status-connected`,`status-warning`),e.classList.add(`status-error`)),t&&(t.textContent=`DISCONNECTED`),window.setStudioConnectionMode?.(!1)}}function N(e){let t=e.toLowerCase().trim(),n=document.querySelectorAll(`.table-btn`),r=0;n.forEach(e=>{(e.dataset.table||``).toLowerCase().includes(t)?(e.style.display=`flex`,r++):e.style.display=`none`});let i=document.getElementById(`table-nav`),a=document.getElementById(`sidebar-no-match`);r===0&&n.length>0?a||(a=document.createElement(`div`),a.id=`sidebar-no-match`,a.className=`sidebar-empty-state`,a.innerHTML=`
678
- <span class="material-symbols-outlined icon-20 text-soft">search_off</span>
679
- <span>No matching tables</span>
680
- `,i?.appendChild(a)):a&&a.remove();let o=document.getElementById(`table-count-badge`);o&&(o.textContent=r.toString())}function P(){let e=document.getElementById(`search-input`),t=document.getElementById(`search-clear-btn`),n=document.getElementById(`refresh-tables-btn`),r=document.getElementById(`sidebar-collapse-btn`),i=document.getElementById(`sidebar-panel`);e&&(e.addEventListener(`input`,e=>{let n=e.target.value;t&&(n?t.classList.remove(`hidden`):t.classList.add(`hidden`)),N(n)}),e.addEventListener(`keydown`,t=>{if(t.key===`Enter`){t.preventDefault();let n=document.querySelector(`.table-btn:not([style*="display: none"])`);n&&(n.click(),e.blur())}}),t&&t.addEventListener(`click`,()=>{e.value=``,t.classList.add(`hidden`),N(``),e.focus()}));let a=document.getElementById(`add-table-btn`);if(a&&a.addEventListener(`click`,()=>{D()}),n&&n.addEventListener(`click`,async()=>{n.classList.add(`animate-spin`),await k(!0),setTimeout(()=>{n.classList.remove(`animate-spin`),window.showToast&&window.showToast(`Tables refreshed`,`success`)},300)}),i){localStorage.getItem(`drixio_sidebar_collapsed`)===`true`&&(i.classList.add(`collapsed`),F(!0)),r&&r.addEventListener(`click`,e=>{e.stopPropagation(),i.classList.toggle(`collapsed`);let t=i.classList.contains(`collapsed`);localStorage.setItem(`drixio_sidebar_collapsed`,t.toString()),F(t)});let e=document.getElementById(`sidebar-header`);e&&e.addEventListener(`click`,()=>{i.classList.contains(`collapsed`)&&(i.classList.remove(`collapsed`),localStorage.setItem(`drixio_sidebar_collapsed`,`false`),F(!1))})}document.addEventListener(`keydown`,n=>{if(n.key===`/`&&document.activeElement?.tagName!==`INPUT`&&document.activeElement?.tagName!==`TEXTAREA`){if(n.preventDefault(),window.AppState?.currentTab===`erd-btn`){let e=document.getElementById(`erd-search-input`);if(e){e.focus(),e.select();return}}e?.focus(),e?.select()}else n.key===`Escape`&&document.activeElement===e&&(e.value&&(e.value=``,t?.classList.add(`hidden`),N(``)),e.blur())})}function F(e){let t=document.getElementById(`sidebar-collapse-icon`),n=document.getElementById(`sidebar-header`);t&&(t.textContent=e?`menu`:`menu_open`),n&&(n.title=e?`Click to expand sidebar`:``)}function I(e,t){document.querySelectorAll(`#${e} .cell-in-range, #${e} .cell-selected, #${e} .range-top, #${e} .range-bottom, #${e} .range-left, #${e} .range-right, #${e} .row-header-selected`).forEach(e=>{e.classList.remove(`cell-in-range`,`cell-selected`,`range-top`,`range-bottom`,`range-left`,`range-right`,`row-header-selected`)});let n=t.selection;if(n.startRow===-1)return;let r=Math.min(n.startRow,n.endRow),i=Math.max(n.startRow,n.endRow),a=Math.min(n.startCol,n.endCol),o=Math.max(n.startCol,n.endCol);document.querySelectorAll(`#${e} td.data-cell`).forEach(e=>{let s=parseInt(e.dataset.rowIdx),c=parseInt(e.dataset.colIdx);s>=r&&s<=i&&c>=a&&c<=o&&(e.classList.add(`cell-in-range`),s===r&&e.classList.add(`range-top`),s===i&&e.classList.add(`range-bottom`),c===a&&e.classList.add(`range-left`),c===o&&e.classList.add(`range-right`),s===n.startRow&&c===n.startCol&&(e.classList.add(`cell-selected`),t.selectedCell=e))}),n.isDraggingRow&&document.querySelectorAll(`#${e} td.row-header`).forEach(e=>{let t=parseInt(e.dataset.rowIdx);t>=r&&t<=i&&e.classList.add(`row-header-selected`)})}function L(e,t,n,r){e.addEventListener(`mousedown`,e=>{if(e.target.tagName===`INPUT`||e.target.tagName===`SELECT`)return;let i=e.target.closest(`td.row-header`);if(i&&e.button!==2){let e=parseInt(i.dataset.rowIdx);n.selection={isDragging:!1,isDraggingRow:!0,isRowSelection:!0,startRow:e,endRow:e,startCol:0,endCol:r-1},I(t,n);return}let a=e.target.closest(`td.data-cell`);if(!a)return;let o=parseInt(a.dataset.rowIdx),s=parseInt(a.dataset.colIdx);n.selection={isDragging:!0,isRowSelection:!1,startRow:o,startCol:s,endRow:o,endCol:s},I(t,n)}),e.addEventListener(`mouseover`,e=>{if(n.selection.isDraggingRow){let r=e.target.closest(`td.row-header`);if(!r)return;n.selection.endRow=parseInt(r.dataset.rowIdx),I(t,n);return}if(!n.selection.isDragging)return;let r=e.target.closest(`td.data-cell`);r&&(n.selection.endRow=parseInt(r.dataset.rowIdx),n.selection.endCol=parseInt(r.dataset.colIdx),I(t,n))}),e.addEventListener(`mouseup`,()=>{n.selection&&(n.selection.isDragging=!1,n.selection.isDraggingRow=!1)})}function R(e,t){let n=document.createElement(`div`);n.className=`resizer`,e.appendChild(n),n.addEventListener(`click`,e=>e.stopPropagation()),n.addEventListener(`mousedown`,n=>{n.preventDefault(),n.stopPropagation();let r=n.pageX,i=e.offsetWidth,a=!1,o=n=>{a=!0,t&&(t.isResizing=!0);let o=i+(n.pageX-r);e.style.width=o+`px`,e.style.minWidth=o+`px`,e.style.maxWidth=o+`px`},s=()=>{document.removeEventListener(`mousemove`,o),document.removeEventListener(`mouseup`,s),document.body.style.cursor=``,a&&setTimeout(()=>{t&&(t.isResizing=!1)},100)};document.body.style.cursor=`col-resize`,document.addEventListener(`mousemove`,o),document.addEventListener(`mouseup`,s)})}function z(){let e=window.AppState?.currentTable;if(!e)return``;let t=document.getElementById(`filter-val-${e}`)?.value.trim(),n=document.getElementById(`filter-op-${e}`)?.value,r=document.getElementById(`filter-col-${e}`)?.value,i=``;if(t||n===`IS NULL`){let e=t;e&&!e.startsWith(`'`)&&!e.endsWith(`'`)&&isNaN(Number(e))&&(e.toUpperCase().includes(` AND `)||e.toUpperCase().includes(` OR `)||(e=`'${e.replace(/'/g,`''`)}'`)),i=`"${r}" ${n} ${e}`}return i}function ee(e,t){if(e==null||e===`null`||e===``)return{html:`<em>null</em>`,title:``};let n=(t?.type||``).toUpperCase(),r=n===`DATE`,i=n.includes(`DATETIME`)||n.includes(`TIMESTAMP`),a=n.includes(`BOOL`)||n===`TINYINT(1)`,o=String(e).trim(),s=n.match(/(?:NUMERIC|DECIMAL|FLOAT|DOUBLE)\s*\(\s*\d+\s*,\s*(\d+)\s*\)/);if(s&&!isNaN(Number(o))&&!o.toLowerCase().includes(`e`)){let e=parseInt(s[1],10);if(e>0){let[t,n=``]=o.split(`.`);if(n.length<e){n=n.padEnd(e,`0`);let r=`${t}.${n}`;return{html:r,title:o===r?``:`Raw: ${o}`}}}}if(a){let e=o===`1`||o.toLowerCase()===`true`,t=o===`0`||o.toLowerCase()===`false`;if(e)return{html:`<span class="badge-bool bool-true">true</span>`,title:`Boolean: ${o}`};if(t)return{html:`<span class="badge-bool bool-false">false</span>`,title:`Boolean: ${o}`}}if((r||i)&&o){let e=null,t=!1;if(/^\d{13}$/.test(o)?(e=new Date(parseInt(o,10)),t=!0):/^\d{10}$/.test(o)?(e=new Date(parseInt(o,10)*1e3),t=!0):(o.includes(`T`)||o.includes(`Z`)||r&&o.length>10&&o.includes(` `))&&(e=new Date(o.includes(` `)&&!o.includes(`T`)?o.replace(` `,`T`):o)),e&&!isNaN(e.getTime())){let n=`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,`0`)}-${String(e.getDate()).padStart(2,`0`)}`;if(i){let t=String(e.getHours()).padStart(2,`0`),r=String(e.getMinutes()).padStart(2,`0`),i=String(e.getSeconds()).padStart(2,`0`);n+=` ${t}:${r}:${i}`}return{html:n.replace(/</g,`&lt;`),title:t?`Raw Timestamp: ${o}`:o===n?``:`Raw: ${o}`}}}return{html:o.replace(/</g,`&lt;`),title:``}}function te(e,t,n,r,i=[]){let a=`<td class="row-header" data-row-idx="${t}">${t+1}</td>`,o=n?e[n]:t;return r.forEach((n,r)=>{let s=i.find(e=>e.name===n),c=e[n];typeof c==`object`&&c&&(c=c.type===`Buffer`&&Array.isArray(c.data)?`[BLOB ${c.data.length} B]`:JSON.stringify(c));let l=c==null?`null`:String(c),u=l.replace(/"/g,`&quot;`),{html:d,title:f}=ee(l,s),p=f?` title="${f}"`:``;if(s&&s.fkTarget&&l!==`null`){let e=s.fkTarget.table,i=s.fkTarget.column;a+=`<td class="data-cell is-fk-cell" data-row-idx="${t}" data-col-idx="${r}" data-pk="${o}" data-col="${n}" data-original="${u}" data-fk-table="${e}" data-fk-col="${i}"${p}><span class="cell-text">${d}</span><button type="button" class="fk-jump-btn" title="View & jump to ${e} (${i} = ${u})" data-fk-table="${e}" data-fk-col="${i}" data-fk-val="${u}"><span class="material-symbols-outlined">open_in_new</span></button></td>`}else a+=`<td class="data-cell" data-row-idx="${t}" data-col-idx="${r}" data-pk="${o}" data-col="${n}" data-original="${u}"${p}><span class="cell-text">${d}</span></td>`}),a}async function ne(){if(!window.DataGrid)return;let{pendingEdits:e,pendingInserts:t,pendingDeletes:n,pkColumn:r,schema:i}=window.DataGrid,a=window.AppState.currentTable;if(!a||!r){alert(`Cannot save: No Primary Key detected for this table.`);return}let o=Object.keys(e||{}).length>0,s=t?.some(e=>Object.values(e).some(e=>e!==``&&e!=null)),c=n&&n.size>0;if(!o&&!s&&!c)return;let l=n?Array.from(n):[];try{let n=await d(a,{pkColumn:r,edits:e,inserts:t,deletes:l,schema:i||[]});if(n.success)window.DataGrid&&(window.DataGrid.pendingEdits={},window.DataGrid.pendingInserts=[{}],window.DataGrid.pendingDeletes=new Set,window.DataGrid.history=[],window.DataGrid.currentTransaction=null,window.DataGrid.refreshData?window.DataGrid.refreshData():window.renderCurrentView()),window.updateSidebarActiveTable?.(),window.loadTableStats?.(),window.showToast&&window.showToast(`Data saved successfully!`);else{let e=n.error||`Failed to save changes`;window.showToast?window.showToast(`Save failed: `+e,`error`):alert(`Save failed:
681
- `+e),document.querySelectorAll(`.cell-edited`).forEach(e=>{e.classList.remove(`cell-edited`),e.classList.add(`cell-error`)})}}catch(e){let t=e.message||`Network error`;window.showToast?window.showToast(`Save failed: `+t,`error`):alert(`Save failed:
682
- `+t),document.querySelectorAll(`.cell-edited`).forEach(e=>{e.classList.remove(`cell-edited`),e.classList.add(`cell-error`)})}window.updateSidebarDirtyState?.()}function re(e,t,n,r=!0){if(r&&window.DataGrid.currentTransaction){let n=e.textContent;(n===`null`||e.classList.contains(`ghost-row`))&&(n=``),n!==t&&window.DataGrid.currentTransaction.push({td:e,oldVal:n,newVal:t})}let i=e.dataset.col,a=e.dataset.colIdx===`0`?`<span class="ghost-cell-hint">+ Add Row</span>`:``,o=ee(t,(window.DataGrid?.schema||[]).find(e=>e.name===i));if(e.innerHTML=t?`<span class="cell-text">${o.html}</span>`:e.dataset.insertIndex===void 0?`<em>null</em>`:a,o.title?e.title=o.title:e.removeAttribute(`title`),e.dataset.insertIndex!==void 0){let r=parseInt(e.dataset.insertIndex);if(window.DataGrid.pendingInserts[r]||(window.DataGrid.pendingInserts[r]={}),t){if(window.DataGrid.pendingInserts[r][i]=t,e.classList.add(`cell-edited`),e.classList.remove(`ghost-row`),r===window.DataGrid.pendingInserts.length-1){e.closest(`tr`).classList.remove(`ghost-row-tr`),window.DataGrid.pendingInserts.push({});let t=document.querySelector(`#data-grid-table-${window.AppState.currentTable} tbody`),i=document.createElement(`tr`);i.className=`ghost-row-tr`;let a=parseInt(e.dataset.rowIdx)+1;i.innerHTML+=`<td class="row-header" data-row-idx="${a}">*</td>`,n.forEach((e,t)=>{let n=t===0?`<span class="ghost-cell-hint">+ Add Row</span>`:``;i.innerHTML+=`<td class="data-cell ghost-row" data-row-idx="${a}" data-col-idx="${t}" data-insert-index="${r+1}" data-col="${e}">${n}</td>`}),t.appendChild(i)}}else window.DataGrid.pendingInserts[r]&&delete window.DataGrid.pendingInserts[r][i],e.classList.remove(`cell-edited`),e.classList.add(`ghost-row`)}else{let n=e.dataset.pk;t===e.dataset.original?(e.classList.remove(`cell-edited`),e.classList.remove(`cell-error`),window.DataGrid.pendingEdits[n]&&(delete window.DataGrid.pendingEdits[n][i],Object.keys(window.DataGrid.pendingEdits[n]).length===0&&delete window.DataGrid.pendingEdits[n])):(window.DataGrid.pendingEdits[n]||(window.DataGrid.pendingEdits[n]={}),window.DataGrid.pendingEdits[n][i]=t,e.classList.add(`cell-edited`),e.classList.remove(`cell-error`))}window.updateSidebarDirtyState?.()}function ie(e,t=!0){let n=document.querySelector(`#data-grid-table-${window.AppState.currentTable} td.data-cell[data-row-idx="${e}"]`)?.closest(`tr`);if(!n||n.classList.contains(`ghost-row-tr`)||n.classList.contains(`row-deleted`))return;let r=n.querySelector(`td.data-cell`),i=r?.dataset.pk;if(i!==void 0)t&&window.DataGrid.currentTransaction&&window.DataGrid.currentTransaction.push({type:`delete`,rowIdx:e,pk:i}),window.DataGrid.pendingDeletes.add(i),n.classList.add(`row-deleted`);else if(r?.dataset.insertIndex!==void 0){let e=parseInt(r.dataset.insertIndex);window.DataGrid.pendingInserts[e]&&delete window.DataGrid.pendingInserts[e],n.remove()}window.updateSidebarDirtyState?.()}function ae(e,t){let n=document.querySelector(`#data-grid-table-${window.AppState.currentTable} td.data-cell[data-row-idx="${e}"]`)?.closest(`tr`);n&&n.classList.remove(`row-deleted`),window.DataGrid.pendingDeletes.delete(t),window.updateSidebarDirtyState?.()}function oe(e,t){e.forEach(e=>{let n=window.AppState.currentTable,r=document.querySelector(`#data-grid-table-${n} td.data-cell[data-row-idx="${e}"]`)?.closest(`tr`);if(!r||r.classList.contains(`ghost-row-tr`))return;let i=document.querySelector(`#data-grid-table-${n} .ghost-row-tr`);i&&t.forEach((e,n)=>{let a=r.querySelector(`td.data-cell[data-col-idx="${n}"]`),o=i.querySelector(`td.data-cell[data-col-idx="${n}"]`);if(a&&o){let n=a.dataset.insertIndex===void 0?a.classList.contains(`cell-edited`)?window.DataGrid.pendingEdits[a.dataset.pk]?.[e]:a.dataset.original:window.DataGrid.pendingInserts[a.dataset.insertIndex][e];n!=null&&n!==`null`&&re(o,n,t,!0)}})})}function se(e,t,n){e.addEventListener(`dblclick`,e=>{let r=e.target.closest(`td.data-cell`);if(!r||r.querySelector(`input, select`))return;let i=r.dataset.col,a=t.find(e=>e.name===i),o=a?.type?.toUpperCase()||``;if(o.includes(`BLOB`)||o===`BYTEA`||o.includes(`BINARY`)){window.showToast&&window.showToast(`Binary (BLOB) data cannot be edited directly.`,`info`);return}let c=o.includes(`ENUM`)||a?.enumValues&&a.enumValues.length>0,l=o===`DATE`,u=o.includes(`DATETIME`)||o.includes(`TIMESTAMP`),d=o.includes(`BOOL`)||o===`TINYINT(1)`,f=o===`TIME`||o.startsWith(`TIME(`),p=r.classList.contains(`ghost-row`)||r.dataset.insertIndex!==void 0&&!r.classList.contains(`cell-edited`),m=r.dataset.pk,h=r.dataset.insertIndex,g=h===void 0?m===void 0?void 0:window.DataGrid?.pendingEdits?.[m]?.[i]:window.DataGrid?.pendingInserts?.[parseInt(h,10)]?.[i],_=``;p||(_=g===void 0?r.dataset.original===void 0?r.querySelector(`.cell-text`)?.textContent.trim()||r.textContent.trim():r.dataset.original:String(g)),(_===`null`||_===`+ New`||_===`+ Add Row`)&&(_=``);let v;if(a&&a.fkTarget){v=document.createElement(`select`);let e=document.createElement(`option`);e.value=_,e.textContent=`Loading...`,v.appendChild(e);let{table:t,column:n}=a.fkTarget;s(t,{limit:100}).then(e=>{if(e.success&&e.data&&e.data.rows){if(v.innerHTML=``,a.nullable){let e=document.createElement(`option`);e.value=``,e.textContent=`-- None --`,_||(e.selected=!0),v.appendChild(e)}let t=n;if(e.data.columns){let n=[`name`,`title`,`label`,`description`],r=e.data.columns.find(e=>n.includes(e.toLowerCase()));r&&(t=r)}if(e.data.rows.forEach(e=>{let r=String(e[n]),i=t===n?r:`${r} - ${e[t]}`,a=document.createElement(`option`);a.value=r,a.textContent=i,r===_&&(a.selected=!0),v.appendChild(a)}),_&&!e.data.rows.find(e=>String(e[n])===_)){let e=document.createElement(`option`);e.value=_,e.textContent=`${_} (Not in limit)`,e.selected=!0,v.appendChild(e)}}}).catch(t=>{e.textContent=`Error loading options`})}else if(c){let e=a?.enumValues||[];if(e.length===0&&a?.type){let t=a.type.match(/enum\((.*?)\)/i);t&&(e=t[1].split(`,`).map(e=>e.trim().replace(/^'|'$/g,``)))}if(v=document.createElement(`select`),a?.nullable){let e=document.createElement(`option`);e.value=``,e.textContent=`-- None --`,_||(e.selected=!0),v.appendChild(e)}e.forEach(e=>{let t=document.createElement(`option`);t.value=e,t.textContent=e,e===_&&(t.selected=!0),v.appendChild(t)})}else if(l){v=document.createElement(`input`),v.type=`date`;let e=``;if(_)if(/^\d{13}$/.test(_)){let t=new Date(parseInt(_,10));isNaN(t.getTime())||(e=`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,`0`)}-${String(t.getDate()).padStart(2,`0`)}`)}else if(/^\d{10}$/.test(_)){let t=new Date(parseInt(_,10)*1e3);isNaN(t.getTime())||(e=`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,`0`)}-${String(t.getDate()).padStart(2,`0`)}`)}else if(/^\d{4}-\d{2}-\d{2}/.test(_))e=_.slice(0,10);else{let t=new Date(_);isNaN(t.getTime())||(e=`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,`0`)}-${String(t.getDate()).padStart(2,`0`)}`)}v.value=e}else if(u){v=document.createElement(`input`),v.type=`datetime-local`;let e=``;if(_){let t=null;t=/^\d{13}$/.test(_)?new Date(parseInt(_,10)):/^\d{10}$/.test(_)?new Date(parseInt(_,10)*1e3):_.includes(`T`)||_.includes(` `)?new Date(_.replace(` `,`T`)):new Date(_),t&&!isNaN(t.getTime())&&(e=`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,`0`)}-${String(t.getDate()).padStart(2,`0`)}T${String(t.getHours()).padStart(2,`0`)}:${String(t.getMinutes()).padStart(2,`0`)}`)}v.value=e}else f?(v=document.createElement(`input`),v.type=`time`,v.step=`1`,v.value=_?_.slice(0,8):``):d?(v=document.createElement(`input`),v.type=`checkbox`,v.checked=_===`1`||_.toLowerCase()===`true`):(v=document.createElement(`input`),v.type=`text`,v.value=_);let y=!1,b=()=>{y=!0;let e=document.getElementById(`modal-editor-overlay`),t=document.getElementById(`modal-textarea`),n=document.getElementById(`modal-cancel-btn`),r=document.getElementById(`modal-save-btn`),a=document.getElementById(`modal-title`);if(!e||!t)return;a.textContent=`Edit ${i}`,t.value=v.value,e.classList.remove(`hidden`),e.style.display=`flex`,t.focus();let o=()=>{e.classList.add(`hidden`),e.style.display=`none`,r.onclick=null,n.onclick=null,y=!1,v.focus()};n.onclick=o,r.onclick=()=>{v.value=t.value,o(),x()},e.onclick=t=>{t.target===e&&o()}};if(r.innerHTML=``,r.appendChild(v),o===``||!c&&!l&&!u&&!f&&!d&&!(a&&a.fkTarget)){let e=document.createElement(`span`);e.className=`material-symbols-outlined cell-expand-btn`,e.textContent=`open_in_full`,e.title=`Open Full Editor (Shift + Enter)`,e.onmousedown=e=>{e.preventDefault(),e.stopPropagation()},e.onclick=e=>{e.preventDefault(),e.stopPropagation(),b()},r.appendChild(e),v.style.width=`100%`,v.style.paddingRight=`32px`,v.addEventListener(`keydown`,e=>{e.key===`Enter`&&e.shiftKey?(e.preventDefault(),b()):e.key===`Enter`&&v.blur(),e.key===`Tab`&&(e.preventDefault(),v.blur(),document.body.dispatchEvent(new KeyboardEvent(`keydown`,{key:`Tab`,shiftKey:e.shiftKey,bubbles:!0})))})}else v.addEventListener(`keydown`,e=>{if(e.key===`Enter`&&v.blur(),e.key===`Tab`){if(l||u||f)return;e.preventDefault(),v.blur(),document.body.dispatchEvent(new KeyboardEvent(`keydown`,{key:`Tab`,shiftKey:e.shiftKey,bubbles:!0}))}});v.focus();let x=()=>{if(y)return;let e=d?v.checked?`1`:`0`:v.value,t=o.match(/(?:NUMERIC|DECIMAL|FLOAT|DOUBLE)\s*\(\s*\d+\s*,\s*(\d+)\s*\)/);if(t&&e&&!isNaN(Number(e))&&!e.toLowerCase().includes(`e`)){let n=parseInt(t[1],10);if(n>0){let[t,r=``]=e.split(`.`);r.length<n&&(r=r.padEnd(n,`0`),e=`${t}.${r}`)}}if(f&&e&&/^\d{2}:\d{2}$/.test(e)&&(e+=`:00`),l&&e){if(/^\d{13}$/.test(_)){let t=new Date(e);isNaN(t.getTime())||(e=String(t.getTime()))}else if(/^\d{10}$/.test(_)){let t=new Date(e);isNaN(t.getTime())||(e=String(Math.floor(t.getTime()/1e3)))}}else if(u&&e)if(/^\d{13}$/.test(_)){let t=new Date(e);isNaN(t.getTime())||(e=String(t.getTime()))}else if(/^\d{10}$/.test(_)){let t=new Date(e);isNaN(t.getTime())||(e=String(Math.floor(t.getTime()/1e3)))}else e=e.replace(`T`,` `),/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/.test(e)&&(e+=`:00`);window.DataGrid.currentTransaction=[],re(r,e,n),window.DataGrid.currentTransaction.length>0&&window.DataGrid.history.push(window.DataGrid.currentTransaction),window.DataGrid.currentTransaction=null};v.addEventListener(`blur`,()=>{setTimeout(()=>{!y&&document.activeElement!==v&&x()},120)})})}var B=null,V=null,ce=null,H=null;function le(){return B||(B=document.createElement(`div`),B.id=`fk-preview-popover`,B.className=`fk-preview-popover hidden`,document.body.appendChild(B),B.addEventListener(`mouseenter`,()=>{ce&&=(clearTimeout(ce),null)}),B.addEventListener(`mouseleave`,e=>{e.relatedTarget&&H&&H.contains(e.relatedTarget)||ue()}),document.addEventListener(`keydown`,e=>{e.key===`Escape`&&B&&!B.classList.contains(`hidden`)&&ue()}),window.addEventListener(`scroll`,e=>{B&&!B.classList.contains(`hidden`)&&(B.contains(e.target)||ue())},!0),B)}function ue(){V&&=(clearTimeout(V),null),B&&B.classList.add(`hidden`),H=null}function de(e,t,n,r){ue(),window.fkNavHistory={fromTable:r,toTable:e,timestamp:Date.now()};let i=`"${t}" = ${/^-?\d+(\.\d+)?$/.test(n)?n:`'${n.replace(/'/g,`''`)}'`}`,a=document.querySelector(`.table-btn[data-table="${e}"]`)||Array.from(document.querySelectorAll(`.table-btn`)).find(t=>(t.dataset.table||t.querySelector(`span`)?.textContent.trim())===e);window.AppState.currentTable=e,window.AppState.currentTableBtnElement=a||null,window.AppState.currentTab=`data-btn`,document.querySelectorAll(`.table-btn`).forEach(e=>e.classList.remove(`active`)),a&&a.classList.add(`active`);let o=`view-data-btn-${e}`,s=document.getElementById(o);s&&(s.innerHTML=``),window.renderCurrentView(i),window.showToast&&window.showToast(`Jumped to ${e} (${i})`,`info`)}function fe(e,t){let n=le();e.addEventListener(`click`,e=>{let n=e.target.closest(`.fk-jump-btn`);if(!n)return;e.stopPropagation();let r=n.dataset.fkTable,i=n.dataset.fkCol,a=n.dataset.fkVal;r&&i&&a!==void 0&&de(r,i,a,t)}),e.addEventListener(`mouseover`,e=>{let r=e.target.closest(`.is-fk-cell`);r&&(ce&&=(clearTimeout(ce),null),H!==r&&(H=r,V&&clearTimeout(V),V=setTimeout(async()=>{let e=r.dataset.fkTable,i=r.dataset.fkCol,a=r.dataset.original;if(!e||!i||a===void 0||a===`null`)return;n.innerHTML=`
683
- <div class="fk-popover-header">
684
- <div class="fk-popover-title">
685
- <span class="material-symbols-outlined icon-16" style="color: var(--color-primary);">table_chart</span>
686
- <span class="fk-popover-table">${e}</span>
687
- <span class="fk-popover-badge">FK &rarr; ${i}</span>
688
- </div>
689
- </div>
690
- <div class="fk-popover-body">
691
- <div class="fk-popover-loading">
692
- <span class="material-symbols-outlined animate-spin icon-16">progress_activity</span>
693
- <span>Loading relation...</span>
694
- </div>
695
- </div>
696
- `,pe(r,n),n.classList.remove(`hidden`);let o=/^-?\d+(\.\d+)?$/.test(a)?a:`'${a.replace(/'/g,`''`)}'`;try{let c=await s(e,{where:`"${i}" = ${o}`,limit:1});if(H!==r)return;if(c.success&&c.data&&c.data.rows&&c.data.rows.length>0){let o=c.data.rows[0],s=Object.keys(o).slice(0,7),l=``;s.forEach(e=>{let t=o[e];t=t==null?`null`:typeof t==`object`?JSON.stringify(t):String(t),l+=`
697
- <div class="fk-field-row">
698
- <span class="fk-field-name">${e}</span>
699
- <span class="fk-field-value ${t===`null`?`is-null`:``}" title="${t.replace(/"/g,`&quot;`)}">${t}</span>
700
- </div>
701
- `}),n.innerHTML=`
702
- <div class="fk-popover-header">
703
- <div class="fk-popover-title">
704
- <span class="material-symbols-outlined icon-16" style="color: var(--color-primary);">table_chart</span>
705
- <span class="fk-popover-table">${e}</span>
706
- <span class="fk-popover-badge">${i} = ${a}</span>
707
- </div>
708
- <button type="button" class="fk-popover-jump-btn" id="btn-fk-popover-jump">
709
- <span>Jump</span>
710
- <span class="material-symbols-outlined icon-14">arrow_forward</span>
711
- </button>
712
- </div>
713
- <div class="fk-popover-body">
714
- <div class="fk-fields-list">${l}</div>
715
- </div>
716
- <div class="fk-popover-footer">
717
- <span class="material-symbols-outlined icon-14">touch_app</span>
718
- <span>Click to inspect full record</span>
719
- </div>
720
- `,n.querySelector(`#btn-fk-popover-jump`)?.addEventListener(`click`,n=>{n.stopPropagation(),de(e,i,a,t)}),n.onclick=()=>{de(e,i,a,t)},pe(r,n)}else n.innerHTML=`
721
- <div class="fk-popover-header">
722
- <div class="fk-popover-title">
723
- <span class="material-symbols-outlined icon-16" style="color: var(--color-primary);">table_chart</span>
724
- <span class="fk-popover-table">${e}</span>
725
- </div>
726
- </div>
727
- <div class="fk-popover-body">
728
- <div class="fk-popover-empty">
729
- <span class="material-symbols-outlined icon-18">error_outline</span>
730
- <span>No matching row found for ${i} = ${a}</span>
731
- </div>
732
- </div>
733
- `,pe(r,n)}catch(e){if(H!==r)return;n.innerHTML=`
734
- <div class="fk-popover-body">
735
- <div class="fk-popover-empty">
736
- <span>Failed to fetch preview: ${e.message}</span>
737
- </div>
738
- </div>
739
- `,pe(r,n)}},250)))}),e.addEventListener(`mouseout`,e=>{let t=e.target.closest(`.is-fk-cell`);t&&(e.relatedTarget&&(t.contains(e.relatedTarget)||n&&n.contains(e.relatedTarget))||(V&&=(clearTimeout(V),null),ce=setTimeout(()=>{ue()},350)))})}function pe(e,t){let n=e.getBoundingClientRect(),r=t.getBoundingClientRect(),i=n.bottom+2,a=n.left;i+r.height>window.innerHeight-10&&(i=Math.max(10,n.top-r.height-2)),a+r.width>window.innerWidth-10&&(a=Math.max(10,window.innerWidth-r.width-10)),t.style.top=`${i}px`,t.style.left=`${a}px`}async function U(e,t,n=``,r=!1,i=null){if(window.updateSidebarActiveTable)window.updateSidebarActiveTable(e);else{document.querySelectorAll(`.table-btn`).forEach(e=>e.classList.remove(`active`));let n=t||document.querySelector(`.table-btn[data-table="${e}"]`);n&&(n.classList.add(`active`),window.AppState&&(window.AppState.currentTableBtnElement=n))}let a=document.getElementById(`table-name`);a&&(a.textContent=e);let o=i||document.getElementById(`main-content`);r||(o.innerHTML=`<div class='p-6'>Loading...</div>`);try{let i=await c(e);if(!i.success)throw Error(i.error);let a=i.data,l=a.find(e=>e.isPk)?.name;(!r||!window.DataGrid)&&(window.DataGrid={schema:a,pkColumn:l,pendingEdits:{},pendingInserts:[{}],pendingDeletes:new Set,selectedCell:null,history:[],currentTransaction:null,sortState:{col:l||a[0].name,asc:!0},pagination:{limit:50,offset:0,isLoading:!1,hasMore:!0},selection:{isDragging:!1,isDraggingRow:!1,startRow:-1,startCol:-1,endRow:-1,endCol:-1}}),window.TableStates&&window.TableStates[e]&&(window.TableStates[e].dataGrid=window.DataGrid);let u=await s(e,{where:n,limit:window.DataGrid.pagination.limit,offset:window.DataGrid.pagination.offset,orderCol:window.DataGrid.sortState.col,orderAsc:window.DataGrid.sortState.asc});if(u.success&&u.data){window.loadTableStats?.();let i=u.data.rows,c=u.data.columns;window.DataGrid.rows=i,window.DataGrid.columns=c,window.DataGrid.pagination.hasMore=i.length===window.DataGrid.pagination.limit;let d=c.map(e=>`<option value="${e}">${e}</option>`).join(``);r||(o.innerHTML=`
740
- <div class="toolbar">
741
- ${window.fkNavHistory&&window.fkNavHistory.toTable===e?`<button type="button" id="btn-fk-back-${e}" class="fk-back-btn" title="Back to table '${window.fkNavHistory.fromTable}'">
742
- <span class="material-symbols-outlined icon-14">arrow_back</span>
743
- <span>Back to ${window.fkNavHistory.fromTable}</span>
744
- </button>`:``}
745
- <div class="filter-group">
746
- <div class="filter-icon-container">
747
- <span class="material-symbols-outlined" id="filter-icon">filter_list</span>
748
- <span>Filter</span>
749
- </div>
750
- <select id="filter-col-${e}" class="filter-select">${d}</select>
751
- <select id="filter-op-${e}" class="filter-select">
752
- <option value="=">=</option>
753
- <option value=">">&gt;</option>
754
- <option value="<">&lt;</option>
755
- <option value=">=">&gt;=</option>
756
- <option value="<=">&lt;=</option>
757
- <option value="LIKE">LIKE</option>
758
- <option value="!=">!=</option>
759
- </select>
760
- <div class="filter-input-wrapper">
761
- <input type="text" id="filter-val-${e}" class="filter-input" placeholder="Filter value..." />
762
- <button type="button" id="btn-clear-filter-${e}" class="filter-clear-btn hidden" title="Clear filter">
763
- <span class="material-symbols-outlined">close</span>
764
- </button>
765
- </div>
766
- </div>
767
- <button id="btn-refresh-data-${e}" class="refresh-btn" title="Refresh Data (F5)">
768
- <span class="material-symbols-outlined">refresh</span>
769
- </button>
770
- </div>
771
- <div class="table-container" id="data-grid-container-${e}"></div>
772
- `);let f=document.getElementById(`data-grid-container-${e}`);if(!f)return;if((!i||i.length===0)&&!window.forceEmptyGrid)if(n){f.innerHTML=`
773
- <div class="table-empty-container">
774
- <div class="table-empty-card">
775
- <div class="table-empty-icon-wrap">
776
- <span class="material-symbols-outlined table-empty-icon">search_off</span>
777
- </div>
778
- <h3 class="table-empty-title">No matching records found</h3>
779
- <p class="table-empty-desc">No records in "${e}" matched the current filter conditions.</p>
780
- <div class="table-empty-actions">
781
- <button type="button" class="table-empty-btn-primary" id="btn-empty-clear-filter-${e}">
782
- <span class="material-symbols-outlined icon-16">filter_alt_off</span>
783
- <span>Clear Filter</span>
784
- </button>
785
- </div>
786
- </div>
787
- </div>
788
- `;let n=document.getElementById(`btn-empty-clear-filter-${e}`);n&&(n.onclick=()=>{let n=document.getElementById(`filter-val-${e}`);n&&(n.value=``),U(e,t,``,!0)})}else{f.innerHTML=`
789
- <div class="table-empty-container">
790
- <div class="table-empty-card">
791
- <div class="table-empty-icon-wrap">
792
- <span class="material-symbols-outlined table-empty-icon">dataset</span>
793
- </div>
794
- <h3 class="table-empty-title">Table "${e}" is empty</h3>
795
- <p class="table-empty-desc">This table currently contains 0 records. You can insert a new row manually or generate realistic mock data with one click.</p>
796
- <div class="table-empty-actions">
797
- <button type="button" class="table-empty-btn-secondary" id="btn-empty-add-row-${e}">
798
- <span class="material-symbols-outlined icon-16">add</span>
799
- <span>Insert Row</span>
800
- </button>
801
- <button type="button" class="table-empty-btn-primary" id="btn-empty-mock-${e}">
802
- <span class="material-symbols-outlined icon-16">auto_fix_high</span>
803
- <span>Generate Mock Data</span>
804
- </button>
805
- </div>
806
- </div>
807
- </div>
808
- `;let n=document.getElementById(`btn-empty-mock-${e}`);n&&(n.onclick=()=>{E(e,a,()=>{window.forceEmptyGrid=!1,U(e,t,``,!0)})});let r=document.getElementById(`btn-empty-add-row-${e}`);r&&(r.onclick=()=>{window.forceEmptyGrid=!0,U(e,t,``,!0)})}else{let n=`<table class="data-table" id="data-grid-table-${e}"><thead><tr>`;n+=`<th class="row-header table-corner-header" id="th-row-header-${e}" title="Click or right-click for table actions (Mock Data, Export, Truncate)">#</th>`,c.forEach(e=>{let t=a.find(t=>t.name===e),r=``,i=t?.type||``;if(t){let e=!!t.isPk,n=!!t.fkTarget;e&&n?r=`<span class="col-badge badge-pfk" title="Primary Foreign Key (referencing ${t.fkTarget.table}.${t.fkTarget.column})">PFK</span>`:e?r=`<span class="col-badge badge-pk" title="Primary Key">PK</span>`:(n||t.name.toLowerCase().includes(`id`))&&(r=`<span class="col-badge badge-fk" title="Foreign Key">FK</span>`)}let o=``,s=window.DataGrid.sortState.col===e;s&&(o=`<span class="material-symbols-outlined th-sort-icon">${window.DataGrid.sortState.asc?`arrow_upward`:`arrow_downward`}</span>`),n+=`<th class="sortable ${s?`sorted`:``}" data-col="${e}">
809
- <div class="th-content-wrapper">
810
- <div class="th-col-info">
811
- <div class="th-name-row">
812
- <span class="th-col-name">${e}</span>
813
- ${r}
814
- </div>
815
- ${i?`<span class="th-col-type">${i}</span>`:``}
816
- </div>
817
- <div class="th-sort-indicator ${s?`active`:``}">
818
- ${o}
819
- </div>
820
- </div>
821
- </th>`}),n+=`</tr></thead><tbody>`,i&&i.length>0&&i.forEach((e,t)=>{n+=`<tr>${te(e,t,l,c,a)}</tr>`}),n+=`<tr class="ghost-row-tr">`;let r=i?i.length:0;n+=`<td class="row-header" data-row-idx="${r}">*</td>`,c.forEach((e,t)=>{n+=`<td class="data-cell ghost-row" data-row-idx="${r}" data-col-idx="${t}" data-insert-index="0" data-col="${e}">${t===0?`<span class="ghost-cell-hint">+ Add Row</span>`:``}</td>`}),n+=`</tr></tbody></table>`,f.innerHTML=n,f.querySelectorAll(`th.sortable`).forEach(n=>{n.onclick=r=>{if(window.DataGrid&&window.DataGrid.isResizing)return;if(Object.keys(window.DataGrid.pendingEdits).length>0||window.DataGrid.pendingInserts.length>1||window.DataGrid.pendingDeletes.size>0){alert(`You have unsaved changes. Please save changes before sorting.`);return}let i=n.dataset.col,a=e,o=window.TableStates?.[a]?.dataGrid||window.DataGrid;o&&(o.sortState.col===i?o.sortState.asc=!o.sortState.asc:(o.sortState.col=i,o.sortState.asc=!0),o.pagination.offset=0),U(a,t,z(),!0)},R(n,window.DataGrid)}),L(f,`data-grid-table-${e}`,window.DataGrid,c.length),se(f,a,c),fe(f,e);let o=document.getElementById(`th-row-header-${e}`);if(o){let n=n=>{n.preventDefault&&n.preventDefault(),n.stopPropagation&&n.stopPropagation();let r=z(),i=window.DataGrid?.sortState,o=!!r,s=t=>{let n=`/api/tables/${encodeURIComponent(e)}/export?format=${t}`;return r&&(n+=`&where=${encodeURIComponent(r)}`),i&&i.col&&(n+=`&orderCol=${encodeURIComponent(i.col)}&orderAsc=${i.asc}`),n};w(n,[{icon:`auto_fix_high`,label:`Generate Mock Data...`,action:()=>{E(e,a,()=>{U(e,t,z(),!0)})}},{icon:`content_copy`,label:`Copy Table Name`,action:async()=>{try{await navigator.clipboard.writeText(e)}catch{let t=document.createElement(`textarea`);t.value=e,document.body.appendChild(t),t.select(),document.execCommand(`copy`),t.remove()}window.showToast&&window.showToast(`Copied "${e}"`,`success`)}},{type:`divider`},{icon:`download`,label:o?`Export as CSV (Filtered)`:`Export as CSV`,action:()=>{window.open(s(`csv`),`_blank`)}},{icon:`download`,label:o?`Export as JSON (Filtered)`:`Export as JSON`,action:()=>{window.open(s(`json`),`_blank`)}},{type:`divider`},{icon:`mop`,label:`Truncate Table (Clear Rows)`,danger:!0,action:async()=>{if(confirm(`Are you sure you want to delete ALL rows from table "${e}"? This cannot be undone!`)){let n=await p(e);n.success?(window.showToast&&window.showToast(`Cleared all rows from "${e}"`,`info`),U(e,t,``,!0)):alert(`Failed to clear table: ${n.error}`)}}}])};o.addEventListener(`click`,n),o.addEventListener(`contextmenu`,n)}}let m=document.getElementById(`btn-fk-back-${e}`);if(m&&(m.onclick=()=>{let e=window.fkNavHistory?.fromTable;if(window.fkNavHistory=null,e){let t=document.querySelector(`.table-btn[data-table="${e}"]`)||Array.from(document.querySelectorAll(`.table-btn`)).find(t=>t.textContent.includes(e));window.AppState.currentTable=e,window.AppState.currentTableBtnElement=t||null,document.querySelectorAll(`.table-btn`).forEach(e=>e.classList.remove(`active`)),t&&t.classList.add(`active`);let n=`view-data-btn-${e}`,r=document.getElementById(n);r&&(r.innerHTML=``),window.renderCurrentView()}}),!r){let n=document.getElementById(`filter-val-${e}`),r=(n=!0)=>{n&&window.DataGrid&&(window.DataGrid.pagination.offset=0),U(e,t,z(),!0)},i=document.getElementById(`btn-clear-filter-${e}`),o=()=>{i&&(n?.value.trim()?i.classList.remove(`hidden`):i.classList.add(`hidden`))};i&&n&&i.addEventListener(`click`,()=>{n.value=``,i.classList.add(`hidden`),r(!0),n.focus()});let u;n&&n.addEventListener(`input`,()=>{o(),clearTimeout(u),u=setTimeout(()=>r(!0),400)});let d=document.getElementById(`filter-col-${e}`),f=document.getElementById(`filter-op-${e}`);d&&d.addEventListener(`change`,()=>r(!0)),f&&f.addEventListener(`change`,()=>r(!0));let p=()=>{(Object.keys(window.DataGrid.pendingEdits).length>0||window.DataGrid.pendingInserts.length>1||window.DataGrid.pendingDeletes.size>0)&&!confirm(`You have unsaved changes. Are you sure you want to refresh and discard them?`)||(window.DataGrid.pendingEdits={},window.DataGrid.pendingInserts=[{}],window.DataGrid.pendingDeletes=new Set,window.DataGrid.history=[],window.DataGrid.currentTransaction=null,window.updateSidebarDirtyState?.(),r(!1))},m=document.getElementById(`btn-refresh-data-${e}`);m&&(m.onclick=p),window.DataGrid.refreshData=p;let h=async()=>{if(window.DataGrid.pagination.isLoading||!window.DataGrid.pagination.hasMore)return;window.DataGrid.pagination.isLoading=!0,window.DataGrid.pagination.offset+=window.DataGrid.pagination.limit;let t={where:z(),limit:window.DataGrid.pagination.limit,offset:window.DataGrid.pagination.offset,orderCol:window.DataGrid.sortState.col,orderAsc:window.DataGrid.sortState.asc};try{let n=await s(e,t);if(window.DataGrid.pagination.isLoading=!1,n.success&&n.data){let t=n.data.rows;window.DataGrid.pagination.hasMore=t.length===window.DataGrid.pagination.limit;let r=document.querySelector(`#data-grid-table-${e} tbody`);if(!r)return;let i=r.querySelector(`td[data-insert-index]`)?.closest(`tr`),o=window.DataGrid.pagination.offset;t.forEach((e,t)=>{let n=document.createElement(`tr`);n.innerHTML=te(e,o+t,l,c,a),i?r.insertBefore(n,i):r.appendChild(n)});let s=o+t.length,u=r.querySelectorAll(`td[data-insert-index]`),d=new Set;u.forEach(e=>{let t=e.closest(`tr`);if(d.has(t))return;d.add(t);let n=t.querySelector(`.row-header`);n&&(n.dataset.rowIdx=s),t.querySelectorAll(`td.data-cell`).forEach(e=>e.dataset.rowIdx=s),s++})}}catch{window.DataGrid.pagination.isLoading=!1}};setTimeout(()=>{let t=document.getElementById(`data-grid-container-${e}`);t&&t.addEventListener(`scroll`,e=>{let{scrollTop:t,scrollHeight:n,clientHeight:r}=e.target;t+r>=n-50&&h()})},50)}}else o.innerHTML=`<div class="p-6 text-error">Error: ${u.error}</div>`}catch(e){o.innerHTML=`<div class="p-6 text-error">Failed to load data: ${e.message}</div>`}}async function me(){if(!window.SchemaGrid)return;let{pendingEdits:e,pendingInserts:t,pendingDeletes:n,pendingIndexEdits:r,schema:i}=window.SchemaGrid,a=window.AppState.currentTable;if(!a)return;let o=n&&n.size>0||Object.keys(e||{}).length>0||t&&t.some(e=>e.name&&e.name.trim()!==``),s=r&&(r.added.length>0||r.dropped.length>0);if(!o&&!s)return;let c=n?Array.from(n):[];try{let n=await f(a,{pendingEdits:e,pendingInserts:t,pendingDeletes:c,pendingIndexEdits:r,columns:i});if(n.success){window.SchemaGrid&&(window.SchemaGrid.pendingEdits={},window.SchemaGrid.pendingInserts=[{}],window.SchemaGrid.pendingDeletes=new Set,window.SchemaGrid.pendingIndexEdits={added:[],dropped:[]},window.SchemaGrid.history=[],window.SchemaGrid.currentTransaction=null),window.TableStates?.[a]&&(window.TableStates[a].dataGrid=null);let e=document.getElementById(`btn-refresh-schema-${a}`);e?e.click():window.renderCurrentView(),window.updateSidebarDirtyState?.(),window.loadTableStats?.(),window.showToast&&window.showToast(`Schema saved successfully!`,`success`)}else{let e=n.error||`Failed to apply schema changes`;window.showToast?window.showToast(`Migration failed: ${e}`,`error`):alert(`Migration failed: ${e}`)}}catch(e){let t=e.message||`Network error`;window.showToast?window.showToast(`Migration failed: ${t}`,`error`):alert(`Migration failed: ${t}`)}}function W(e,t,n,r=!0){if(r&&window.SchemaGrid.currentTransaction){let n=e.textContent;(n===`null`||e.classList.contains(`ghost-row`))&&(n=``),n!==t&&window.SchemaGrid.currentTransaction.push({td:e,oldVal:n,newVal:t})}let i=e.dataset.colKey,a=e.dataset.colIdx===`0`?`<span class="ghost-cell-hint">+ Add column</span>`:``,o=t;if(i===`isPk`&&t){let e=t.includes(`PK`)||t.includes(`PFK`),n=t.match(/(?:FK|PFK)(?:\s*\(|:\s*|\s*→\s*|\s*->\s*)([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)(?:\s*\((CASCADE|SET NULL|RESTRICT|NO ACTION)\))?/i),r=n&&n[3]&&n[3].toUpperCase()!==`NO ACTION`?` (${n[3].toUpperCase()})`:``;e&&n?o=`<span class="col-badge badge-pfk" title="Primary Foreign Key (Composite Key referencing ${n[1]}.${n[2]}${r?` [ON DELETE ${n[3]}]`:``})">PFK &rarr; ${n[1]}.${n[2]}${r}</span>`:e?o=`<span class="col-badge badge-pk">PK</span>`:n&&(o=`<span class="col-badge badge-fk" title="References ${n[1]}.${n[2]}${r?` [ON DELETE ${n[3]}]`:``}">FK &rarr; ${n[1]}.${n[2]}${r}</span>`)}if(e.innerHTML=o||(e.dataset.insertIndex===void 0?`<span class="text-soft">-</span>`:a),e.dataset.insertIndex!==void 0){let r=parseInt(e.dataset.insertIndex);if(window.SchemaGrid.pendingInserts[r]||(window.SchemaGrid.pendingInserts[r]={}),t){if(window.SchemaGrid.pendingInserts[r][i]=t,e.classList.add(`cell-edited`),e.classList.remove(`ghost-row`),r===window.SchemaGrid.pendingInserts.length-1){e.closest(`tr`).classList.remove(`ghost-row-tr`),window.SchemaGrid.pendingInserts.push({});let t=document.querySelector(`#schema-grid-table-${window.AppState.currentTable} tbody`),i=document.createElement(`tr`);i.className=`ghost-row-tr`;let a=parseInt(e.dataset.rowIdx)+1;i.innerHTML+=`<td class="row-header" data-row-idx="${a}">*</td>`,n.forEach((e,t)=>{if(e===`indexing`)return;let n=t===0?`<span class="ghost-cell-hint">+ Add column</span>`:``;i.innerHTML+=`<td class="data-cell ghost-row" data-row-idx="${a}" data-col-idx="${t}" data-insert-index="${r+1}" data-col-key="${e}">${n}</td>`}),t.appendChild(i)}}else window.SchemaGrid.pendingInserts[r]&&delete window.SchemaGrid.pendingInserts[r][i],e.classList.remove(`cell-edited`),e.classList.add(`ghost-row`)}else{let n=e.dataset.pk,r=e.dataset.original;r===`-`&&(r=``),t===r?(e.classList.remove(`cell-edited`),e.classList.remove(`cell-error`),window.SchemaGrid.pendingEdits[n]&&(delete window.SchemaGrid.pendingEdits[n][i],Object.keys(window.SchemaGrid.pendingEdits[n]).length===0&&delete window.SchemaGrid.pendingEdits[n])):(window.SchemaGrid.pendingEdits[n]||(window.SchemaGrid.pendingEdits[n]={}),window.SchemaGrid.pendingEdits[n][i]=t,e.classList.add(`cell-edited`),e.classList.remove(`cell-error`))}window.updateSidebarDirtyState?.()}function he(e,t=!0){let n=document.querySelector(`#schema-grid-table-${window.AppState.currentTable} td.data-cell[data-row-idx="${e}"]`)?.closest(`tr`);if(!n||n.classList.contains(`ghost-row-tr`)||n.classList.contains(`row-deleted`))return;let r=n.querySelector(`td.data-cell`)?.dataset.pk;r!==void 0&&(t&&window.SchemaGrid.currentTransaction&&window.SchemaGrid.currentTransaction.push({type:`delete`,rowIdx:e,pk:r}),window.SchemaGrid.pendingDeletes.add(r),n.classList.add(`row-deleted`)),window.updateSidebarDirtyState?.()}function ge(e,t){let n=document.querySelector(`#schema-grid-table-${window.AppState.currentTable} td.data-cell[data-row-idx="${e}"]`)?.closest(`tr`);n&&n.classList.remove(`row-deleted`),window.SchemaGrid.pendingDeletes.delete(t),window.updateSidebarDirtyState?.()}function _e(e,t){e.forEach(e=>{let n=window.AppState.currentTable,r=document.querySelector(`#schema-grid-table-${n} td.data-cell[data-row-idx="${e}"]`)?.closest(`tr`);if(!r||r.classList.contains(`ghost-row-tr`))return;let i=document.querySelector(`#schema-grid-table-${n} .ghost-row-tr`);i&&t.forEach((e,n)=>{let a=r.querySelector(`td.data-cell[data-col-idx="${n}"]`),o=i.querySelector(`td.data-cell[data-col-idx="${n}"]`);if(a&&o){let n=a.dataset.insertIndex===void 0?a.classList.contains(`cell-edited`)?window.SchemaGrid.pendingEdits[a.dataset.pk]?.[e]:a.dataset.original:window.SchemaGrid.pendingInserts[a.dataset.insertIndex][e];n!=null&&n!==`null`&&(e===`name`&&(n+=`_copy`),W(o,n,t,!0))}})})}function ve(){let e=window.SchemaGrid;if(!e)return;let t=document.getElementById(`index-modal`);t&&t.remove();let n=[...e.indexes],r=[...e.pendingIndexEdits.added],i=new Set(e.pendingIndexEdits.dropped),a=n.filter(e=>!i.has(e.name)).concat(r),o=e.schema.map(e=>e.name);e.pendingInserts.forEach(e=>{e.name&&e.name.trim()!==``&&o.push(e.name.trim())});let s=[];t=document.createElement(`div`),t.id=`index-modal`,t.className=`modal-overlay`,t.innerHTML=`
822
- <div class="modal-container idx-modal-container">
823
- <div class="modal-header">
824
- <div class="flex items-center gap-2">
825
- <span class="material-symbols-outlined text-primary" style="font-size:20px;">key</span>
826
- <h3 class="m-0 text-15 font-semibold">Manage Indexes</h3>
827
- </div>
828
- <button id="close-idx-modal" class="modal-close-btn" title="Close"><span class="material-symbols-outlined">close</span></button>
829
- </div>
830
-
831
- <div class="modal-body-scroll" style="max-height: 220px; padding: 16px;">
832
- <div class="idx-section-title">Current Indexes</div>
833
- <div id="idx-list-container"></div>
834
- </div>
835
-
836
- <div class="modal-section-bg" style="padding: 16px 20px;">
837
- <div class="idx-section-title" style="margin-bottom: 24px;">+ Create New Index</div>
838
- <div class="flex flex-col gap-3">
839
- <div>
840
- <label class="text-11 text-secondary block mb-1.5">Index Name</label>
841
- <input type="text" id="new-idx-name" placeholder="Optional (auto-generated if empty)" class="modal-input w-full" style="padding: 8px 12px; font-size: 13px;" />
842
- </div>
843
-
844
- <div>
845
- <div class="flex items-center justify-between mb-1">
846
- <label class="text-11 text-secondary">Columns (click to select in order)</label>
847
- <button type="button" id="btn-clear-selected-cols" class="idx-clear-btn hidden" title="Clear column selection">
848
- <span class="material-symbols-outlined" style="font-size:12px; line-height:1;">close</span>
849
- <span>Clear selection</span>
850
- </button>
851
- </div>
852
- <div id="idx-chips-container" class="idx-chips-container"></div>
853
- </div>
854
-
855
- <div class="flex items-center justify-between pt-1">
856
- <label class="flex items-center gap-2 cursor-pointer select-none text-13">
857
- <input type="checkbox" id="new-idx-unique" style="accent-color: var(--color-primary);" />
858
- <span>Unique Index</span>
859
- </label>
860
- <button type="button" id="add-idx-btn" class="btn-add-idx">
861
- <span class="material-symbols-outlined">add</span>
862
- <span>Add to List</span>
863
- </button>
864
- </div>
865
- </div>
866
- </div>
867
-
868
- <div class="modal-footer">
869
- <button id="cancel-idx-btn" class="btn-secondary">Cancel</button>
870
- <button id="save-idx-btn" class="btn-primary">Save Changes</button>
871
- </div>
872
- </div>
873
- `,document.body.appendChild(t);let c=()=>t.remove();document.getElementById(`close-idx-modal`).onclick=c,document.getElementById(`cancel-idx-btn`).onclick=c;let l=()=>{let e=document.getElementById(`idx-chips-container`),t=document.getElementById(`btn-clear-selected-cols`);e&&(t&&(s.length>0?(t.classList.remove(`hidden`),t.onclick=()=>{s=[],l()}):t.classList.add(`hidden`)),e.innerHTML=o.map(e=>{let t=s.indexOf(e),n=t!==-1,r=n?`<span class="idx-chip-badge">${t+1}</span>`:``;return`
874
- <div class="idx-chip ${n?`selected`:``}" data-col="${e}">
875
- ${r}
876
- <span>${e}</span>
877
- </div>
878
- `}).join(``),e.querySelectorAll(`.idx-chip`).forEach(e=>{e.onclick=()=>{let t=e.dataset.col,n=s.indexOf(t);n===-1?s.push(t):s.splice(n,1),l()}}))};l();let u=()=>{let e=document.getElementById(`idx-list-container`);if(e){if(a.length===0){e.innerHTML=`
879
- <div style="display:flex; flex-direction:column; align-items:center; justify-content:center; padding: 24px 0; color: var(--color-text-soft);">
880
- <span class="material-symbols-outlined" style="font-size:32px; opacity:0.4; margin-bottom:6px;">dataset</span>
881
- <div style="font-size:12px;">No indexes defined for this table yet.</div>
882
- </div>
883
- `;return}e.innerHTML=a.map((e,t)=>`
884
- <div class="idx-card">
885
- <div class="idx-card-info">
886
- <div class="idx-card-header">
887
- <span class="idx-name">${e.name||`unnamed_idx`}</span>
888
- ${e.isUnique?`<span class="badge-unique" style="font-size:10px; padding:1px 6px; line-height:14px;">UNIQUE</span>`:``}
889
- </div>
890
- <div class="idx-cols-tags">
891
- ${e.columns.map(e=>`<span class="idx-col-pill">${e}</span>`).join(``)}
892
- </div>
893
- </div>
894
- <button type="button" class="idx-del-btn delete-idx-btn" data-idx="${t}" title="Remove index">
895
- <span class="material-symbols-outlined">delete</span>
896
- </button>
897
- </div>
898
- `).join(``),e.querySelectorAll(`.delete-idx-btn`).forEach(e=>{e.onclick=e=>{let t=parseInt(e.currentTarget.dataset.idx,10);a.splice(t,1),u()}})}};u(),document.getElementById(`add-idx-btn`).onclick=()=>{if(s.length===0){window.showToast?window.showToast(`Please select at least one column for the index.`,`warning`):alert(`Please select at least one column for the index.`);return}let e=window.AppState?.currentTable||`table`,t=document.getElementById(`new-idx-name`).value.trim()||`idx_${e}_${s.join(`_`)}`,n=document.getElementById(`new-idx-unique`).checked;a.push({name:t,columns:[...s],isUnique:n}),document.getElementById(`new-idx-name`).value=``,document.getElementById(`new-idx-unique`).checked=!1,s=[],l(),u()},document.getElementById(`save-idx-btn`).onclick=()=>{let t=[],r=new Set;n.forEach(e=>{a.find(t=>t.name===e.name&&JSON.stringify(t.columns)===JSON.stringify(e.columns))||r.add(e.name)}),a.forEach(e=>{n.find(t=>t.name===e.name&&JSON.stringify(t.columns)===JSON.stringify(e.columns))||t.push(e)}),e.pendingIndexEdits.added=t,e.pendingIndexEdits.dropped=Array.from(r),window.updateSidebarDirtyState?.(),window.renderSchemaGrid(),c()}}async function ye(e,t){if(!window.SchemaGrid)return;let n=document.getElementById(`pkfk-modal`);n&&n.remove();let r=e.dataset.insertIndex!==void 0,i=r?parseInt(e.dataset.insertIndex,10):null,a=e.dataset.pk||e.parentElement?.querySelector(`[data-col-key="name"]`)?.textContent?.trim()||``,o=``;o=r&&i!==null&&window.SchemaGrid?.pendingInserts?.[i]?window.SchemaGrid.pendingInserts[i].isPk||``:a&&window.SchemaGrid?.pendingEdits?.[a]?.isPk!==void 0?window.SchemaGrid.pendingEdits[a].isPk||``:t||e.textContent?.trim()||e.dataset.original||``;let s=o.includes(`PK`)||o.includes(`PFK`),c=!1,l=``,u=``,d=`NO ACTION`,f=`NO ACTION`,p=(window.SchemaGrid?.schema||[]).find(e=>e.name===a);p?.fkTarget&&(l=p.fkTarget.table||``,u=p.fkTarget.column||``,p.fkTarget.onDelete&&(d=p.fkTarget.onDelete),p.fkTarget.onUpdate&&(f=p.fkTarget.onUpdate),c=!0);let m=r&&i!==null?window.SchemaGrid?.pendingInserts?.[i]?.fkTarget:a?window.SchemaGrid?.pendingEdits?.[a]?.fkTarget:void 0;m&&(m.table&&(l=m.table),m.column&&(u=m.column),m.onDelete&&(d=m.onDelete),m.onUpdate&&(f=m.onUpdate),c=!0);let h=o.match(/(?:FK|PFK)(?:\s*\(|:\s*|\s*→\s*|\s*->\s*)([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)(?:\s*\((CASCADE|SET NULL|RESTRICT|NO ACTION)\))?/i);h?(l=h[1],u=h[2],h[3]&&(d=h[3].toUpperCase()),c=!0):(o.includes(`FK`)||o.includes(`PFK`))&&(c=!0),n=document.createElement(`div`),n.id=`pkfk-modal`,n.className=`modal-overlay`,n.innerHTML=`
899
- <div class="keys-modal-container">
900
- <div class="modal-header">
901
- <div class="flex items-center gap-2">
902
- <span class="material-symbols-outlined text-primary" style="font-size:20px;">vpn_key</span>
903
- <h3 class="m-0 text-15 font-semibold">Manage Keys</h3>
904
- ${a?`<span class="keys-col-badge">${a}</span>`:``}
905
- </div>
906
- <button id="close-pkfk-modal" class="modal-close-btn" title="Close"><span class="material-symbols-outlined">close</span></button>
907
- </div>
908
-
909
- <div class="key-cards-list">
910
- <!-- Primary Key Card -->
911
- <div class="key-card ${s?`active`:``}" id="pk-card">
912
- <div class="key-card-header" id="pk-card-toggle">
913
- <div class="key-card-icon pk">
914
- <span class="material-symbols-outlined">key</span>
915
- </div>
916
- <div class="key-card-content">
917
- <div class="key-card-title">Primary Key (PK)</div>
918
- <div class="key-card-desc">Uniquely identifies each record in this table</div>
919
- </div>
920
- <label class="key-switch" id="pk-switch-label">
921
- <input type="checkbox" id="modal-is-pk" ${s?`checked`:``} />
922
- <span class="key-slider"></span>
923
- </label>
924
- </div>
925
- </div>
926
-
927
- <!-- Foreign Key Card -->
928
- <div class="key-card ${c?`active`:``}" id="fk-card">
929
- <div class="key-card-header" id="fk-card-toggle">
930
- <div class="key-card-icon fk">
931
- <span class="material-symbols-outlined">link</span>
932
- </div>
933
- <div class="key-card-content">
934
- <div class="key-card-title">Foreign Key (FK)</div>
935
- <div class="key-card-desc">Reference a column in another table</div>
936
- </div>
937
- <label class="key-switch" id="fk-switch-label">
938
- <input type="checkbox" id="modal-is-fk" ${c?`checked`:``} />
939
- <span class="key-slider"></span>
940
- </label>
941
- </div>
942
-
943
- <!-- FK Configuration Drawer -->
944
- <div id="fk-settings-container" class="key-fk-drawer ${c?`open`:``}">
945
- <div class="key-field-group">
946
- <label class="key-field-label">Target Table</label>
947
- <div class="key-select-wrapper">
948
- <select id="modal-fk-table" class="key-select">
949
- <option value="">Loading tables...</option>
950
- </select>
951
- <span class="material-symbols-outlined key-select-arrow">expand_more</span>
952
- </div>
953
- </div>
954
-
955
- <div class="key-field-group">
956
- <label class="key-field-label">Target Column</label>
957
- <div class="key-select-wrapper">
958
- <select id="modal-fk-col" class="key-select" ${l?``:`disabled`}>
959
- <option value="">Select a table first</option>
960
- </select>
961
- <span class="material-symbols-outlined key-select-arrow">expand_more</span>
962
- </div>
963
- </div>
964
-
965
- <div class="key-field-group" style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
966
- <div>
967
- <label class="key-field-label">On Delete</label>
968
- <div class="key-select-wrapper">
969
- <select id="modal-fk-ondelete" class="key-select">
970
- <option value="NO ACTION" ${d===`NO ACTION`?`selected`:``}>NO ACTION</option>
971
- <option value="CASCADE" ${d===`CASCADE`?`selected`:``}>CASCADE</option>
972
- <option value="RESTRICT" ${d===`RESTRICT`?`selected`:``}>RESTRICT</option>
973
- <option value="SET NULL" ${d===`SET NULL`?`selected`:``}>SET NULL</option>
974
- </select>
975
- <span class="material-symbols-outlined key-select-arrow">expand_more</span>
976
- </div>
977
- </div>
978
- <div>
979
- <label class="key-field-label">On Update</label>
980
- <div class="key-select-wrapper">
981
- <select id="modal-fk-onupdate" class="key-select">
982
- <option value="NO ACTION" ${f===`NO ACTION`?`selected`:``}>NO ACTION</option>
983
- <option value="CASCADE" ${f===`CASCADE`?`selected`:``}>CASCADE</option>
984
- <option value="RESTRICT" ${f===`RESTRICT`?`selected`:``}>RESTRICT</option>
985
- <option value="SET NULL" ${f===`SET NULL`?`selected`:``}>SET NULL</option>
986
- </select>
987
- <span class="material-symbols-outlined key-select-arrow">expand_more</span>
988
- </div>
989
- </div>
990
- </div>
991
- </div>
992
- </div>
993
- </div>
994
-
995
- <div class="modal-footer">
996
- <button id="cancel-pkfk-btn" class="btn-secondary">Cancel</button>
997
- <button id="save-pkfk-btn" class="btn-primary flex items-center gap-1.5">
998
- <span class="material-symbols-outlined" style="font-size:16px;">check</span>
999
- <span>Save Changes</span>
1000
- </button>
1001
- </div>
1002
- </div>
1003
- `,document.body.appendChild(n);let g=e=>{e.key===`Escape`&&_()};document.addEventListener(`keydown`,g);let _=()=>{document.removeEventListener(`keydown`,g),n.remove()};document.getElementById(`close-pkfk-modal`).onclick=_,document.getElementById(`cancel-pkfk-btn`).onclick=_,n.addEventListener(`click`,e=>{e.target===n&&_()});let v=document.getElementById(`pk-card`),y=document.getElementById(`modal-is-pk`),b=document.getElementById(`fk-card`),x=document.getElementById(`modal-is-fk`),S=document.getElementById(`fk-settings-container`),C=document.getElementById(`modal-fk-table`),w=document.getElementById(`modal-fk-col`);document.getElementById(`pk-card-toggle`).addEventListener(`click`,e=>{e.target.closest(`.key-switch`)||(y.checked=!y.checked,y.dispatchEvent(new Event(`change`)))}),y.addEventListener(`change`,()=>{v.classList.toggle(`active`,y.checked)}),document.getElementById(`fk-card-toggle`).addEventListener(`click`,e=>{e.target.closest(`.key-switch`)||(x.checked=!x.checked,x.dispatchEvent(new Event(`change`)))});let T=[];x.addEventListener(`change`,()=>{let e=x.checked;if(e&&T.length===0){x.checked=!1,b.classList.remove(`active`),S.classList.remove(`open`),window.showToast?window.showToast(`No other tables available in the database to link as a foreign key.`,`warning`):alert(`No other tables available in the database to link as a foreign key.`);return}if(b.classList.toggle(`active`,e),e){if(S.classList.add(`open`),!C.value&&T.length>0){let e=T[0];C.value=e,E(e)}}else S.classList.remove(`open`)});try{let e=await(await fetch(`/api/tables`)).json();if(e.success&&e.data){let t=window.AppState?.currentTable;T=e.data.filter(e=>e!==t),C.innerHTML=(T.length===0?`<option value="">No other tables available</option>`:`<option value="">-- Select Table --</option>`)+T.map(e=>`<option value="${e}" ${e===l?`selected`:``}>${e}</option>`).join(``),l&&T.includes(l)&&E(l,u)}}catch{C.innerHTML=`<option value="">Error loading tables</option>`}async function E(e,t=``){w.disabled=!0,w.innerHTML=`<option value="">Loading columns...</option>`;try{let n=await(await fetch(`/api/tables/${e}/schema`)).json();if(n.success&&n.data){let r=e===window.AppState?.currentTable?n.data.filter(e=>e.name!==a):n.data,i=t;if(!i||!r.some(e=>e.name===i)){let e=r.find(e=>e.isPk||e.name.toLowerCase()===`id`);i=e?e.name:r[0]?.name||``}w.innerHTML=`<option value="">-- Select Column --</option>`+r.map(e=>`<option value="${e.name}" ${e.name===i?`selected`:``}>${e.name}</option>`).join(``),w.disabled=!1}}catch{w.innerHTML=`<option value="">Error loading columns</option>`}}C.addEventListener(`change`,e=>{let t=e.target.value;t?E(t):(w.innerHTML=`<option value="">Select a table first</option>`,w.disabled=!0)}),document.getElementById(`save-pkfk-btn`).onclick=()=>{let t=y.checked,n=x.checked,o=C.value,p=w.value,m=document.getElementById(`modal-fk-ondelete`),h=document.getElementById(`modal-fk-onupdate`),g=m?.value||`NO ACTION`,v=h?.value||`NO ACTION`;if(t===s&&n===c&&(!n||o===l&&p===u&&g===d&&v===f)){_();return}let b=g&&g!==`NO ACTION`?` (${g})`:``,S=``;if(t&&n){if(!o||!p){window.showToast?window.showToast(`Please select both Target Table and Target Column for the Foreign Key.`,`warning`):alert(`Please select both Target Table and Target Column for the Foreign Key.`);return}S=`PFK: ${o}.${p}${b}`}else if(t)S=`PK`;else if(n){if(!o||!p){window.showToast?window.showToast(`Please select both Target Table and Target Column for the Foreign Key.`,`warning`):alert(`Please select both Target Table and Target Column for the Foreign Key.`);return}S=`FK: ${o}.${p}${b}`}let T=n&&o&&p?{table:o,column:p,onDelete:g===`NO ACTION`?void 0:g,onUpdate:v===`NO ACTION`?void 0:v}:null;r&&i!==null?(window.SchemaGrid.pendingInserts[i]||(window.SchemaGrid.pendingInserts[i]={}),window.SchemaGrid.pendingInserts[i].fkTarget=T):a&&(window.SchemaGrid.pendingEdits[a]||(window.SchemaGrid.pendingEdits[a]={}),window.SchemaGrid.pendingEdits[a].fkTarget=T),window.SchemaGrid.currentTransaction=[],W(e,S,[`name`,`type`,`isPk`,`nullable`,`defaultValue`,`indexing`]),window.SchemaGrid.currentTransaction.length>0&&window.SchemaGrid.history.push(window.SchemaGrid.currentTransaction),window.SchemaGrid.currentTransaction=null,_()}}function be(e,t){e.addEventListener(`click`,e=>{e.target.closest(`.manage-indexes-cell`)&&ve()}),e.addEventListener(`dblclick`,e=>{let n=e.target.closest(`td.data-cell`);if(!n||n.querySelector(`input, select`))return;let r=n.dataset.colKey,i=n.dataset.insertIndex!==void 0,a=i||n.textContent===`-`||n.textContent===`+ New`||n.textContent.includes(`Add column`)||n.textContent===`+ Add Row`||n.textContent===`null`?``:n.textContent.trim().startsWith(`Yes`)?`1`:n.textContent.trim()===`No`?``:n.textContent,o;if(r===`isPk`){ye(n,n.textContent);return}else if(r===`nullable`){let e=n.dataset.pk,t=window.SchemaGrid?.schema?.find(t=>t.name===e),r=window.SchemaGrid?.pendingEdits?.[e]?.isPk,s=r===void 0?t?t.isPk:!1:typeof r==`string`?r.includes(`PK`)||r.includes(`PFK`):!!r,c=i&&window.SchemaGrid?.pendingInserts?.[parseInt(n.dataset.insertIndex,10)]?.isPk,l=typeof c==`string`?c.includes(`PK`)||c.includes(`PFK`):!!c;if(s||l){window.showToast?window.showToast(`Primary Key cannot be nullable.`,`error`):alert(`Primary Key cannot be nullable.`);return}o=document.createElement(`select`),[`No`,`Yes`].forEach(e=>{let t=document.createElement(`option`);t.value=e,t.textContent=e,(e===`Yes`&&a===`1`||e===`No`&&a===``)&&(t.selected=!0),o.appendChild(t)})}else if(r===`isUnique`){let e=n.dataset.pk,t=window.SchemaGrid?.schema?.find(t=>t.name===e),r=window.SchemaGrid?.pendingEdits?.[e]?.isPk,s=r===void 0?t?t.isPk:!1:typeof r==`string`?r.includes(`PK`)||r.includes(`PFK`):!!r,c=i&&window.SchemaGrid?.pendingInserts?.[parseInt(n.dataset.insertIndex,10)]?.isPk,l=typeof c==`string`?c.includes(`PK`)||c.includes(`PFK`):!!c;if(s||l){window.showToast?window.showToast(`Primary Key is always unique and locked.`,`error`):alert(`Primary Key is always unique and locked.`);return}o=document.createElement(`select`),[`No`,`Yes`].forEach(e=>{let t=document.createElement(`option`);t.value=e,t.textContent=e,(e===`Yes`&&a===`1`||e===`No`&&a===``)&&(t.selected=!0),o.appendChild(t)})}else if(r===`type`){o=document.createElement(`select`);let e=[`INTEGER`,`TEXT`,`REAL`,`BLOB`,`NUMERIC`,`BOOLEAN`,`DATE`,`DATETIME`,`JSON`,`VARCHAR(255)`,`DECIMAL(10,2)`,`UUID`];a&&!e.some(e=>e.toUpperCase()===a.toUpperCase())&&e.unshift(a),e.forEach(e=>{let t=document.createElement(`option`);t.value=e,t.textContent=e,e.toUpperCase()===a.toUpperCase()&&(t.selected=!0),o.appendChild(t)})}else o=document.createElement(`input`),o.type=`text`,o.value=a;n.innerHTML=``,n.appendChild(o),o.style.width=`100%`,o.addEventListener(`keydown`,e=>{e.key===`Enter`&&o.blur(),e.key===`Tab`&&(e.preventDefault(),o.blur(),document.body.dispatchEvent(new KeyboardEvent(`keydown`,{key:`Tab`,shiftKey:e.shiftKey,bubbles:!0})))}),o.focus();let s=()=>{let e=o.value;window.SchemaGrid.currentTransaction=[],W(n,e,t),window.SchemaGrid.currentTransaction.length>0&&window.SchemaGrid.history.push(window.SchemaGrid.currentTransaction),window.SchemaGrid.currentTransaction=null};o.addEventListener(`blur`,()=>{s()})})}async function xe(e){let t=document.getElementById(`orm-export-modal`);t&&t.remove();let n=`prisma`,r=e||`*`,i=``;t=document.createElement(`div`),t.id=`orm-export-modal`,t.className=`modal-overlay`;let a=(window.AppState?.dbType||`database`).toUpperCase();t.innerHTML=`
1004
- <div class="orm-modal-container">
1005
- <div class="modal-header">
1006
- <div class="flex items-center gap-2">
1007
- <span class="material-symbols-outlined text-primary" style="font-size: 20px;">bolt</span>
1008
- <h3 class="m-0 text-15 font-semibold">Generate ORM Schema</h3>
1009
- <span class="badge-type" style="font-size: 10px; padding: 2px 7px; border-radius: 4px; background: rgba(59, 130, 246, 0.12); color: var(--color-primary); font-weight: 700;">${a}</span>
1010
- </div>
1011
- <button id="close-orm-modal-btn" class="modal-close-btn" title="Close"><span class="material-symbols-outlined">close</span></button>
1012
- </div>
1013
-
1014
- <div class="orm-controls-bar">
1015
- <div class="orm-tab-group">
1016
- <button type="button" id="tab-orm-prisma" class="orm-tab-btn active">
1017
- <span class="material-symbols-outlined" style="font-size: 15px;">diamond</span>
1018
- <span>Prisma ORM</span>
1019
- </button>
1020
- <button type="button" id="tab-orm-drizzle" class="orm-tab-btn">
1021
- <span class="material-symbols-outlined" style="font-size: 15px;">water_drop</span>
1022
- <span>Drizzle ORM</span>
1023
- </button>
1024
- </div>
1025
-
1026
- <div class="orm-scope-group">
1027
- <label class="orm-scope-label" for="orm-scope-select">Scope:</label>
1028
- <select id="orm-scope-select" class="orm-scope-select">
1029
- ${e?`<option value="${e}" selected>Table: ${e}</option>`:``}
1030
- <option value="*" ${e?``:`selected`}>All Tables (Database)</option>
1031
- </select>
1032
- </div>
1033
- </div>
1034
-
1035
- <div class="orm-code-wrapper">
1036
- <div class="orm-code-header">
1037
- <div class="orm-code-file-badge">
1038
- <span class="material-symbols-outlined" style="font-size: 14px;">description</span>
1039
- <span id="orm-file-name-label">schema.prisma</span>
1040
- </div>
1041
- <button type="button" id="orm-copy-code-btn" class="orm-code-copy-btn" title="Copy code to clipboard">
1042
- <span class="material-symbols-outlined" style="font-size: 14px;">content_copy</span>
1043
- <span id="orm-copy-btn-text">Copy Code</span>
1044
- </button>
1045
- </div>
1046
- <div class="orm-code-scroll">
1047
- <pre><code id="orm-code-display" class="orm-code-content">// Generating schema...</code></pre>
1048
- </div>
1049
- </div>
1050
-
1051
- <div class="modal-footer" style="justify-content: space-between; align-items: center;">
1052
- <div class="orm-modal-footer-hint">
1053
- <span class="material-symbols-outlined" style="font-size: 15px; color: #f59e0b;">lightbulb</span>
1054
- <span>Automatic project setup (<code>drixio add prisma/drizzle</code>) coming soon!</span>
1055
- </div>
1056
- <button type="button" id="close-orm-btn" class="btn-secondary">Close</button>
1057
- </div>
1058
- </div>
1059
- `,document.body.appendChild(t);let o=()=>{document.removeEventListener(`keydown`,s),t.remove()},s=e=>{e.key===`Escape`&&o()};document.addEventListener(`keydown`,s),t.addEventListener(`click`,e=>{e.target===t&&o()}),document.getElementById(`close-orm-modal-btn`).onclick=o,document.getElementById(`close-orm-btn`).onclick=o;let c=document.getElementById(`orm-code-display`),l=document.getElementById(`orm-file-name-label`),u=document.getElementById(`orm-copy-code-btn`),d=document.getElementById(`orm-copy-btn-text`),f=document.getElementById(`tab-orm-prisma`),p=document.getElementById(`tab-orm-drizzle`),m=document.getElementById(`orm-scope-select`),h=async()=>{c.textContent=`// Loading ${n===`prisma`?`Prisma`:`Drizzle`} schema...`,l.textContent=n===`prisma`?`schema.prisma`:`schema.ts`;try{let e=await(await fetch(`/api/generate-orm?target=${encodeURIComponent(n)}&table=${encodeURIComponent(r)}`)).json();e.success&&e.data?(i=e.data.code||``,c.textContent=i):c.textContent=`// Error generating schema:\n// ${e.error||`Unknown error`}`}catch(e){c.textContent=`// Failed to fetch ORM schema: ${e.message}`}};f.onclick=()=>{n!==`prisma`&&(n=`prisma`,f.classList.add(`active`),p.classList.remove(`active`),h())},p.onclick=()=>{n!==`drizzle`&&(n=`drizzle`,p.classList.add(`active`),f.classList.remove(`active`),h())},m.onchange=()=>{r=m.value,h()},u.onclick=()=>{i&&(navigator.clipboard.writeText(i),d.textContent=`Copied!`,window.showToast&&window.showToast(`${n===`prisma`?`Prisma`:`Drizzle`} schema copied to clipboard!`,`success`),setTimeout(()=>{d.textContent=`Copy Code`},1800))},h()}function Se(){let e=document.getElementById(`schema-diff-modal`);e&&e.remove();let t=(window.AppState?.dbType||`database`).toUpperCase(),n=window.AppState?.dbName||`Current DB`,i=document.createElement(`div`);i.id=`schema-diff-modal`,i.className=`modal-overlay`,i.innerHTML=`
1060
- <div class="diff-modal-container" id="diff-container-box">
1061
- <div class="modal-header">
1062
- <div class="flex items-center gap-2">
1063
- <span class="material-symbols-outlined text-primary" style="font-size: 20px;">compare_arrows</span>
1064
- <h3 class="m-0 text-15 font-semibold">Schema Diff & Migration</h3>
1065
- <span class="badge-type" style="font-size: 10px; padding: 2px 7px; border-radius: 4px; background: rgba(59, 130, 246, 0.12); color: var(--color-primary); font-weight: 700;">${t}: ${n}</span>
1066
- </div>
1067
- <button id="close-diff-modal-btn" class="modal-close-btn" title="Close"><span class="material-symbols-outlined">close</span></button>
1068
- </div>
1069
-
1070
- <div class="diff-controls-bar">
1071
- <div class="diff-target-tabs">
1072
- <button type="button" id="tab-diff-file" class="diff-tab-btn active">
1073
- <span class="material-symbols-outlined" style="font-size: 15px;">data_object</span>
1074
- <span>Snapshot File</span>
1075
- </button>
1076
- <button type="button" id="tab-diff-url" class="diff-tab-btn">
1077
- <span class="material-symbols-outlined" style="font-size: 15px;">cloud_sync</span>
1078
- <span>Database Connection</span>
1079
- </button>
1080
- </div>
1081
-
1082
- <button type="button" id="btn-save-current-snapshot" class="diff-action-btn" title="Export current DB schema as portable JSON snapshot">
1083
- <span class="material-symbols-outlined" style="font-size: 15px;">download</span>
1084
- <span>Save Current Snapshot</span>
1085
- </button>
1086
- </div>
1087
-
1088
- <!-- Target Input Area -->
1089
- <div class="diff-input-panel">
1090
- <!-- File Input Mode -->
1091
- <div id="diff-panel-file" class="diff-panel-section">
1092
- <div id="diff-dropzone" class="diff-dropzone">
1093
- <input type="file" id="diff-file-input" accept=".json" style="display: none;" />
1094
- <span class="material-symbols-outlined" style="font-size: 28px; color: var(--color-primary); margin-bottom: 6px;">upload_file</span>
1095
- <div id="diff-file-prompt" class="text-13" style="color: var(--color-text-secondary);">
1096
- <span class="font-semibold" style="color: var(--color-primary); cursor: pointer;">Click to choose</span> or drag & drop a <code>schema.json</code> snapshot
1097
- </div>
1098
- <div id="diff-file-info" class="text-12 hidden" style="color: #10b981; font-weight: 600; margin-top: 6px;"></div>
1099
- </div>
1100
- </div>
1101
-
1102
- <!-- URL Input Mode -->
1103
- <div id="diff-panel-url" class="diff-panel-section hidden">
1104
- <div class="flex items-center gap-2">
1105
- <input type="text" id="diff-target-url-input" class="diff-url-input" placeholder="e.g. postgres://user:pass@remote-host:5432/mydb or mysql://... or file:backup.db" />
1106
- <button type="button" id="btn-run-diff" class="diff-run-btn" disabled>
1107
- <span class="material-symbols-outlined" style="font-size: 16px;">play_arrow</span>
1108
- <span>Compare</span>
1109
- </button>
1110
- </div>
1111
- </div>
1112
- </div>
1113
-
1114
- <!-- Diff Results Container (Only displayed after comparison) -->
1115
- <div id="diff-results-wrap" class="diff-results-wrap hidden">
1116
- <!-- Stats Bar -->
1117
- <div id="diff-stats-bar" class="diff-stats-bar"></div>
1118
-
1119
- <!-- Split Grid: Left Visual Diff, Right Migration SQL -->
1120
- <div class="diff-body-split">
1121
- <!-- Left: Changes Tree -->
1122
- <div class="diff-tree-column">
1123
- <div class="diff-column-header">
1124
- <span class="material-symbols-outlined" style="font-size: 15px;">account_tree</span>
1125
- <span>Schema Changes</span>
1126
- </div>
1127
- <div id="diff-tree-content" class="diff-tree-content"></div>
1128
- </div>
1129
-
1130
- <!-- Right: Generated SQL -->
1131
- <div class="diff-sql-column">
1132
- <div class="diff-column-header flex items-center justify-between">
1133
- <div class="flex items-center gap-2">
1134
- <span class="material-symbols-outlined" style="font-size: 15px;">code</span>
1135
- <span id="diff-sql-title">Migration SQL (Up)</span>
1136
- </div>
1137
- <div class="flex items-center gap-2">
1138
- <button type="button" id="btn-toggle-sql-direction" class="diff-mini-btn" title="Toggle between Forward (Up) and Rollback (Down)">
1139
- <span class="material-symbols-outlined" style="font-size: 13px;">swap_vert</span>
1140
- <span id="sql-direction-label">Rollback (Down)</span>
1141
- </button>
1142
- <button type="button" id="btn-copy-diff-sql" class="diff-mini-btn" title="Copy SQL">
1143
- <span class="material-symbols-outlined" style="font-size: 13px;">content_copy</span>
1144
- <span>Copy</span>
1145
- </button>
1146
- <button type="button" id="btn-download-diff-sql" class="diff-mini-btn" title="Download .sql">
1147
- <span class="material-symbols-outlined" style="font-size: 13px;">download</span>
1148
- <span>.sql</span>
1149
- </button>
1150
- </div>
1151
- </div>
1152
- <pre id="diff-sql-preview" class="diff-sql-pre"><code>-- SQL will appear here...</code></pre>
1153
- </div>
1154
- </div>
1155
- </div>
1156
-
1157
- <!-- Standard Modal Footer -->
1158
- <div class="modal-footer" style="justify-content: space-between; align-items: center;">
1159
- <div class="orm-modal-footer-hint" id="diff-footer-hint">
1160
- <span class="material-symbols-outlined" style="font-size: 15px; color: var(--color-primary);">info</span>
1161
- <span>Select a target snapshot or connection string to compare.</span>
1162
- </div>
1163
- <div class="flex items-center gap-2">
1164
- <button type="button" id="close-diff-footer-btn" class="btn-secondary">Close</button>
1165
- <button type="button" id="btn-apply-diff-migration" class="btn-primary hidden" style="background: #10b981; border-color: #10b981; gap: 6px;">
1166
- <span class="material-symbols-outlined" style="font-size: 16px;">bolt</span>
1167
- <span>Apply Migration</span>
1168
- </button>
1169
- </div>
1170
- </div>
1171
- </div>
1172
- `,document.body.appendChild(i);let a=null,o=null,s=!1,c=document.getElementById(`diff-container-box`),l=document.getElementById(`close-diff-modal-btn`),u=document.getElementById(`close-diff-footer-btn`),d=document.getElementById(`tab-diff-file`),f=document.getElementById(`tab-diff-url`),p=document.getElementById(`diff-panel-file`),m=document.getElementById(`diff-panel-url`),v=document.getElementById(`diff-dropzone`),y=document.getElementById(`diff-file-input`),b=document.getElementById(`diff-file-info`),x=document.getElementById(`diff-target-url-input`),S=document.getElementById(`btn-run-diff`),C=document.getElementById(`btn-save-current-snapshot`),w=document.getElementById(`diff-results-wrap`),T=document.getElementById(`diff-stats-bar`),E=document.getElementById(`diff-tree-content`),D=document.getElementById(`diff-sql-preview`),O=document.getElementById(`btn-toggle-sql-direction`),k=document.getElementById(`btn-copy-diff-sql`),A=document.getElementById(`btn-download-diff-sql`),j=document.getElementById(`btn-apply-diff-migration`),M=document.getElementById(`diff-footer-hint`),N=()=>{document.removeEventListener(`keydown`,P),i.remove()},P=e=>{e.key===`Escape`&&N()};document.addEventListener(`keydown`,P),i.addEventListener(`click`,e=>{e.target===i&&N()}),l.onclick=N,u.onclick=N,C.onclick=()=>{_()},d.onclick=()=>{d.classList.add(`active`),f.classList.remove(`active`),p.classList.remove(`hidden`),m.classList.add(`hidden`)},f.onclick=()=>{f.classList.add(`active`),d.classList.remove(`active`),m.classList.remove(`hidden`),p.classList.add(`hidden`)},v.onclick=()=>y.click(),v.ondragover=e=>{e.preventDefault(),v.classList.add(`dragover`)},v.ondragleave=()=>v.classList.remove(`dragover`),v.ondrop=e=>{e.preventDefault(),v.classList.remove(`dragover`),e.dataTransfer.files&&e.dataTransfer.files.length>0&&F(e.dataTransfer.files[0])},y.onchange=e=>{e.target.files&&e.target.files.length>0&&F(e.target.files[0])};function F(e){if(!e.name.endsWith(`.json`)){alert(`Please select a valid .json schema snapshot file.`);return}let t=new FileReader;t.onload=async t=>{try{let n=JSON.parse(t.target.result);if(!n.tables||!Array.isArray(n.tables)){alert(`Invalid schema snapshot format. Missing tables array.`);return}a=n,b.textContent=`✔ Loaded "${e.name}" (${(e.size/1024).toFixed(1)} KB, ${n.tables.length} tables)`,b.classList.remove(`hidden`),await I({snapshot:a})}catch(e){alert(`Failed to parse snapshot JSON: ${e.message}`)}},t.readAsText(e)}x.oninput=()=>{S.disabled=!x.value.trim()},x.onkeydown=e=>{e.key===`Enter`&&x.value.trim()&&S.click()},S.onclick=async()=>{let e=x.value.trim();if(e){S.disabled=!0,S.innerHTML=`<span class="material-symbols-outlined spin" style="font-size: 16px;">sync</span><span>Comparing...</span>`;try{await I({targetUrl:e})}finally{S.disabled=!1,S.innerHTML=`<span class="material-symbols-outlined" style="font-size: 16px;">play_arrow</span><span>Compare</span>`}}};async function I(e){w.classList.remove(`hidden`),c.classList.add(`has-results`),T.innerHTML=`<div class="p-2 text-13" style="color: var(--color-text-secondary);">Comparing schemas...</div>`,E.innerHTML=`<div class="p-4 text-center text-13" style="color: var(--color-text-secondary);">Computing differences...</div>`,D.textContent=`-- Computing diff...`;try{let t=await h(e);if(!t.success){T.innerHTML=`<div class="diff-stat-error">✘ Diff Error: ${t.error}</div>`,E.innerHTML=``,D.textContent=`-- Diff failed:\n-- ${t.error}`;return}o=t.data,s=!1,L(o)}catch(e){T.innerHTML=`<div class="diff-stat-error">✘ Request failed: ${e.message}</div>`}}function L(e){let{diff:t,migrationSql:n,rollbackSql:r}=e,{stats:i}=t;if(!t.hasChanges){T.innerHTML=`
1173
- <div class="diff-stat-item clean">
1174
- <span class="material-symbols-outlined" style="color: #10b981;">check_circle</span>
1175
- <span>Schemas are identical! No migration required.</span>
1176
- </div>
1177
- `,E.innerHTML=`<div class="p-4 text-center text-13" style="color: #10b981;">Both schemas match completely.</div>`,D.textContent=`-- Schemas are identical.`,j.classList.add(`hidden`),M.innerHTML=`<span class="material-symbols-outlined" style="font-size: 15px; color: #10b981;">check_circle</span><span>No differences detected. Database is up to date.</span>`;return}j.classList.remove(`hidden`),M.innerHTML=`<span class="material-symbols-outlined" style="font-size: 15px; color: #f59e0b;">warning</span><span>Review changes carefully before applying to database.</span>`,T.innerHTML=`
1178
- <div class="diff-stat-group">
1179
- ${i.addedTablesCount>0?`<span class="diff-stat-badge added">+${i.addedTablesCount} Tables Added</span>`:``}
1180
- ${i.droppedTablesCount>0?`<span class="diff-stat-badge dropped">-${i.droppedTablesCount} Tables Dropped</span>`:``}
1181
- ${i.alteredTablesCount>0?`<span class="diff-stat-badge altered">~${i.alteredTablesCount} Tables Altered</span>`:``}
1182
- <span class="diff-stat-badge neutral">${i.addedColumnsCount+i.droppedColumnsCount+i.modifiedColumnsCount} Column Changes</span>
1183
- ${i.addedIndexesCount+i.droppedIndexesCount>0?`<span class="diff-stat-badge neutral">${i.addedIndexesCount+i.droppedIndexesCount} Index Changes</span>`:``}
1184
- </div>
1185
- `;let a=``;for(let e of t.tables)e.type===`added`?a+=`
1186
- <div class="diff-tree-table-item">
1187
- <div class="diff-table-header added">
1188
- <span class="material-symbols-outlined" style="font-size: 16px;">add_box</span>
1189
- <span class="font-semibold">${e.tableName}</span>
1190
- <span class="diff-pill added">NEW TABLE</span>
1191
- </div>
1192
- <div class="diff-table-details">
1193
- ${e.addedColumns.map(e=>`<div class="diff-item added">+ ${e.name} <span class="diff-type">${e.type}</span></div>`).join(``)}
1194
- </div>
1195
- </div>
1196
- `:e.type===`dropped`?a+=`
1197
- <div class="diff-tree-table-item">
1198
- <div class="diff-table-header dropped">
1199
- <span class="material-symbols-outlined" style="font-size: 16px;">indeterminate_check_box</span>
1200
- <span class="font-semibold">${e.tableName}</span>
1201
- <span class="diff-pill dropped">DROP TABLE</span>
1202
- </div>
1203
- </div>
1204
- `:e.type===`altered`&&(a+=`
1205
- <div class="diff-tree-table-item">
1206
- <div class="diff-table-header altered">
1207
- <span class="material-symbols-outlined" style="font-size: 16px;">change_circle</span>
1208
- <span class="font-semibold">${e.tableName}</span>
1209
- <span class="diff-pill altered">ALTER TABLE</span>
1210
- </div>
1211
- <div class="diff-table-details">
1212
- ${e.addedColumns.map(e=>`<div class="diff-item added">+ ADD COLUMN ${e.name} <span class="diff-type">${e.type}</span></div>`).join(``)}
1213
- ${e.droppedColumns.map(e=>`<div class="diff-item dropped">- DROP COLUMN ${e.name}</div>`).join(``)}
1214
- ${e.modifiedColumns.map(e=>`<div class="diff-item modified">~ MODIFY ${e.name}: <span class="diff-type">${e.oldType}</span> ➔ <span class="diff-type">${e.newType}</span></div>`).join(``)}
1215
- ${e.addedIndexes.map(e=>`<div class="diff-item added">+ INDEX ${e.name||`unnamed`} (${e.columns.join(`, `)})</div>`).join(``)}
1216
- ${e.droppedIndexes.map(e=>`<div class="diff-item dropped">- DROP INDEX ${e.name||`unnamed`}</div>`).join(``)}
1217
- </div>
1218
- </div>
1219
- `);E.innerHTML=a,R()}function R(){if(!o)return;let e=s?o.rollbackSql:o.migrationSql;D.textContent=e,document.getElementById(`diff-sql-title`).textContent=s?`Rollback SQL (Down)`:`Migration SQL (Up)`,document.getElementById(`sql-direction-label`).textContent=s?`Migration (Up)`:`Rollback (Down)`}O.onclick=()=>{s=!s,R()},k.onclick=async()=>{let e=D.textContent;try{await navigator.clipboard.writeText(e);let t=k.querySelector(`span:last-child`),n=t.textContent;t.textContent=`Copied!`,setTimeout(()=>{t.textContent=n},1800)}catch{alert(`Failed to copy to clipboard.`)}},A.onclick=()=>{let e=D.textContent,t=new Blob([e],{type:`application/sql`}),n=URL.createObjectURL(t),r=document.createElement(`a`);r.href=n,r.download=`migration_${new Date().toISOString().replace(/[:.]/g,`-`)}.sql`,document.body.appendChild(r),r.click(),document.body.removeChild(r),URL.revokeObjectURL(n)},j.onclick=async()=>{let e=D.textContent;if(!(!e||!o?.diff?.hasChanges)&&confirm(`Are you sure you want to execute this ${s?`ROLLBACK`:`MIGRATION`} script against the current database?\n\nThis will execute DDL statements immediately!`)){j.disabled=!0,j.innerHTML=`<span class="material-symbols-outlined spin" style="font-size: 16px;">sync</span><span>Applying...</span>`;try{let t=await g(e);t.success?(alert(`✔ Migration applied successfully to the database!`),await r(),i.remove(),window.renderSchemaGrid&&window.location.reload()):alert(`✘ Migration execution failed: ${t.error}`)}catch(e){alert(`✘ Migration failed: ${e.message}`)}finally{j.disabled=!1,j.innerHTML=`<span class="material-symbols-outlined" style="font-size: 16px;">bolt</span><span>Apply Migration</span>`}}}}async function Ce(e,t,n=null){document.querySelectorAll(`.table-btn`).forEach(e=>e.classList.remove(`active`)),t&&t.classList.add(`active`);let r=document.getElementById(`table-name`);r&&(r.textContent=e+` (Schema)`);let i=n||document.getElementById(`main-content`);i.innerHTML=`<div class='p-6'>Loading Schema...</div>`;try{let[n,r]=await Promise.all([c(e),l(e)]);if(n.success&&n.data){let a=n.data,o=r.success&&r.data?r.data:[];window.SchemaGrid={schema:a,indexes:o,pendingEdits:{},pendingIndexEdits:{added:[],dropped:[]},pendingInserts:[{}],pendingDeletes:new Set,selectedCell:null,history:[],currentTransaction:null,sortState:{colKey:null,asc:!0},filterText:``,isResizing:!1,selection:{startRow:-1,startCol:-1,endRow:-1,endCol:-1,isDragging:!1}},window.TableStates&&window.TableStates[e]&&(window.TableStates[e].schemaGrid=window.SchemaGrid),i.innerHTML=`
1220
- <div class="toolbar">
1221
- <div class="filter-group">
1222
- <div class="filter-icon-container">
1223
- <span class="material-symbols-outlined">search</span>
1224
- <span>Search</span>
1225
- </div>
1226
- <div class="filter-input-wrapper">
1227
- <input type="text" id="schema-search-val-${e}" class="filter-input" placeholder="Search name or type..." value="${window.SchemaGrid.filterText}" />
1228
- <button type="button" id="btn-clear-schema-search-${e}" class="filter-clear-btn ${window.SchemaGrid.filterText?``:`hidden`}" title="Clear search">
1229
- <span class="material-symbols-outlined">close</span>
1230
- </button>
1231
- </div>
1232
- </div>
1233
- <div class="flex-1"></div>
1234
- <button type="button" id="btn-export-orm-${e}" class="header-btn secondary" style="height: 30px; font-size: 12px; gap: 6px; padding: 0 12px;" title="Export Prisma / Drizzle ORM Schema">
1235
- <span class="material-symbols-outlined" style="font-size: 16px; color: var(--color-primary);">bolt</span>
1236
- <span>Export ORM</span>
1237
- </button>
1238
- <button type="button" id="btn-schema-diff-${e}" class="header-btn secondary" style="height: 30px; font-size: 12px; gap: 6px; padding: 0 12px;" title="Compare Schemas & Generate Migration SQL">
1239
- <span class="material-symbols-outlined" style="font-size: 16px; color: #10b981;">compare_arrows</span>
1240
- <span>Schema Diff</span>
1241
- </button>
1242
- <button id="btn-refresh-schema-${e}" class="refresh-btn" title="Refresh Schema (F5)"><span class="material-symbols-outlined">refresh</span></button>
1243
- </div>
1244
- <div id="schema-grid-container-${e}" class="table-container"></div>
1245
- `,window.renderSchemaGrid=()=>{let t=document.getElementById(`schema-grid-container-${e}`);if(!t)return;let n=[...window.SchemaGrid.schema];if(window.SchemaGrid.filterText){let e=window.SchemaGrid.filterText.toLowerCase();n=n.filter(t=>t.name&&t.name.toLowerCase().includes(e)||t.type&&t.type.toLowerCase().includes(e))}window.SchemaGrid.sortState.colKey&&n.sort((e,t)=>{let n=window.SchemaGrid.sortState.colKey,r=e[n],i=t[n];return r??=``,i??=``,r<i?window.SchemaGrid.sortState.asc?-1:1:r>i?window.SchemaGrid.sortState.asc?1:-1:0});let r=[`name`,`type`,`isPk`,`nullable`,`isUnique`,`defaultValue`,`indexing`],i=[`Name`,`Type`,`PK / FK`,`Nullable`,`Unique`,`Default Value`,`Indexing`],a=`<table class="data-table" id="schema-grid-table-${e}"><thead><tr><th class="row-header">#</th>`;r.forEach((e,t)=>{if(e===`indexing`){a+=`<th class="th-indexing" data-col-key="indexing">
1246
- <div class="th-content-wrapper">
1247
- <span class="th-name">${i[t]}</span>
1248
- </div>
1249
- </th>`;return}let n=``;window.SchemaGrid.sortState.colKey===e&&(n=`<span class="material-symbols-outlined th-sort-icon">${window.SchemaGrid.sortState.asc?`arrow_upward`:`arrow_downward`}</span>`),a+=`<th class="sortable" data-col-key="${e}">
1250
- <div class="th-content-wrapper">
1251
- <span class="th-name">${i[t]}</span>
1252
- ${n}
1253
- </div>
1254
- </th>`}),a+=`</tr></thead><tbody>`,n&&n.length>0&&n.forEach((e,t)=>{a+=`<tr><td class="row-header" data-row-idx="${t}">${t+1}</td>`,r.forEach((r,i)=>{if(r===`indexing`){if(t===0){let e=(n?n.length:0)+1,t=window.SchemaGrid.indexes.length,r=window.SchemaGrid.pendingIndexEdits.added.length,i=window.SchemaGrid.pendingIndexEdits.dropped.length,o=t+r-i;a+=`<td class="manage-indexes-cell" rowspan="${e}">
1255
- <div class="manage-indexes-wrapper">
1256
- <button type="button" class="btn-manage-indexes" title="Open Index Manager">
1257
- <span class="material-symbols-outlined">key</span>
1258
- <span>Manage Indexes</span>
1259
- <span class="index-count-pill">${o}</span>
1260
- </button>
1261
- </div>
1262
- </td>`}return}let o=e[r],s=``,c=``;if(r===`name`)c=String(o||``).replace(/"/g,`&quot;`),s=`<strong>${String(o||``).replace(/</g,`&lt;`)}</strong>`;else if(r===`type`)c=String(o||``).replace(/"/g,`&quot;`),s=`<span class="schema-type-mono">${String(o||``).replace(/</g,`&lt;`)}</span>`;else if(r===`isPk`){let t=!!e.isPk,n=!!e.fkTarget;if(t&&n){let t=e.fkTarget.onDelete&&e.fkTarget.onDelete!==`NO ACTION`?` (${e.fkTarget.onDelete})`:``,n=[e.fkTarget.onDelete?`ON DELETE ${e.fkTarget.onDelete}`:``,e.fkTarget.onUpdate?`ON UPDATE ${e.fkTarget.onUpdate}`:``].filter(Boolean).join(`, `),r=`Primary Foreign Key (Composite Key referencing ${e.fkTarget.table}.${e.fkTarget.column}${n?` [${n}]`:``})`;c=`PFK: ${e.fkTarget.table}.${e.fkTarget.column}${t}`,s=`<span class="col-badge badge-pfk" title="${r}">PFK &rarr; ${e.fkTarget.table}.${e.fkTarget.column}${t}</span>`}else if(t)c=`PK`,s=`<span class="col-badge badge-pk">PK</span>`;else if(n){let t=e.fkTarget.onDelete&&e.fkTarget.onDelete!==`NO ACTION`?` (${e.fkTarget.onDelete})`:``,n=[e.fkTarget.onDelete?`ON DELETE ${e.fkTarget.onDelete}`:``,e.fkTarget.onUpdate?`ON UPDATE ${e.fkTarget.onUpdate}`:``].filter(Boolean).join(`, `),r=`References ${e.fkTarget.table}.${e.fkTarget.column}${n?` [${n}]`:``}`;c=`FK: ${e.fkTarget.table}.${e.fkTarget.column}${t}`,s=`<span class="col-badge badge-fk" title="${r}">FK &rarr; ${e.fkTarget.table}.${e.fkTarget.column}${t}</span>`}else c=`-`,s=`<span class="text-soft">-</span>`}else if(r===`nullable`){let t=!e.isPk&&!!e.nullable;c=t?`Yes`:`No`,s=t?`<span class="text-soft">Yes</span>`:`<span class="badge-notnull">No</span>`}else if(r===`isUnique`){let t=!!e.isPk,n=t||!!e.isUnique;c=n?`Yes`:`No`,s=t?`<span class="badge-unique-pk" title="Primary Key is always unique"><span class="material-symbols-outlined text-12">lock</span> Yes</span>`:n?`<span class="badge-unique">Yes</span>`:`<span class="text-soft">-</span>`}else r===`defaultValue`?(c=String(o??``).replace(/"/g,`&quot;`),s=o!=null&&o!==``?`<span class="schema-type-mono text-soft">${String(o).replace(/</g,`&lt;`)}</span>`:`<span class="text-soft">-</span>`):(c=String(o||``).replace(/"/g,`&quot;`),s=String(o||``).replace(/</g,`&lt;`));a+=`<td class="data-cell" data-row-idx="${t}" data-col-idx="${i}" data-pk="${e.name}" data-col-key="${r}" data-original="${c}">${s}</td>`}),a+=`</tr>`}),a+=`<tr class="ghost-row-tr">`;let o=n?n.length:0;a+=`<td class="row-header" data-row-idx="${o}">*</td>`,r.forEach((e,t)=>{e!==`indexing`&&(a+=`<td class="data-cell ghost-row" data-row-idx="${o}" data-col-idx="${t}" data-insert-index="0" data-col-key="${e}">${t===0?`<span class="ghost-cell-hint">+ Add Column</span>`:``}</td>`)}),a+=`</tr></tbody></table>`,t.innerHTML=a,document.querySelectorAll(`#schema-grid-table-${e} th.sortable`).forEach(e=>{e.onclick=t=>{if(window.SchemaGrid.isResizing)return;if(Object.keys(window.SchemaGrid.pendingEdits).length>0||window.SchemaGrid.pendingInserts.length>1||window.SchemaGrid.pendingDeletes.size>0||window.SchemaGrid.pendingIndexEdits.added.length>0||window.SchemaGrid.pendingIndexEdits.dropped.length>0){alert(`You have unsaved changes! Please press Ctrl+S to save them before sorting.`);return}let n=e.dataset.colKey;window.SchemaGrid.sortState.colKey===n?window.SchemaGrid.sortState.asc=!window.SchemaGrid.sortState.asc:(window.SchemaGrid.sortState.colKey=n,window.SchemaGrid.sortState.asc=!0),window.SchemaGrid.selection={startRow:-1,startCol:-1,endRow:-1,endCol:-1,isDragging:!1},window.renderSchemaGrid()},R(e,window.SchemaGrid)}),L(t,`schema-grid-table-${e}`,window.SchemaGrid,r.length),be(t,r)},window.renderSchemaGrid();let s=document.getElementById(`schema-search-val-${e}`),c=document.getElementById(`btn-clear-schema-search-${e}`),l=()=>{!c||!s||(s.value.trim()===``?c.classList.add(`hidden`):c.classList.remove(`hidden`))};s&&s.addEventListener(`input`,e=>{window.SchemaGrid.filterText=e.target.value,l(),window.SchemaGrid.selection={startRow:-1,startCol:-1,endRow:-1,endCol:-1,isDragging:!1},window.renderSchemaGrid()}),c&&(c.onclick=()=>{s&&(s.value=``,window.SchemaGrid.filterText=``,l(),window.renderSchemaGrid(),s.focus())});let u=document.getElementById(`btn-export-orm-${e}`);u&&(u.onclick=()=>xe(e));let d=document.getElementById(`btn-schema-diff-${e}`);d&&(d.onclick=()=>Se());let f=document.getElementById(`btn-refresh-schema-${e}`);f&&(f.onclick=()=>{(Object.keys(window.SchemaGrid.pendingEdits).length>0||window.SchemaGrid.pendingInserts.length>1||window.SchemaGrid.pendingDeletes.size>0||window.SchemaGrid.pendingIndexEdits.added.length>0||window.SchemaGrid.pendingIndexEdits.dropped.length>0)&&!confirm(`You have unsaved changes. Are you sure you want to refresh and discard them?`)||(window.SchemaGrid.pendingEdits={},window.SchemaGrid.pendingInserts=[{}],window.SchemaGrid.pendingDeletes.clear(),window.SchemaGrid.pendingIndexEdits={added:[],dropped:[]},window.SchemaGrid.history=[],window.SchemaGrid.currentTransaction=null,window.updateSidebarDirtyState?.(),Ce(e,t,document.getElementById(`view-schema-btn-${e}`)))})}else i.innerHTML=`<div class="p-6 text-error">Error: ${n.error}</div>`}catch(e){i.innerHTML=`<div class="p-6 text-error">Failed to load schema: ${e.message}</div>`}}var we=()=>localStorage.getItem(`drixio_safe_mode_disabled`)!==`true`,Te=e=>{e?localStorage.removeItem(`drixio_safe_mode_disabled`):localStorage.setItem(`drixio_safe_mode_disabled`,`true`),window.dispatchEvent(new CustomEvent(`drixio-safemode-changed`,{detail:{enabled:e}}))},Ee=e=>e.replace(/--.*$/gm,``).replace(/\/\*[\s\S]*?\*\//g,``).trim(),De=e=>{if(!e)return{isDangerous:!1};let t=Ee(e);if(!t)return{isDangerous:!1};let n=t.split(/;(?=(?:[^'"]*['"][^'"]*['"])*[^'"]*$)/).map(e=>e.trim()).filter(Boolean);for(let e of n){let t=e.replace(/\s+/g,` `),n=t.match(/^DROP\s+(TABLE|DATABASE|SCHEMA)\s+(IF\s+EXISTS\s+)?([`"']?[a-zA-Z0-9_]+[`"']?)/i);if(n){let t=n[1].toUpperCase(),r=n[3]||`target`;return{isDangerous:!0,type:`DROP ${t}`,title:`Permanent Deletion: DROP ${t}`,description:`This statement will permanently destroy the ${t.toLowerCase()} <code>${r}</code> and all data contained within it.`,sql:e}}let r=t.match(/^TRUNCATE(\s+TABLE)?\s+([`"']?[a-zA-Z0-9_]+[`"']?)/i);if(r)return{isDangerous:!0,type:`TRUNCATE TABLE`,title:`Wipe Out Table: TRUNCATE`,description:`This statement will immediately wipe out ALL records in table <code>${r[2]||`target`}</code>.`,sql:e};if(/^DELETE\s+FROM\s+/i.test(t)||/^DELETE\s+[a-zA-Z0-9_`"']+\s+FROM/i.test(t)){if(!/\bWHERE\b/i.test(t))return{isDangerous:!0,type:`DELETE without WHERE`,title:`Unrestricted DELETE Statement`,description:`This <b>DELETE</b> query does NOT contain a <code>WHERE</code> clause. Executing this will permanently delete <b>EVERY SINGLE RECORD</b> in the table.`,sql:e};let n=t.substring(t.search(/\bWHERE\b/i)+5).trim();if(/^(1\s*=\s*1|true|1)\s*$/i.test(n))return{isDangerous:!0,type:`DELETE with Trivial WHERE`,title:`Unrestricted DELETE Statement`,description:`This <b>DELETE</b> query uses an unconditional clause (<code>WHERE 1=1 / true</code>) which will delete <b>ALL RECORDS</b> in the table.`,sql:e}}if(/^UPDATE\s+([`"']?[a-zA-Z0-9_]+[`"']?)\s+SET\s+/i.test(t)){if(!/\bWHERE\b/i.test(t))return{isDangerous:!0,type:`UPDATE without WHERE`,title:`Unrestricted UPDATE Statement`,description:`This <b>UPDATE</b> query does NOT contain a <code>WHERE</code> clause. Executing this will overwrite data in <b>EVERY SINGLE RECORD</b> in the table.`,sql:e};let n=t.substring(t.search(/\bWHERE\b/i)+5).trim();if(/^(1\s*=\s*1|true|1)\s*$/i.test(n))return{isDangerous:!0,type:`UPDATE with Trivial WHERE`,title:`Unrestricted UPDATE Statement`,description:`This <b>UPDATE</b> query uses an unconditional clause (<code>WHERE 1=1 / true</code>) which will overwrite <b>ALL RECORDS</b> in the table.`,sql:e}}}return{isDangerous:!1}},Oe=({dangerInfo:e,fullSql:t,onConfirm:n,onCancel:r})=>{let i=document.getElementById(`safe-mode-modal`);i&&i.remove();let a=document.createElement(`div`);a.id=`safe-mode-modal`,a.className=`modal-overlay`;let o=(e?.sql||t||``).trim();a.innerHTML=`
1263
- <div class="modal-container safe-modal-container">
1264
- <div class="safe-modal-header">
1265
- <div class="safe-modal-title-wrap">
1266
- <div class="safe-modal-icon">
1267
- <span class="material-symbols-outlined">warning</span>
1268
- </div>
1269
- <div>
1270
- <h3 class="safe-modal-title">${e.title||`Destructive Operation Warning`}</h3>
1271
- <span class="safe-modal-badge">${e.type||`HIGH RISK`}</span>
1272
- </div>
1273
- </div>
1274
- <button id="safe-modal-close-btn" class="modal-close-btn" title="Cancel">
1275
- <span class="material-symbols-outlined">close</span>
1276
- </button>
1277
- </div>
1278
-
1279
- <div class="safe-modal-body">
1280
- <p class="safe-modal-desc">${e.description}</p>
1281
-
1282
- <div class="safe-sql-preview-label">Statement to be executed:</div>
1283
- <div class="safe-sql-preview">
1284
- <code>${o.replace(/</g,`&lt;`).replace(/>/g,`&gt;`)}</code>
1285
- </div>
1286
-
1287
- <div class="safe-modal-preference">
1288
- <label class="safe-checkbox-label">
1289
- <input type="checkbox" id="safe-dont-warn-checkbox" />
1290
- <span class="safe-checkbox-text">不再提示 (Don't warn me again)</span>
1291
- </label>
1292
- <span class="safe-preference-hint">You can re-enable Safe Mode anytime from the Console toolbar.</span>
1293
- </div>
1294
- </div>
1295
-
1296
- <div class="safe-modal-footer">
1297
- <button type="button" id="safe-modal-cancel-btn" class="btn-secondary">
1298
- Cancel
1299
- </button>
1300
- <button type="button" id="safe-modal-confirm-btn" class="safe-btn-confirm">
1301
- <span class="material-symbols-outlined" style="font-size: 16px;">gavel</span>
1302
- <span>Confirm & Run</span>
1303
- </button>
1304
- </div>
1305
- </div>
1306
- `,document.body.appendChild(a);let s=()=>{document.removeEventListener(`keydown`,c),a.remove()},c=e=>{e.key===`Escape`&&(s(),r&&r())};document.addEventListener(`keydown`,c),a.addEventListener(`click`,e=>{e.target===a&&(s(),r&&r())}),document.getElementById(`safe-modal-close-btn`).onclick=()=>{s(),r&&r()},document.getElementById(`safe-modal-cancel-btn`).onclick=()=>{s(),r&&r()},document.getElementById(`safe-modal-confirm-btn`).onclick=()=>{document.getElementById(`safe-dont-warn-checkbox`)?.checked&&(Te(!1),window.showToast&&window.showToast(`Safe Mode disabled. Future queries will run without confirmation.`,`info`)),s(),n&&n()}};function ke(e){if(!e)return[];let t=new Set,n=/\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g,r;for(;(r=n.exec(e))!==null;)r[1]&&t.add(r[1]);let i=/(?:^|[^:]):([a-zA-Z0-9_]+)/g;for(;(r=i.exec(e))!==null;)r[1]&&t.add(r[1]);return Array.from(t)}function Ae(e,t){if(!e)return``;let n=e;for(let[e,r]of Object.entries(t)){let t=r==null?``:String(r);!isNaN(Number(t))&&t.trim();let i=RegExp(`'\\{\\{\\s*${e}\\s*\\}\\}'`,`g`),a=RegExp(`':${e}'`,`g`),o=t.replace(/'/g,`''`);n=n.replace(i,`'${o}'`),n=n.replace(a,`'${o}'`);let s=RegExp(`\\{\\{\\s*${e}\\s*\\}\\}`,`g`);n=n.replace(s,t);let c=RegExp(`(^|[^:]):${e}\\b`,`g`);n=n.replace(c,(e,n)=>`${n}${t}`)}return n}function je({snippetId:e=null,defaultTitle:t=``,defaultSql:n=``,defaultDescription:r=``,defaultTags:i=[],onSaved:a=null}){let o=document.getElementById(`snippet-save-modal`);o&&o.remove();let s=document.createElement(`div`);s.id=`snippet-save-modal`,s.className=`modal-overlay`;let c=!!e,l=ke(n);s.innerHTML=`
1307
- <div class="modal-container snippet-modal-container">
1308
- <div class="modal-header">
1309
- <div class="flex items-center gap-2">
1310
- <span class="material-symbols-outlined text-primary" style="font-size: 20px;">${c?`edit_note`:`bookmark_add`}</span>
1311
- <h3 class="m-0 text-15 font-semibold">${c?`Edit Query Snippet`:`Save Query as Snippet`}</h3>
1312
- </div>
1313
- <button type="button" id="close-save-snippet-btn" class="modal-close-btn" title="Close">
1314
- <span class="material-symbols-outlined">close</span>
1315
- </button>
1316
- </div>
1317
-
1318
- <div class="snippet-modal-body">
1319
- <div class="snippet-field-group">
1320
- <div class="snippet-field-label">
1321
- <span>Title <span style="color: var(--color-error); font-size: 11px;">*</span></span>
1322
- </div>
1323
- <input type="text" id="save-snippet-title" class="snippet-input" placeholder="e.g. Active Users by Status" value="${t}" autofocus />
1324
- </div>
1325
-
1326
- <div class="snippet-field-group">
1327
- <div class="snippet-field-label">
1328
- <span>Description</span>
1329
- <span class="snippet-field-hint">Optional</span>
1330
- </div>
1331
- <input type="text" id="save-snippet-desc" class="snippet-input" placeholder="e.g. Summarizes user counts with role filter" value="${r}" />
1332
- </div>
1333
-
1334
- <div class="snippet-field-group">
1335
- <div class="snippet-field-label">
1336
- <span>Tags</span>
1337
- <span class="snippet-field-hint">Comma separated</span>
1338
- </div>
1339
- <input type="text" id="save-snippet-tags" class="snippet-input" placeholder="e.g. users, metrics, cleanup" value="${i.join(`, `)}" />
1340
- </div>
1341
-
1342
- <div class="snippet-field-group">
1343
- <div class="snippet-field-label">
1344
- <span>SQL Query <span style="color: var(--color-error); font-size: 11px;">*</span></span>
1345
- <div id="save-snippet-param-badges" class="snippet-badges-wrap">
1346
- ${l.map(e=>`<span class="snippet-param-badge">:${e}</span>`).join(``)}
1347
- </div>
1348
- </div>
1349
- <textarea id="save-snippet-sql" class="snippet-textarea" placeholder="SELECT * FROM table...">${n}</textarea>
1350
- </div>
1351
- </div>
1352
-
1353
- <div class="modal-footer">
1354
- <div class="text-11 text-soft flex items-center gap-1" style="user-select: none;">
1355
- <span class="material-symbols-outlined" style="font-size: 14px;">keyboard</span>
1356
- <span>Ctrl + Enter to save</span>
1357
- </div>
1358
- <div class="snippet-footer-actions">
1359
- <button type="button" id="cancel-save-snippet-btn" class="btn-secondary">Cancel</button>
1360
- <button type="button" id="confirm-save-snippet-btn" class="btn-primary" style="display: inline-flex; align-items: center; gap: 6px;">
1361
- <span class="material-symbols-outlined" style="font-size: 16px;">check</span>
1362
- <span id="confirm-save-snippet-btn-text">${c?`Update Snippet`:`Save Snippet`}</span>
1363
- </button>
1364
- </div>
1365
- </div>
1366
- </div>
1367
- `,document.body.appendChild(s);let u=()=>{document.removeEventListener(`keydown`,d),s.remove()},d=e=>{e.key===`Escape`&&u(),(e.ctrlKey||e.metaKey)&&e.key===`Enter`&&(e.preventDefault(),_.click())};document.addEventListener(`keydown`,d),s.addEventListener(`click`,e=>{e.target===s&&u()}),document.getElementById(`close-save-snippet-btn`).onclick=u,document.getElementById(`cancel-save-snippet-btn`).onclick=u;let f=document.getElementById(`save-snippet-title`),p=document.getElementById(`save-snippet-desc`),m=document.getElementById(`save-snippet-tags`),h=document.getElementById(`save-snippet-sql`),g=document.getElementById(`save-snippet-param-badges`),_=document.getElementById(`confirm-save-snippet-btn`),v=document.getElementById(`confirm-save-snippet-btn-text`);h.oninput=()=>{let e=ke(h.value);g.innerHTML=e.map(e=>`<span class="snippet-param-badge">:${e}</span>`).join(``)},_.onclick=async()=>{let t=f.value.trim(),n=h.value.trim();if(!n){window.showToast?window.showToast(`SQL query cannot be empty.`,`error`):alert(`SQL query cannot be empty.`),h.focus();return}let r=p.value.trim(),i=m.value.split(`,`).map(e=>e.trim()).filter(Boolean);_.disabled=!0,v&&(v.textContent=`Saving...`);try{let o;o=c?await b(e,{title:t||`Untitled Snippet`,sql:n,description:r,tags:i}):await y({title:t||`Untitled Snippet`,sql:n,description:r,tags:i}),o.success?(window.showToast&&window.showToast(c?`Snippet updated`:`Snippet saved to .drixio/snippets.json`,`success`),a&&a(o.data),u()):window.showToast?window.showToast(`Failed to save snippet: ${o.error}`,`error`):alert(`Failed to save snippet: ${o.error}`)}catch(e){window.showToast?window.showToast(`Error saving snippet: ${e.message}`,`error`):alert(`Error saving snippet: ${e.message}`)}finally{_.disabled=!1,v&&(v.textContent=c?`Update Snippet`:`Save Snippet`)}},f.focus()}function Me({snippet:e,onExecute:t}){let n=ke(e.sql);if(n.length===0){t(e.sql);return}let r=document.getElementById(`snippet-param-modal`);r&&r.remove();let i=document.createElement(`div`);i.id=`snippet-param-modal`,i.className=`modal-overlay`,i.innerHTML=`
1368
- <div class="modal-container snippet-modal-container" style="width: 600px;">
1369
- <div class="modal-header">
1370
- <div class="flex items-center gap-2">
1371
- <span class="material-symbols-outlined text-primary" style="font-size: 20px;">tune</span>
1372
- <h3 class="m-0 text-15 font-semibold">Run Query with Parameters</h3>
1373
- </div>
1374
- <button type="button" id="close-param-modal-btn" class="modal-close-btn" title="Close">
1375
- <span class="material-symbols-outlined">close</span>
1376
- </button>
1377
- </div>
1378
-
1379
- <div class="snippet-modal-body" style="gap: 14px;">
1380
- <div class="flex items-center gap-2 text-12" style="color: var(--color-text-secondary); background: var(--color-bg-secondary); padding: 8px 12px; border-radius: 6px; border: 1px solid var(--color-border);">
1381
- <span class="material-symbols-outlined text-primary" style="font-size: 16px;">bookmark</span>
1382
- <span>Query: <b style="color: var(--color-text);">${e.title}</b></span>
1383
- </div>
1384
-
1385
- <div class="snippet-field-group">
1386
- <div class="snippet-field-label">
1387
- <span>Parameters (${n.length})</span>
1388
- <span class="snippet-field-hint">Fill values for placeholders</span>
1389
- </div>
1390
- <div id="param-fields-container" style="display: flex; flex-direction: column; gap: 8px;">
1391
- ${n.map(e=>`
1392
- <div class="flex items-center gap-2">
1393
- <span class="snippet-param-badge" style="width: 100px; justify-content: center; height: 32px; box-sizing: border-box; font-size: 12px;">:${e}</span>
1394
- <input type="text" class="snippet-input param-input" data-param="${e}" placeholder="Enter value for :${e}" style="flex: 1; height: 32px; font-size: 12.5px;" />
1395
- </div>
1396
- `).join(``)}
1397
- </div>
1398
- </div>
1399
-
1400
- <div class="snippet-field-group">
1401
- <div class="snippet-field-label">
1402
- <span>Preview SQL</span>
1403
- </div>
1404
- <pre id="param-preview-sql" class="diff-sql-pre" style="min-height: 100px; height: 120px; border: 1px solid var(--color-border); border-radius: 6px; padding: 10px 12px; font-size: 12px; line-height: 1.5; font-family: var(--font-mono); overflow-y: auto; background: var(--color-bg-secondary-dark);"></pre>
1405
- </div>
1406
- </div>
1407
-
1408
- <div class="modal-footer">
1409
- <div class="text-11 text-soft flex items-center gap-1" style="user-select: none;">
1410
- <span class="material-symbols-outlined" style="font-size: 14px;">keyboard</span>
1411
- <span>Enter to execute</span>
1412
- </div>
1413
- <div class="snippet-footer-actions">
1414
- <button type="button" id="cancel-param-btn" class="btn-secondary">Cancel</button>
1415
- <button type="button" id="confirm-run-param-btn" class="btn-primary" style="display: inline-flex; align-items: center; gap: 6px;">
1416
- <span class="material-symbols-outlined" style="font-size: 16px;">play_arrow</span>
1417
- <span>Execute Query</span>
1418
- </button>
1419
- </div>
1420
- </div>
1421
- </div>
1422
- `,document.body.appendChild(i);let a=()=>{document.removeEventListener(`keydown`,o),i.remove()},o=e=>{e.key===`Escape`&&a()};document.addEventListener(`keydown`,o),i.addEventListener(`click`,e=>{e.target===i&&a()}),document.getElementById(`close-param-modal-btn`).onclick=a,document.getElementById(`cancel-param-btn`).onclick=a;let s=document.getElementById(`param-preview-sql`),c=i.querySelectorAll(`.param-input`),l=()=>{let t={};c.forEach(e=>{let n=e.getAttribute(`data-param`);t[n]=e.value||`:${n}`}),s.textContent=Ae(e.sql,t)};c.forEach(e=>{e.oninput=l,e.onkeydown=e=>{e.key===`Enter`&&document.getElementById(`confirm-run-param-btn`).click()}}),l(),c.length>0&&c[0].focus(),document.getElementById(`confirm-run-param-btn`).onclick=()=>{let n={};c.forEach(e=>{let t=e.getAttribute(`data-param`);n[t]=e.value});let r=Ae(e.sql,n);a(),t(r)}}var Ne=0,Pe=async(e,t,n,r=!1)=>{let i=e||t.value.substring(t.selectionStart,t.selectionEnd).trim();if(i||=t.value.trim(),!i)return;if(i.toLowerCase()===`clear`||i.toLowerCase()===`clear;`){n.innerHTML=``,e||(t.selectionStart=t.selectionEnd,t.value=``);return}if(!r&&we()){let r=De(i);if(r.isDangerous){let a=!e;Oe({dangerInfo:r,fullSql:i,onConfirm:()=>{a&&(t.selectionStart=t.selectionEnd,t.value=``,t.dispatchEvent(new Event(`input`))),Pe(i,t,n,!0)}});return}}window.AppState.lastQuery=i,window.AppState.queryHistory||(window.AppState.queryHistory=[]);let a=window.AppState.queryHistory;a.includes(i)||(a.unshift(i),a.length>10&&a.pop(),window.dispatchEvent(new Event(`query-history-updated`))),Ne++;let o=`history-block-${Ne}`,s=document.getElementById(`console-welcome-msg`);s&&(s.style.display=`none`);let c=document.createElement(`div`);c.className=`console-history-block`,c.innerHTML=`
1423
- <div class="console-history-header">
1424
- <div class="console-history-sql">${i.replace(/</g,`&lt;`).replace(/>/g,`&gt;`)}</div>
1425
- <div class="console-history-actions">
1426
- <button class="icon-btn save-snippet-btn" title="Save as Snippet"><span class="material-symbols-outlined" class="icon-16">bookmark_add</span></button>
1427
- <button class="icon-btn copy-btn" title="Copy SQL"><span class="material-symbols-outlined" class="icon-16">content_copy</span></button>
1428
- <button class="icon-btn rerun-btn" title="Re-run"><span class="material-symbols-outlined" class="icon-16">refresh</span></button>
1429
- </div>
1430
- </div>
1431
- <div id="${o}-results" class="console-history-results">
1432
- <div class="console-msg-executing">
1433
- <span class="material-symbols-outlined" class="animate-spin">sync</span> Executing...
1434
- </div>
1435
- </div>
1436
- `,n.appendChild(c);let l=c.querySelector(`.save-snippet-btn`);l&&(l.onclick=()=>{je({defaultSql:i,onSaved:()=>{window.dispatchEvent(new CustomEvent(`drixio-snippets-updated`))}})}),c.querySelector(`.copy-btn`).onclick=()=>{navigator.clipboard.writeText(i),window.showToast?window.showToast(`SQL copied to clipboard`):alert(`Copied`)},c.querySelector(`.rerun-btn`).onclick=()=>Pe(i,t,n),n.scrollTop=n.scrollHeight,e||(t.selectionStart=t.selectionEnd,t.value=``);try{let e=performance.now(),t=await u(i),r=(performance.now()-e).toFixed(1),a=document.getElementById(`${o}-results`);if(!t.success){a.innerHTML=`<div class="console-msg-error"><span class="material-symbols-outlined" class="icon-16 mt-2px">error</span><span>Error: ${t.error}</span></div>`,n.scrollTop=n.scrollHeight;return}let s=t.data.rows||[],c=s.length,l=s.length===1&&s[0].Result===`Success`&&s[0].AffectedRows!==void 0||c===0&&t.data.affectedRows!==void 0;if(t.data?.connectionChanged){window.showToast&&window.showToast(`Database updated: ${t.data.dbConfig?.dbName||`Connected`}`,`success`),window.initSidebar&&window.initSidebar(!0);let e=t.data.dbConfig?.type&&t.data.dbConfig.type!==`none`;if(window.setStudioConnectionMode&&window.setStudioConnectionMode(!!e),t.data.dbConfig?.type){window.AppState.dbType=t.data.dbConfig.type;let e=document.getElementById(`brand-db-type`);e&&(e.textContent=t.data.dbConfig.type===`none`?`NO DATABASE`:t.data.dbConfig.type.toUpperCase());let n=document.getElementById(`sidebar-db-status-dot`);n&&(t.data.dbConfig.type===`none`?(n.classList.remove(`status-connected`,`status-error`),n.classList.add(`status-warning`)):(n.classList.remove(`status-error`,`status-warning`),n.classList.add(`status-connected`)))}a.innerHTML=`
1437
- <div class="console-mutation-banner" style="background: rgba(16, 185, 129, 0.08); border-color: rgba(16, 185, 129, 0.25);">
1438
- <div class="console-mutation-info" style="color: #10b981;">
1439
- <span class="material-symbols-outlined icon">check_circle</span>
1440
- <span><b>${s[0]?.Result||`Connected`}:</b> ${s[0]?.Database||s[0]?.Target||s[0]?.Path||`Database ready`}</span>
1441
- <span class="text-soft">&middot;</span>
1442
- <span class="text-soft">${r}ms</span>
1443
- </div>
1444
- </div>
1445
- `,n.scrollTop=n.scrollHeight;return}if(/^\s*(CREATE|DROP|ALTER)\s+(TABLE|VIEW)/i.test(i)&&window.initSidebar?.(!0),l){let e=t.data.affectedRows??s[0]?.AffectedRows??0;a.innerHTML=`
1446
- <div class="console-mutation-banner">
1447
- <div class="console-mutation-info">
1448
- <span class="material-symbols-outlined icon">task_alt</span>
1449
- <span>Query OK, <b>${e}</b> row${e===1?``:`s`} affected</span>
1450
- <span class="text-soft">&middot;</span>
1451
- <span class="text-soft">${r}ms</span>
1452
- </div>
1453
- </div>
1454
- `,n.scrollTop=n.scrollHeight;return}if(c===0){a.innerHTML=`
1455
- <div class="console-mutation-banner">
1456
- <div class="console-mutation-info">
1457
- <span class="material-symbols-outlined icon">task_alt</span>
1458
- <span>Query OK, 0 rows returned</span>
1459
- <span class="text-soft">&middot;</span>
1460
- <span class="text-soft">${r}ms</span>
1461
- </div>
1462
- </div>
1463
- `,n.scrollTop=n.scrollHeight;return}let d=Object.keys(s[0]),f=document.createElement(`div`);f.className=`console-stats-header console-stats-divider`;let p=s.slice(0,1e3),h=c>1e3?` (Showing first 1000)`:``,g=t.data.affectedRows!==void 0&&t.data.affectedRows>0?` &middot; ${t.data.affectedRows} affected`:``;f.innerHTML=`
1464
- <div class="console-stats-info">
1465
- <span class="material-symbols-outlined icon">check_circle</span>
1466
- <span>Query OK, <b>${c}</b> row${c===1?``:`s`}${h}${g} &middot; ${r}ms</span>
1467
- </div>
1468
- <button class="console-export-btn export-btn" title="Export current results as CSV">
1469
- <span class="material-symbols-outlined" style="font-size: 15px;">download</span> CSV
1470
- </button>
1471
- `,a.innerHTML=``,a.appendChild(f),f.querySelector(`.export-btn`).onclick=()=>{m({rows:s},`csv`)};let _=document.createElement(`div`);_.className=`table-container`,_.style.border=`1px solid var(--color-border)`,_.style.borderRadius=`6px`,_.style.overflow=`auto`,_.style.maxHeight=`320px`;let v=document.createElement(`table`);v.className=`data-table`;let y=document.createElement(`thead`);y.style.position=`sticky`,y.style.top=`0`,y.style.zIndex=`10`;let b=document.createElement(`tr`),x=document.createElement(`th`);x.className=`row-header`,x.textContent=`#`,b.appendChild(x),d.forEach(e=>{let t=document.createElement(`th`);t.textContent=e,b.appendChild(t)}),y.appendChild(b),v.appendChild(y);let S=document.createElement(`tbody`);p.forEach((e,t)=>{let n=document.createElement(`tr`),r=document.createElement(`td`);r.className=`row-header`,r.textContent=t+1,n.appendChild(r),d.forEach(t=>{let r=document.createElement(`td`);r.className=`data-cell data-cell-truncate`;let i=e[t];if(i===null){let e=document.createElement(`span`);e.textContent=`NULL`,e.classList.add(`text-soft`,`italic`),r.appendChild(e)}else{let e=String(i);r.textContent=e,e.length>40&&(r.title=e)}n.appendChild(r)}),S.appendChild(n)}),v.appendChild(S),_.appendChild(v),a.appendChild(_),setTimeout(()=>n.scrollTop=n.scrollHeight,10)}catch(e){let t=document.getElementById(`${o}-results`);t&&(t.innerHTML=`<div class="console-msg-error"><span class="material-symbols-outlined" class="icon-16 mt-2px">error</span><span>Error: ${e.message}</span></div>`,n.scrollTop=n.scrollHeight)}},Fe=(e,t)=>{let n=-1,r=``,i=``,a=(e,t)=>{if(!e||!e.includes(`;`))return e.trim();let n=[],r=0,i=!1,a=!1;for(let t=0;t<e.length;t++){let o=e[t],s=t>0?e[t-1]:``;o===`'`&&!a&&s!==`\\`?i=!i:o===`"`&&!i&&s!==`\\`?a=!a:o===`;`&&!i&&!a&&(n.push({start:r,end:t+1,sql:e.slice(r,t+1).trim()}),r=t+1)}if(r<e.length){let t=e.slice(r).trim();t&&n.push({start:r,end:e.length,sql:t})}return n.find(e=>t>=e.start&&t<=e.end)?.sql||n[n.length-1]?.sql||e.trim()},o=()=>{let t=e.selectionStart,n=e.selectionEnd,r=document.getElementById(`run-sql-btn-text`),a=document.getElementById(`run-sql-btn`);if(typeof t==`number`&&typeof n==`number`&&t!==n){let o=e.value.substring(t,n).trim();if(o){i=o,r&&(r.textContent=`Run Selection`),a&&(a.title=`Run Selected SQL (Ctrl+Enter)`,a.classList.add(`has-selection`));return}}i=``,r&&(r.textContent=`Run`),a&&(a.title=`Run SQL Query (Ctrl+Enter)`,a.classList.remove(`has-selection`))},s=()=>{let o=i;if(!o){let t=e.selectionStart,n=e.selectionEnd;typeof t==`number`&&typeof n==`number`&&t!==n&&(o=e.value.substring(t,n).trim())}if(!o){let t=e.value.trim();t&&(o=a(e.value,e.selectionStart)||t)}o&&Pe(o,e,t),n=-1,r=``,m()},c=document.getElementById(`console-safe-toggle-btn`),l=document.getElementById(`console-safe-text`),u=()=>{!c||!l||(we()?(c.className=`console-safe-toggle-btn active`,l.textContent=`Safe Mode ON`,c.title=`Safe Mode: Protection ON (Destructive queries will prompt for confirmation)`):(c.className=`console-safe-toggle-btn disabled`,l.textContent=`Safe Mode OFF`,c.title=`Safe Mode: Protection OFF (Click to re-enable guard)`))};c&&(u(),c.onclick=()=>{let e=we();Te(!e),u(),window.showToast&&window.showToast(e?`Safe Mode disabled (Guard OFF)`:`Safe Mode enabled (Destructive query guard ON)`,e?`info`:`success`)},window.addEventListener(`drixio-safemode-changed`,()=>{u()}));let d=document.getElementById(`run-sql-btn`);d&&(d.onclick=()=>s());let f=document.getElementById(`console-clear-editor-btn`);f&&(f.onclick=()=>{e.value=``,m(),e.focus({preventScroll:!0})}),document.querySelectorAll(`.template-chip`).forEach(t=>{t.addEventListener(`click`,()=>{let n=t.getAttribute(`data-sql`);n&&(e.value=n,m(),e.focus({preventScroll:!0}))})});let p=document.getElementById(`sql-highlight-layer`),m=()=>{let t=e.value;if(t===``){p&&(p.innerHTML=`<span class="hl-placeholder">Write your SQL query here (e.g. SELECT * FROM users)...</span>`);return}t=t.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`);let n=RegExp(`('.*?'|".*?")|\\b(\\d+)\\b|\\b(${`SELECT.FROM.WHERE.AND.OR.IN.NOT.NULL.IS.ORDER.BY.GROUP.ASC.DESC.LIMIT.OFFSET.JOIN.INNER.LEFT.RIGHT.ON.AS.CREATE.TABLE.DROP.ALTER.INSERT.INTO.VALUES.UPDATE.SET.DELETE.PRAGMA.EXPLAIN.QUERY.PLAN.WITH.UNION.ALL.HAVING.LIKE.BETWEEN.EXISTS.CASE.WHEN.THEN.ELSE.END.CAST.DEFAULT.PRIMARY.KEY.FOREIGN.UNIQUE.CHECK.REFERENCES.AUTOINCREMENT.COUNT.SUM.AVG.MIN.MAX.COALESCE.BEGIN.COMMIT.ROLLBACK.INDEX.VIEW.TRIGGER.REPLACE.CROSS.FULL.OUTER`.split(`.`).join(`|`)})\\b`,`gi`);t=t.replace(n,(e,t,n,r)=>t?`<span class="hl-string">${t}</span>`:n?`<span class="hl-number">${n}</span>`:r?`<span class="hl-keyword">${r.toUpperCase()}</span>`:e),t.endsWith(`\\n`)&&(t+=` `),p&&(p.innerHTML=t)},h=document.getElementById(`console-autocomplete-popup`),g=0,_=[],y=null,b=`SELECT.FROM.WHERE.INSERT INTO.UPDATE.SET.DELETE FROM.JOIN.LEFT JOIN.RIGHT JOIN.INNER JOIN.ON.ORDER BY.GROUP BY.HAVING.LIMIT.OFFSET.AND.OR.NOT.IN.IS NULL.IS NOT NULL.LIKE.BETWEEN.EXISTS.CASE.WHEN.THEN.ELSE.END.CREATE TABLE.ALTER TABLE.DROP TABLE.PRIMARY KEY.FOREIGN KEY.COUNT(*).SUM().AVG().MIN().MAX().DISTINCT.AS.VALUES.BEGIN.COMMIT.ROLLBACK.PRAGMA.EXPLAIN.WITH.UNION ALL`.split(`.`),S=()=>{let e=document.querySelectorAll(`.table-btn[data-table]`);return Array.from(e).map(e=>e.getAttribute(`data-table`)).filter(Boolean)},C=()=>{h&&(h.classList.add(`hidden`),h.innerHTML=``),_=[],g=0,y=null},w=()=>{if(!h||_.length===0){C();return}h.innerHTML=_.map((e,t)=>`
1472
- <div class="autocomplete-item ${t===g?`active`:``}" data-idx="${t}">
1473
- <div class="autocomplete-item-left">
1474
- <span class="material-symbols-outlined autocomplete-item-icon">${e.icon}</span>
1475
- <span class="autocomplete-item-text">${e.text}</span>
1476
- </div>
1477
- <span class="autocomplete-item-type">${e.type}</span>
1478
- </div>
1479
- `).join(``),h.classList.remove(`hidden`),h.querySelectorAll(`.autocomplete-item`).forEach(e=>{e.onmousedown=t=>{t.preventDefault();let n=parseInt(e.dataset.idx,10);E(n)}})},T=()=>{let t=e.selectionStart,n=e.value.slice(0,t).match(/([a-zA-Z0-9_]+)$/);if(!n){C();return}let r=n[1];y={prefix:r,start:t-r.length,end:t};let i=r.toLowerCase(),a=[];S().forEach(e=>{e.toLowerCase().startsWith(i)&&e.toLowerCase()!==i&&a.push({text:e,type:`table`,icon:`table_chart`})}),b.forEach(e=>{e.toLowerCase().startsWith(i)&&e.toLowerCase()!==i&&a.push({text:e,type:`keyword`,icon:`code`})}),_=a.slice(0,8),g=0,_.length>0?w():C()},E=(t=g)=>{let n=_[t];if(!n||!y)return;let r=e.value.slice(0,y.start),i=e.value.slice(y.end),a=n.text+` `;e.value=r+a+i;let o=y.start+a.length;e.selectionStart=o,e.selectionEnd=o,C(),m(),e.focus({preventScroll:!0})};e.addEventListener(`input`,()=>{m(),T(),o()}),e.addEventListener(`select`,o),e.addEventListener(`mouseup`,o),e.addEventListener(`keyup`,e=>{(e.key.startsWith(`Arrow`)||e.key===`Home`||e.key===`End`||e.shiftKey)&&o()}),document.addEventListener(`selectionchange`,()=>{document.activeElement===e&&o()}),e.addEventListener(`blur`,()=>{setTimeout(C,150)}),e.addEventListener(`scroll`,()=>{p&&(p.scrollTop=e.scrollTop,p.scrollLeft=e.scrollLeft)}),m(),e.addEventListener(`keydown`,t=>{if(h&&!h.classList.contains(`hidden`)&&_.length>0){if(t.key===`ArrowDown`){t.preventDefault(),g=(g+1)%_.length,w();return}else if(t.key===`ArrowUp`){t.preventDefault(),g=(g-1+_.length)%_.length,w();return}else if(t.key===`Tab`||t.key===`Enter`&&!t.ctrlKey&&!t.metaKey){t.preventDefault(),E(g);return}else if(t.key===`Escape`){t.preventDefault(),C();return}}let i=window.AppState.queryHistory||[];if(t.key===`ArrowUp`)e.selectionStart===0&&e.selectionEnd===0&&n<i.length-1&&(t.preventDefault(),n===-1&&(r=e.value),n++,e.value=i[n],m(),setTimeout(()=>{e.selectionStart=e.value.length,e.selectionEnd=e.value.length},0));else if(t.key===`ArrowDown`)e.selectionStart===e.value.length&&e.selectionEnd===e.value.length&&n>-1&&(t.preventDefault(),n--,n===-1?e.value=r:e.value=i[n],m(),setTimeout(()=>{e.selectionStart=e.value.length,e.selectionEnd=e.value.length},0));else if((t.ctrlKey||t.metaKey)&&t.key===`Enter`)t.preventDefault(),C(),s();else if(t.key===`Tab`){t.preventDefault();let n=e.selectionStart,r=e.selectionEnd,i=e.value;if(n===r)document.queryCommandSupported?.(`insertText`)?document.execCommand(`insertText`,!1,` `):(e.value=i.substring(0,n)+` `+i.substring(r),e.selectionStart=e.selectionEnd=n+2);else{let a=i.substring(0,n),o=i.substring(r),s=a.lastIndexOf(`
1480
- `)+1,c=i.substring(s,r).split(`
1481
- `);if(t.shiftKey){let t=c.map(e=>e.replace(/^(?: | )/,``)).join(`
1482
- `);e.value=i.substring(0,s)+t+o,e.selectionStart=s,e.selectionEnd=s+t.length}else{let t=c.map(e=>` `+e).join(`
1483
- `);e.value=i.substring(0,s)+t+o,e.selectionStart=s,e.selectionEnd=s+t.length}}m()}else t.key===`Enter`&&!t.shiftKey&&e.value.trim().endsWith(`;`)&&(t.preventDefault(),C(),s())});let D=document.getElementById(`console-snippets-drawer`),O=document.getElementById(`snippets-list-container`),k=document.getElementById(`snippets-search-input`),A=document.getElementById(`console-snippets-toggle-btn`),j=document.getElementById(`console-save-snippet-btn`),M=document.getElementById(`close-snippets-drawer-btn`),N=document.getElementById(`snippets-drawer-new-btn`),P=[],F=async()=>{if(O)try{let e=await v();e.success&&Array.isArray(e.data)?(P=e.data,I()):O.innerHTML=`<div class="p-3 text-12 text-center" style="color: var(--color-error);">Failed to load snippets</div>`}catch(e){O.innerHTML=`<div class="p-3 text-12 text-center" style="color: var(--color-error);">Error loading snippets: ${e.message}</div>`}},I=()=>{if(!O)return;let n=(k?.value||``).trim().toLowerCase(),r=P;if(n&&(r=P.filter(e=>{let t=(e.title||``).toLowerCase().includes(n),r=(e.description||``).toLowerCase().includes(n),i=(e.sql||``).toLowerCase().includes(n),a=(e.tags||[]).some(e=>e.toLowerCase().includes(n));return t||r||i||a})),r.length===0){O.innerHTML=`
1484
- <div class="snippets-empty-state">
1485
- <span class="material-symbols-outlined">bookmark_border</span>
1486
- <span>No snippets found</span>
1487
- </div>`;return}O.innerHTML=r.map(e=>{let t=ke(e.sql).length>0,n=(e.tags||[]).map(e=>`<span class="snippet-tag-pill">${e}</span>`).join(``),r=(e.sql||``).replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`);return`
1488
- <div class="snippet-card" data-id="${e.id}">
1489
- <div class="snippet-card-title-row">
1490
- <span class="snippet-card-title" title="${e.title}">${e.title}</span>
1491
- ${e.isBuiltin?`<span class="snippet-badge-builtin">Built-in</span>`:``}
1492
- </div>
1493
- ${e.description?`<div class="snippet-card-desc">${e.description}</div>`:``}
1494
- ${n?`<div class="snippet-card-tags">${n}</div>`:``}
1495
- <pre class="snippet-sql-preview">${r}</pre>
1496
- <div class="snippet-card-actions">
1497
- <div class="snippet-card-actions-left">
1498
- <button type="button" class="snippet-btn primary snip-run-btn" title="${t?`Run with Parameters`:`Run Query`}">
1499
- <span class="material-symbols-outlined" style="font-size: 14px;">play_arrow</span>
1500
- <span>Run</span>
1501
- </button>
1502
- <button type="button" class="snippet-btn secondary snip-insert-btn" title="Insert into Editor">
1503
- <span class="material-symbols-outlined" style="font-size: 14px;">input</span>
1504
- <span>Insert</span>
1505
- </button>
1506
- </div>
1507
- <div class="snippet-card-actions-right">
1508
- <button type="button" class="snippet-action-btn snip-edit-btn" title="Edit Snippet">
1509
- <span class="material-symbols-outlined">edit</span>
1510
- </button>
1511
- ${e.isBuiltin?``:`
1512
- <button type="button" class="snippet-action-btn delete snip-delete-btn" title="Delete Snippet">
1513
- <span class="material-symbols-outlined">delete</span>
1514
- </button>`}
1515
- </div>
1516
- </div>
1517
- </div>
1518
- `}).join(``),O.querySelectorAll(`.snippet-card`).forEach(n=>{let r=n.getAttribute(`data-id`),i=P.find(e=>e.id===r);i&&(n.querySelector(`.snip-run-btn`)?.addEventListener(`click`,()=>{ke(i.sql).length>0?Me({snippet:i,onExecute:n=>{Pe(n,e,t)}}):Pe(i.sql,e,t)}),n.querySelector(`.snip-insert-btn`)?.addEventListener(`click`,()=>{e.value=i.sql,m(),e.focus({preventScroll:!0})}),n.querySelector(`.snip-edit-btn`)?.addEventListener(`click`,()=>{je({snippetId:i.id,defaultTitle:i.title,defaultSql:i.sql,defaultDescription:i.description,defaultTags:i.tags||[],onSaved:()=>F()})}),n.querySelector(`.snip-delete-btn`)?.addEventListener(`click`,async()=>{if(confirm(`Are you sure you want to delete snippet "${i.title}"?`)){let e=await x(i.id);e.success?(window.showToast&&window.showToast(`Snippet deleted`,`info`),F()):alert(`Failed to delete snippet: ${e.error}`)}}))})};A&&D&&(A.onclick=()=>{D.classList.contains(`hidden`)?(D.classList.remove(`hidden`),F()):D.classList.add(`hidden`)}),M&&D&&(M.onclick=()=>{D.classList.add(`hidden`)}),j&&(j.onclick=()=>{je({defaultSql:e.value,onSaved:()=>{D&&!D.classList.contains(`hidden`)&&F()}})}),N&&(N.onclick=()=>{je({defaultSql:``,onSaved:()=>F()})}),k&&k.addEventListener(`input`,I),window.addEventListener(`drixio-snippets-updated`,()=>{D&&!D.classList.contains(`hidden`)&&F()});let L=document.querySelector(`.console-resizer-handle`),R=document.getElementById(`console-input-pane`);if(L&&R){let e=!1,t=0,n=0,r=r=>{r.button===0&&(e=!0,t=r.clientY,n=R.getBoundingClientRect().height,L.classList.add(`is-dragging`),document.body.style.userSelect=`none`,document.body.style.cursor=`row-resize`,window.addEventListener(`mousemove`,i),window.addEventListener(`mouseup`,a))},i=r=>{if(!e)return;let i=t-r.clientY,a=n+i,o=Math.max(140,window.innerHeight*.75);a=Math.max(140,Math.min(a,o)),R.style.height=`${a}px`},a=()=>{if(!e)return;e=!1,L.classList.remove(`is-dragging`),document.body.style.userSelect=``,document.body.style.cursor=``,window.removeEventListener(`mousemove`,i),window.removeEventListener(`mouseup`,a);let t=R.getBoundingClientRect().height;localStorage.setItem(`drixio_console_pane_height`,Math.round(t).toString())};L.addEventListener(`mousedown`,r)}let z=document.getElementById(`console-expand-editor-btn`),ee=document.getElementById(`console-expand-btn-icon`),te=()=>{if(!R)return;let t=R.classList.toggle(`is-fullscreen`);ee&&(ee.textContent=t?`close_fullscreen`:`open_in_full`),z&&(z.title=t?`Exit Fullscreen (Esc)`:`Toggle Fullscreen Editor (Esc to exit)`),t?(R.dataset.prevHeight=R.style.height,R.style.height=``):R.dataset.prevHeight&&(R.style.height=R.dataset.prevHeight),setTimeout(()=>{e.focus({preventScroll:!0}),m()},50)};z&&(z.onclick=te);let ne=document.querySelector(`.sql-editor-wrapper`);ne&&ne.addEventListener(`click`,t=>{!t.target.closest(`#console-expand-editor-btn`)&&!t.target.closest(`#console-autocomplete-popup`)&&e.focus({preventScroll:!0})}),window.addEventListener(`keydown`,e=>{e.key===`Escape`&&R?.classList.contains(`is-fullscreen`)&&(e.preventDefault(),te())})};function Ie(e,t,n){document.querySelectorAll(`.table-btn`).forEach(e=>e.classList.remove(`active`)),t&&t.classList.add(`active`);let r=document.getElementById(`table-name`);r&&(r.textContent=`SQL Console`),n.innerHTML=`
1519
- <div class="sql-console-wrapper">
1520
- <div class="console-main-pane">
1521
- <div id="console-history-pane">
1522
- <div id="console-welcome-msg">
1523
- <div class="console-welcome-icon-box">
1524
- <span class="material-symbols-outlined">terminal</span>
1525
- </div>
1526
- <h2 class="console-welcome-heading">Drixio SQL Terminal</h2>
1527
- <p class="console-welcome-sub">Execute arbitrary queries with real-time profiling, syntax highlighting, and CSV export.</p>
1528
-
1529
- <div class="console-shortcuts-hint">
1530
- <span class="shortcut-pill"><kbd>Ctrl</kbd> + <kbd>Enter</kbd> to execute</span>
1531
- <span class="shortcut-pill"><kbd>&uarr;</kbd> <kbd>&darr;</kbd> history navigation</span>
1532
- <span class="shortcut-pill">End with <kbd>;</kbd> + <kbd>Enter</kbd></span>
1533
- </div>
1534
-
1535
- <div class="console-quick-templates">
1536
- <span class="quick-template-label">Quick Queries:</span>
1537
- <button type="button" class="template-chip" data-sql="CREATE DATABASE drixio.sqlite;">Create SQLite DB</button>
1538
- <button type="button" class="template-chip" data-sql="CONNECT file:./drixio.sqlite;">Connect SQLite</button>
1539
- <button type="button" class="template-chip" data-sql="SELECT * FROM sqlite_master WHERE type='table';">Show Tables</button>
1540
- <button type="button" class="template-chip" data-sql="SELECT 1 + 1 as ping;">Ping Database</button>
1541
- </div>
1542
- </div>
1543
- </div>
1544
-
1545
- <!-- Drag Resizer Bar -->
1546
- <div id="console-resizer" class="console-resizer">
1547
- <div class="console-resizer-handle" title="Drag to adjust editor height">
1548
- <div class="console-resizer-pill"></div>
1549
- </div>
1550
- </div>
1551
-
1552
- <div id="console-input-pane">
1553
- <div class="console-editor-toolbar">
1554
- <div class="editor-toolbar-left">
1555
- <span class="editor-mode-tag">
1556
- <span class="material-symbols-outlined" style="font-size: 14px;">code</span>
1557
- SQL
1558
- </span>
1559
- <button type="button" id="console-safe-toggle-btn" class="console-safe-toggle-btn active" title="Safe Mode: Destructive queries will prompt for confirmation">
1560
- <span class="safe-indicator-dot"></span>
1561
- <span id="console-safe-text">Safe Mode ON</span>
1562
- </button>
1563
- <span class="editor-shortcut-info">Press <b>Ctrl+Enter</b> to run</span>
1564
- </div>
1565
- <div class="editor-toolbar-right">
1566
- <button type="button" id="console-snippets-toggle-btn" class="header-btn secondary" style="height: 28px; padding: 0 10px; font-size: 12px; gap: 5px;" title="Toggle Saved Queries & Snippets Library">
1567
- <span class="material-symbols-outlined" style="font-size: 15px; color: #f59e0b;">bookmark</span>
1568
- <span>Snippets</span>
1569
- </button>
1570
- <button type="button" id="console-save-snippet-btn" class="header-btn secondary" style="height: 28px; padding: 0 10px; font-size: 12px; gap: 5px;" title="Save current editor SQL as snippet">
1571
- <span class="material-symbols-outlined" style="font-size: 15px; color: var(--color-primary);">bookmark_add</span>
1572
- <span>Save</span>
1573
- </button>
1574
- <button type="button" id="console-clear-editor-btn" class="header-btn secondary" style="height: 28px; padding: 0 10px; font-size: 12px;" title="Clear Editor">
1575
- <span class="material-symbols-outlined" style="font-size: 14px;">backspace</span>
1576
- Clear
1577
- </button>
1578
- <button type="button" id="run-sql-btn" class="header-btn primary" style="height: 28px; padding: 0 12px; font-size: 12px;" title="Run SQL Query (Ctrl+Enter)">
1579
- <span class="material-symbols-outlined" style="font-size: 15px;">play_arrow</span>
1580
- <span id="run-sql-btn-text">Run</span>
1581
- </button>
1582
- </div>
1583
- </div>
1584
-
1585
- <div class="sql-editor-wrapper">
1586
- <div class="sql-editor-layer-container">
1587
- <!-- Autocomplete Floating Popup (VS Code style) -->
1588
- <div id="console-autocomplete-popup" class="console-autocomplete-popup hidden"></div>
1589
- <!-- Syntax Highlight Layer (Background) -->
1590
- <div id="sql-highlight-layer" class="sql-editor"></div>
1591
- <!-- Real Textarea (Foreground) -->
1592
- <textarea id="sql-editor" class="sql-editor" spellcheck="false" autocomplete="off" autocorrect="off" autocapitalize="off" data-gramm="false" data-gramm_editor="false" data-enable-grammarly="false"></textarea>
1593
- <!-- Right-Bottom Floating Expand Button -->
1594
- <button type="button" id="console-expand-editor-btn" class="console-editor-expand-btn" title="Toggle Fullscreen Editor (Esc to exit)">
1595
- <span class="material-symbols-outlined" id="console-expand-btn-icon" style="font-size: 14px;">open_in_full</span>
1596
- </button>
1597
- </div>
1598
- </div>
1599
- </div>
1600
- </div>
1601
-
1602
- <!-- Collapsible Snippets Drawer (Side-by-side Push Panel) -->
1603
- <div id="console-snippets-drawer" class="console-snippets-drawer hidden">
1604
- <div class="snippets-drawer-header">
1605
- <div class="flex items-center gap-2">
1606
- <span class="material-symbols-outlined" style="font-size: 18px; color: var(--color-primary);">bookmarks</span>
1607
- <span class="font-semibold text-13">Saved Queries</span>
1608
- </div>
1609
- <div class="flex items-center gap-1">
1610
- <button type="button" id="snippets-drawer-new-btn" class="snippet-btn primary" title="Create New Snippet">
1611
- <span class="material-symbols-outlined" style="font-size: 14px;">add</span>
1612
- <span>New</span>
1613
- </button>
1614
- <button type="button" id="close-snippets-drawer-btn" class="snippet-action-btn" title="Close Drawer">
1615
- <span class="material-symbols-outlined" style="font-size: 16px;">close</span>
1616
- </button>
1617
- </div>
1618
- </div>
1619
-
1620
- <div class="snippets-drawer-search">
1621
- <div class="snippets-search-box">
1622
- <span class="material-symbols-outlined snippets-search-icon">search</span>
1623
- <input type="text" id="snippets-search-input" class="snippets-search-input" placeholder="Search snippets..." />
1624
- </div>
1625
- </div>
1626
-
1627
- <div id="snippets-list-container" class="snippets-list-container">
1628
- <div class="snippets-empty-state">
1629
- <span class="material-symbols-outlined">hourglass_empty</span>
1630
- <span>Loading snippets...</span>
1631
- </div>
1632
- </div>
1633
- </div>
1634
- </div>
1635
- `;let i=document.getElementById(`sql-editor`),a=document.getElementById(`console-history-pane`);window.AppState.lastQuery&&(i.value=window.AppState.lastQuery);let o=localStorage.getItem(`drixio_console_pane_height`)||localStorage.getItem(`drixio_console_editor_height`),s=n.querySelector(`#console-input-pane`);if(o&&s){let e=parseInt(o,10);!isNaN(e)&&e>=140&&(s.style.height=`${e}px`)}let c=document.querySelector(`main`);c&&(c.scrollTop=0),window.scrollTo(0,0),i.focus({preventScroll:!0}),Fe(i,a)}var G={erdData:[],undoStack:[],selectedTables:new Set,clipboard:[],draggingNode:null,draggingColInfo:null,draggedNodesStart:[],dragStartMouse:{x:0,y:0},targetColForDelete:null,zoom:1,isPanning:!1,panStart:{x:0,y:0},scrollStart:{left:0,top:0},drawingFkStartCol:null,drawingFkStartCoords:null,contextMenuPos:{x:0,y:0},snapToGrid:!0,pushUndoState(){this.undoStack.push(JSON.parse(JSON.stringify(this.erdData))),this.undoStack.length>50&&this.undoStack.shift()},undo(){return this.undoStack.length>0?(this.erdData=this.undoStack.pop(),!0):!1},clearSelection(){this.selectedTables.clear()},toggleSelection(e){this.selectedTables.has(e)?this.selectedTables.delete(e):this.selectedTables.add(e)},setSelection(e){this.selectedTables.clear(),this.selectedTables.add(e)}};async function Le(){let e=await r();if(!e||!e.success||!e.data)return[];let t=e.data,n=[],i=t.map(async e=>{let t=await c(e);t&&t.success&&n.push({table:e,columns:t.data})});return await Promise.all(i),n}function K(){try{let e=localStorage.getItem(`drixio-erd-positions`);return e?JSON.parse(e):{}}catch{return{}}}function Re(e){localStorage.setItem(`drixio-erd-positions`,JSON.stringify(e))}async function ze(e){if(!e)return!0;try{for(let t of e)if(t.isDraft){let e=await f(t.table,{columns:t.columns});if(!e.success)throw Error(e.error||`Failed to create table`)}else{let e=t.columns.filter(e=>e.isDraft);if(e.length>0){let n=await f(t.table,{pendingInserts:e});if(!n.success)throw Error(n.error||`Failed to add column`)}}return!0}catch(e){return window.showToast?.(`Failed to save: `+e.message,`error`),!1}}function Be(e){if(!e)return 0;let t=0;return e.forEach(e=>{e.isDraft?t++:e.columns.forEach(e=>{e.isDraft&&t++})}),t}function Ve(e){if(!e||e.length===0)return;let t={};e.forEach(e=>t[e.table]=new Set),e.forEach(e=>{e.columns.forEach(n=>{n.fkTarget&&n.fkTarget.table&&t[e.table]&&n.fkTarget.table!==e.table&&t[e.table].add(n.fkTarget.table)})});let n={};e.forEach(e=>n[e.table]=0);let r=!0,i=0;for(;r&&i<e.length+5;){r=!1,i++;for(let i of e){let e=n[i.table];for(let a of t[i.table])n[a]!==void 0&&n[a]>=e&&(n[i.table]=n[a]+1,r=!0)}}let a={};for(let t of e){let e=n[t.table]||0;a[e]||(a[e]=[]),a[e].push(t.table)}let o=K()||{};return Object.keys(a).forEach(e=>{let t=parseInt(e);a[e].forEach((e,n)=>{o[e]={x:600+t*360,y:400+n*260}})}),Re(o),o}function He(){if(G.selectedTables.size===0)return;let e=[];G.selectedTables.forEach(t=>{let n=G.erdData.find(e=>e.table===t);if(n){let t=JSON.parse(JSON.stringify(n));t.columns.forEach(e=>{e.fkTarget&&delete e.fkTarget}),e.push(t)}}),G.clipboard=e,window.showToast(`Copied ${e.length} table(s)`,`success`)}function Ue(){G.selectedTables.size!==0&&(G.pushUndoState(),G.erdData.forEach(e=>{e.columns.forEach(e=>{e.fkTarget&&G.selectedTables.has(e.fkTarget.table)&&delete e.fkTarget})}),G.erdData=G.erdData.filter(e=>!G.selectedTables.has(e.table)),G.clearSelection(),J(),window.showToast(`Deleted selected table(s)`,`success`))}function We(){if(!G.clipboard||G.clipboard.length===0)return;G.pushUndoState(),G.clearSelection();let e=K();G.clipboard.forEach((t,n)=>{let r=JSON.parse(JSON.stringify(t)),i=`_copy`,a=r.table+i,o=1;for(;G.erdData.some(e=>e.table===a);)o++,a=r.table+i+o;r.table=a,r.isDraft=!0,r.columns.forEach(e=>e.isDraft=!0),e[t.table]?e[a]={x:e[t.table].x+40+n*20,y:e[t.table].y+40+n*20}:e[a]={x:100+n*20,y:100+n*20},G.erdData.push(r),G.selectedTables.add(a)}),Re(e),J(),window.showToast(`Pasted ${G.clipboard.length} table(s)`,`success`)}function Ge(){document.addEventListener(`keydown`,async e=>{window.AppState&&window.AppState.currentTab!==`erd-btn`||e.target.tagName===`INPUT`||e.target.tagName===`SELECT`||e.target.tagName===`TEXTAREA`||((e.ctrlKey||e.metaKey)&&e.key===`s`&&(e.preventDefault(),await ze(G.erdData)&&(window.showToast(`ERD Schema saved successfully!`,`success`),st(document.getElementById(`main-content`)))),(e.ctrlKey||e.metaKey)&&e.key===`z`&&(e.preventDefault(),G.undo()?(J(),window.showToast(`Undid last action`,`success`)):window.showToast(`Nothing to undo`,`info`)),e.ctrlKey&&e.key===`c`&&(e.preventDefault(),He()),e.ctrlKey&&e.key===`v`&&(e.preventDefault(),We()),e.key===`Delete`&&(e.preventDefault(),Ue()))})}function Ke(){let e=document.getElementById(`erd-workspace`);document.querySelectorAll(`.erd-node`).forEach(t=>{let n=G.erdData.find(e=>e.table===t.dataset.table);if(!n)return;let r=t.querySelector(`.erd-node-header`);if(r.addEventListener(`mousedown`,r=>{if(r.button===1||r.target.tagName===`INPUT`)return;if(!G.selectedTables.has(n.table))!r.ctrlKey&&!r.metaKey&&G.clearSelection(),G.selectedTables.add(n.table),document.querySelectorAll(`.erd-node`).forEach(e=>{G.selectedTables.has(e.dataset.table)?e.classList.add(`is-selected`):e.classList.remove(`is-selected`)});else if(r.ctrlKey||r.metaKey){G.selectedTables.delete(n.table),t.classList.remove(`is-selected`);return}G.draggingNode=t;let i=e.getBoundingClientRect();G.dragStartMouse={x:r.clientX,y:r.clientY},G.draggedNodesStart=[],G.selectedTables.forEach(e=>{let t=document.querySelector(`.erd-node[data-table="${e}"]`);if(t){let e=t.getBoundingClientRect();G.draggedNodesStart.push({node:t,startX:(e.left-i.left)/G.zoom,startY:(e.top-i.top)/G.zoom}),t.style.zIndex=`10`}}),document.body.style.cursor=`grabbing`}),t.addEventListener(`contextmenu`,e=>{e.preventDefault(),e.stopPropagation(),G.selectedTables.has(n.table)||(G.setSelection(n.table),document.querySelectorAll(`.erd-node`).forEach(e=>{G.selectedTables.has(e.dataset.table)?e.classList.add(`is-selected`):e.classList.remove(`is-selected`)})),w(e,[{label:`Copy`,icon:`content_copy`,action:He},{label:`Cut`,icon:`content_cut`,action:()=>{He(),Ue()}},`divider`,{label:`Delete`,icon:`delete`,danger:!0,action:Ue}])}),t.querySelector(`.erd-add-col-btn`).addEventListener(`click`,()=>{G.pushUndoState(),n.columns.push({name:`new_col_${n.columns.length}`,type:`TEXT`,isDraft:!0,nullable:!0}),J()}),n.isDraft){let e=r.querySelector(`.erd-node-name`);r.addEventListener(`dblclick`,t=>{if(t.target.tagName===`INPUT`)return;t.stopPropagation();let r=document.createElement(`input`);r.value=n.table,r.className=`erd-inline-input`,e.replaceWith(r),r.focus(),r.addEventListener(`blur`,()=>{let e=n.table,t=r.value.trim()||e;if(e!==t){G.pushUndoState(),n.table=t;let r=K();r[e]&&(r[t]=r[e],delete r[e],Re(r)),G.erdData.forEach(n=>{n.columns.forEach(n=>{n.fkTarget&&n.fkTarget.table===e&&(n.fkTarget.table=t)})}),G.selectedTables.has(e)&&(G.selectedTables.delete(e),G.selectedTables.add(t))}J()}),r.addEventListener(`keydown`,e=>{e.key===`Enter`&&r.blur()})})}else r.addEventListener(`dblclick`,e=>{e.stopPropagation(),window.AppState.currentTable=t.dataset.table;let n=document.querySelector(`.table-btn[data-table="${t.dataset.table}"]`);n&&(window.AppState.currentTableBtnElement=n,document.querySelectorAll(`.table-btn`).forEach(e=>e.classList.remove(`active`)),n.classList.add(`active`)),window.handleSwitchTab?window.handleSwitchTab(`schema-btn`):window.renderCurrentView&&(window.AppState.currentTab=`schema-btn`,window.renderCurrentView())});let i=t.querySelectorAll(`.erd-column`);i.forEach(t=>{let r=parseInt(t.dataset.index),i=n.columns[r];if(t.addEventListener(`contextmenu`,e=>{i.isPk||!i.isDraft&&!n.isDraft||(e.preventDefault(),e.stopPropagation(),w(e,[{label:`Delete Column`,icon:`delete`,danger:!0,action:()=>{G.pushUndoState(),G.erdData.forEach(e=>{e.columns.forEach(e=>{e.fkTarget&&e.fkTarget.table===n.table&&e.fkTarget.column===i.name&&delete e.fkTarget})}),n.columns.splice(r,1),J()}}]))}),!i.isDraft&&!n.isDraft)return;t.querySelectorAll(`.editable-field`).forEach(e=>{e.addEventListener(`dblclick`,t=>{if(t.target.tagName===`INPUT`||t.target.tagName===`SELECT`)return;t.stopPropagation();let r=e.dataset.field,a;if(r===`type`){a=document.createElement(`select`),a.className=`erd-inline-input`;let e=[`INTEGER`,`TEXT`,`REAL`,`BLOB`,`BOOLEAN`,`DATE`,`DATETIME`];if(e.forEach(e=>{let t=document.createElement(`option`);t.value=e,t.textContent=e,i.type.toUpperCase()===e&&(t.selected=!0),a.appendChild(t)}),!e.includes(i.type.toUpperCase())){let e=document.createElement(`option`);e.value=i.type,e.textContent=i.type,e.selected=!0,a.appendChild(e)}a.addEventListener(`change`,()=>{a.blur()})}else a=document.createElement(`input`),a.value=r===`name`&&i.nullable?i.name+` ?`:i[r],a.className=`erd-inline-input`;e.replaceWith(a),a.focus(),a.addEventListener(`blur`,()=>{let e=i[r],t=r===`type`?a.value.toUpperCase():a.value;if(t=t.trim()||e,e===t&&!t.endsWith(`?`)&&!t.endsWith(`!`)){J();return}G.pushUndoState(),r===`name`&&(t.endsWith(`?`)?(i.nullable=!0,t=t.slice(0,-1).trim()):(i.nullable=!1,t.endsWith(`!`)&&(t=t.slice(0,-1).trim()))),r===`name`&&i.isPk&&e!==t&&G.erdData.forEach(r=>{r.columns.forEach(r=>{r.fkTarget&&r.fkTarget.table===n.table&&r.fkTarget.column===e&&(r.fkTarget.column=t)})}),i[r]=t,J()}),a.addEventListener(`keydown`,e=>{e.key===`Enter`&&a.blur()})})});let a=t.querySelector(`.erd-drag-handle`);a&&a.addEventListener(`mousedown`,t=>{t.stopPropagation(),G.drawingFkStartCol={table:n.table,col:i.name,colIndex:r};let o=a.getBoundingClientRect(),s=e.getBoundingClientRect();G.drawingFkStartCoords={x:(o.right-s.left)/G.zoom,y:(o.top+o.height/2-s.top)/G.zoom}})}),t.querySelectorAll(`.erd-reorder-handle`).forEach(e=>{e.addEventListener(`dragstart`,t=>{let r=e.closest(`.erd-column`);G.draggingColInfo={table:n.table,colIndex:parseInt(r.dataset.index)},t.dataTransfer.effectAllowed=`move`,t.dataTransfer.setData(`text/plain`,n.table)})}),i.forEach(e=>{e.addEventListener(`dragover`,e=>{!G.draggingColInfo||G.draggingColInfo.table!==n.table||(e.preventDefault(),e.dataTransfer.dropEffect=`move`)}),e.addEventListener(`drop`,t=>{if(t.preventDefault(),!G.draggingColInfo||G.draggingColInfo.table!==n.table)return;let r=G.draggingColInfo.colIndex,i=parseInt(e.dataset.index);if(r!==i){G.pushUndoState();let[e]=n.columns.splice(r,1);n.columns.splice(i,0,e),J()}G.draggingColInfo=null})})})}var qe=!1;function Je(){qe||(qe=!0,document.addEventListener(`mousemove`,e=>{if(G.drawingFkStartCol){let t=document.getElementById(`erd-workspace`);if(!t)return;let n=t.getBoundingClientRect(),r=(e.clientX-n.left)/G.zoom,i=(e.clientY-n.top)/G.zoom,a=document.getElementById(`erd-temp-path`),o=document.getElementById(`erd-svg-layer`);a||(a=document.createElementNS(`http://www.w3.org/2000/svg`,`path`),a.id=`erd-temp-path`,a.setAttribute(`fill`,`none`),a.setAttribute(`stroke`,`var(--color-primary)`),a.setAttribute(`stroke-width`,`2`),a.setAttribute(`stroke-dasharray`,`5,5`),o&&o.appendChild(a)),a&&a.setAttribute(`d`,`M ${G.drawingFkStartCoords.x} ${G.drawingFkStartCoords.y} L ${r} ${i}`);return}if(!G.draggingNode)return;let t=(e.clientX-G.dragStartMouse.x)/G.zoom,n=(e.clientY-G.dragStartMouse.y)/G.zoom;G.draggedNodesStart.forEach(e=>{let r=e.startX+t,i=e.startY+n;r<0&&(r=0),i<0&&(i=0),G.snapToGrid&&(r=Math.round(r/20)*20,i=Math.round(i/20)*20),e.node.style.left=`${r}px`,e.node.style.top=`${i}px`}),rt()}),document.addEventListener(`mouseup`,e=>{if(G.drawingFkStartCol){let t=document.elementFromPoint(e.clientX,e.clientY),n=t?t.closest(`.erd-node`):null;if(n){let e=n.dataset.table;if(e!==G.drawingFkStartCol.table){let t=G.erdData.find(t=>t.table===e).columns.find(e=>e.isPk);if(t){let n=confirm(`Create Foreign Key to ${e}?\n\nClick OK for Nullable (0..N)\nClick Cancel for Not Nullable (1..N)`);G.pushUndoState();let r=G.erdData.find(e=>e.table===G.drawingFkStartCol.table),i=r.columns[G.drawingFkStartCol.colIndex];i.fkTarget={table:e,column:t.name},i.nullable=n,r.columns.splice(G.drawingFkStartCol.colIndex,1),r.columns.push(i),J()}else window.showToast(`Target table has no Primary Key!`,`error`)}}else{let e=G.erdData.find(e=>e.table===G.drawingFkStartCol.table).columns[G.drawingFkStartCol.colIndex];e.fkTarget&&(G.pushUndoState(),delete e.fkTarget,J())}let r=document.getElementById(`erd-temp-path`);r&&r.remove(),G.drawingFkStartCol=null}if(G.draggingNode){document.body.style.cursor=``;let e=K();G.draggedNodesStart.forEach(t=>{t.node.style.zIndex=``,e[t.node.dataset.table]={x:parseFloat(t.node.style.left),y:parseFloat(t.node.style.top)}}),Re(e),at(),G.draggingNode=null,G.draggedNodesStart=[]}}))}var Ye=!1,Xe={x:0,y:0},q=null;function Ze(e){let t=()=>{if(!e)return;let t=20*G.zoom,n=Math.max(1,1.25*G.zoom);e.style.backgroundSize=`${t}px ${t}px`,e.style.backgroundImage=`radial-gradient(var(--color-border) ${n}px, transparent ${n}px)`,e.style.backgroundPosition=`${-(e.scrollLeft%t)}px ${-(e.scrollTop%t)}px`};e.addEventListener(`scroll`,()=>{at(),t()}),e.addEventListener(`mousedown`,t=>{t.button===1||t.button===0&&t.altKey?(t.preventDefault(),G.isPanning=!0,G.panStart.x=t.clientX,G.panStart.y=t.clientY,G.scrollStart.left=e.scrollLeft,G.scrollStart.top=e.scrollTop,document.body.style.cursor=`grabbing`):t.button===0&&(t.target.id===`erd-workspace`||t.target.id===`erd-svg-layer`||t.target.id===`erd-nodes-layer`||t.target.id===`erd-wrapper`)&&(Ye=!0,Xe={x:t.clientX,y:t.clientY},!t.ctrlKey&&!t.metaKey&&(G.clearSelection(),J()),q=document.createElement(`div`),q.style.position=`fixed`,q.style.border=`1px solid var(--color-primary)`,q.style.backgroundColor=`rgba(59, 130, 246, 0.12)`,q.style.borderRadius=`4px`,q.style.zIndex=`1000`,q.style.pointerEvents=`none`,document.body.appendChild(q))});let n=()=>{let e=document.getElementById(`btn-erd-zoom-reset`);e&&(e.textContent=Math.round(G.zoom*100)+`%`)},r=e=>{G.zoom=Math.max(.2,Math.min(e,2.5));let r=document.getElementById(`erd-workspace`);r&&(r.style.transform=`scale(${G.zoom})`,rt(),at(),n(),t())},i=document.getElementById(`btn-erd-zoom-in`);i&&(i.onclick=()=>r(G.zoom+.15));let a=document.getElementById(`btn-erd-zoom-out`);a&&(a.onclick=()=>r(G.zoom-.15));let o=document.getElementById(`btn-erd-zoom-reset`);o&&(o.onclick=()=>r(1));let s=()=>{let n=document.querySelectorAll(`.erd-node`);if(!n||n.length===0)return;let i=1/0,a=1/0,o=-1/0,s=-1/0;if(n.forEach(e=>{let t=parseFloat(e.style.left)||0,n=parseFloat(e.style.top)||0,r=e.offsetWidth||290,c=e.offsetHeight||150;t<i&&(i=t),n<a&&(a=n),t+r>o&&(o=t+r),n+c>s&&(s=n+c)}),i===1/0)return;let c=o-i+240,l=s-a+240,u=e.clientWidth,d=e.clientHeight,f=u/c,p=d/l,m=Math.min(1.1,Math.max(.35,Math.min(f,p)));r(m);let h=(i+o)/2,g=(a+s)/2;e.scrollLeft=h*m-u/2,e.scrollTop=g*m-d/2,t()},c=document.getElementById(`btn-erd-fit-view`);c&&(c.onclick=s);let l=document.getElementById(`btn-erd-toggle-minimap`),u=document.getElementById(`btn-erd-close-minimap`),d=document.getElementById(`erd-minimap-container`),f=e=>{if(!d)return;let t=d.classList.contains(`hidden`)||d.style.display===`none`;(e===void 0?t:e)?(d.classList.remove(`hidden`),d.style.display=`block`,l&&l.classList.add(`active`),at()):(d.classList.add(`hidden`),d.style.display=`none`,l&&l.classList.remove(`active`))};l&&(l.onclick=()=>f()),u&&(u.onclick=e=>{e.stopPropagation(),f(!1)});let p=document.getElementById(`btn-erd-add-table`);p&&(p.onclick=()=>{let t=e.scrollLeft/G.zoom,n=e.scrollTop/G.zoom,r=e.clientWidth/G.zoom,i=e.clientHeight/G.zoom;ot(Math.max(60,t+r/2-150),Math.max(60,n+i/2-100)),window.showToast(`Created new table draft`,`success`)});let m=document.getElementById(`btn-erd-auto-layout`);m&&(m.onclick=()=>{G.pushUndoState(),Ve(G.erdData),J(),setTimeout(s,50),window.showToast(`Auto Layout Applied`,`success`)});let h=document.getElementById(`btn-erd-snap-grid`);h&&(h.onclick=()=>{G.snapToGrid=!G.snapToGrid,G.snapToGrid?(h.classList.add(`active`),window.showToast(`Grid Snapping: ON (20px)`,`info`)):(h.classList.remove(`active`),window.showToast(`Grid Snapping: OFF (Free)`,`info`))});let g=document.getElementById(`btn-erd-save-drafts`);g&&(g.onclick=async()=>{await ze(G.erdData)&&(window.showToast(`ERD Schema saved successfully!`,`success`),st(document.getElementById(`main-content`)))}),e.addEventListener(`wheel`,e=>{(e.ctrlKey||e.metaKey)&&(e.preventDefault(),r(G.zoom-e.deltaY*.001))},{passive:!1}),document.addEventListener(`mousemove`,n=>{if(Ye&&q){let e=Math.min(n.clientX,Xe.x),t=Math.min(n.clientY,Xe.y),r=Math.abs(n.clientX-Xe.x),i=Math.abs(n.clientY-Xe.y);q.style.left=e+`px`,q.style.top=t+`px`,q.style.width=r+`px`,q.style.height=i+`px`;return}if(G.isPanning){if(e){let r=n.clientX-G.panStart.x,i=n.clientY-G.panStart.y;e.scrollLeft=G.scrollStart.left-r,e.scrollTop=G.scrollStart.top-i,t()}return}}),document.addEventListener(`mouseup`,e=>{if(Ye&&(Ye=!1,q)){let e=q.getBoundingClientRect();q.remove(),q=null;let t=document.querySelectorAll(`.erd-node`),n=!1;t.forEach(t=>{let r=t.getBoundingClientRect();e.right<r.left||e.left>r.right||e.bottom<r.top||e.top>r.bottom||(G.selectedTables.add(t.dataset.table),n=!0)}),n&&J()}G.isPanning&&(G.isPanning=!1,document.body.style.cursor=``)}),e.addEventListener(`contextmenu`,e=>{(e.target.id===`erd-workspace`||e.target.id===`erd-svg-layer`||e.target.id===`erd-nodes-layer`||e.target.id===`erd-wrapper`)&&(e.preventDefault(),e.stopPropagation(),w(e,[{label:`Add Table`,icon:`add_box`,action:()=>{p&&p.click()}},`divider`,{label:`Auto Layout`,icon:`account_tree`,action:()=>{m&&m.click()}}]))});let _=document.getElementById(`erd-search-input`),v=document.getElementById(`erd-search-results`);if(_&&v){_.addEventListener(`input`,n=>{let r=n.target.value.toLowerCase().trim();if(v.innerHTML=``,!r){v.classList.add(`hidden`);return}let i=G.erdData.filter(e=>e.table.toLowerCase().includes(r));if(i.length===0){v.innerHTML=`<div class="erd-search-item" style="color: var(--color-text-soft);">No tables found</div>`,v.classList.remove(`hidden`);return}i.forEach(n=>{let r=document.createElement(`div`);r.className=`erd-search-item`,r.innerHTML=`
1636
- <span class="material-symbols-outlined icon-16" style="color: var(--color-primary);">table_chart</span>
1637
- <span>${n.table}</span>
1638
- <span class="erd-search-col-badge">${n.columns.length} cols</span>
1639
- `,r.onclick=()=>{G.setSelection(n.table);let r=document.querySelector(`.erd-node[data-table="${n.table}"]`);if(r){let n=r.getBoundingClientRect(),i=e.getBoundingClientRect(),a=parseFloat(r.style.left),o=parseFloat(r.style.top);e.scrollLeft=a*G.zoom-i.width/2+n.width/2,e.scrollTop=o*G.zoom-i.height/2+n.height/2,t()}J(),v.classList.add(`hidden`),_.value=``},v.appendChild(r)}),v.classList.remove(`hidden`)}),document.addEventListener(`click`,e=>{!_.contains(e.target)&&!v.contains(e.target)&&v.classList.add(`hidden`)});let n=document.querySelector(`.erd-search-shortcut`);n&&(n.onclick=()=>_.focus(),n.style.cursor=`pointer`)}e.addEventListener(`mouseover`,e=>{let t=e.target.closest(`.erd-relationship-path`);if(t){t.classList.add(`is-hovered`);let e=t.dataset.fromTable,n=t.dataset.toTable;document.querySelector(`.erd-node[data-table="${e}"]`)?.classList.add(`is-relationship-highlight`),document.querySelector(`.erd-node[data-table="${n}"]`)?.classList.add(`is-relationship-highlight`);return}let n=e.target.closest(`.erd-column.is-fk`);if(n){let e=n.closest(`.erd-node`),t=n.dataset.col;if(e){let n=document.querySelector(`.erd-relationship-path[data-from-table="${e.dataset.table}"][data-from-col="${t}"]`);n&&n.classList.add(`is-hovered`)}}}),e.addEventListener(`mouseout`,e=>{let t=e.target.closest(`.erd-relationship-path`);if(t){t.classList.remove(`is-hovered`),document.querySelectorAll(`.erd-node.is-relationship-highlight`).forEach(e=>e.classList.remove(`is-relationship-highlight`));return}e.target.closest(`.erd-column.is-fk`)&&document.querySelectorAll(`.erd-relationship-path.is-hovered`).forEach(e=>e.classList.remove(`is-hovered`))}),t()}function Qe(e){let t=document.getElementById(`erd-minimap`);if(!t)return;let n=!1,r=n=>{let r=t.getBoundingClientRect(),i=n.clientX-r.left,a=n.clientY-r.top,{scale:o,offsetX:s,offsetY:c,minX:l,minY:u,vW:d,vH:f}=it(e,Array.from(document.querySelectorAll(`.erd-node`))),p=l+(i-s)/o,m=u+(a-c)/o;e.scrollLeft=(p-d/2)*G.zoom,e.scrollTop=(m-f/2)*G.zoom};t.addEventListener(`mousedown`,e=>{if(e.stopPropagation(),e.button!==0)return;n=!0,r(e);let t=e=>{n&&r(e)},i=()=>{n=!1,document.removeEventListener(`mousemove`,t),document.removeEventListener(`mouseup`,i)};document.addEventListener(`mousemove`,t),document.addEventListener(`mouseup`,i)})}var $e=!1;function et(){if($e)return;let e=document.getElementById(`erd-wrapper`);e&&(Ze(e),Qe(e),Ge(),Je(),$e=!0)}function tt(){let e=document.getElementById(`btn-erd-save-drafts`);if(!e)return;let t=Be(G.erdData),n=e.querySelector(`.erd-draft-count-badge`);t>0?(e.classList.remove(`disabled`),e.classList.add(`has-drafts`),n&&(n.textContent=t,n.style.display=`inline-flex`)):(e.classList.remove(`has-drafts`),n&&(n.style.display=`none`))}function J(){let e=document.getElementById(`erd-nodes-layer`);if(!e)return;if(e.innerHTML=``,!G.erdData||G.erdData.length===0){e.innerHTML=`
1640
- <div class="erd-empty-workspace">
1641
- <div class="erd-empty-icon-box">
1642
- <span class="material-symbols-outlined">account_tree</span>
1643
- </div>
1644
- <h3>No tables in this database</h3>
1645
- <p>Right-click the canvas or click "+ Add Table" above to create an entity.</p>
1646
- </div>
1647
- `,tt();return}let t=K(),n=600,r=400,i=0;G.erdData.forEach(a=>{let o,s;t[a.table]?(o=t[a.table].x,s=t[a.table].y):(o=n,s=r,n+=340,n>1800&&(n=600,r+=i+60,i=0));let c=nt(a,o,s);G.selectedTables.has(a.table)&&c.classList.add(`is-selected`),e.appendChild(c);let l=44+a.columns.length*34+36;l>i&&(i=l)}),Ke(),tt(),setTimeout(()=>{rt(),at()},10)}function nt(e,t,n){let r=document.createElement(`div`);r.className=`erd-node ${e.isDraft?`is-draft`:``}`,r.dataset.table=e.table,r.style.left=`${t}px`,r.style.top=`${n}px`;let i=e.columns.map((t,n)=>{let r=t.fkTarget?`is-fk`:``,i=t.isDraft?`is-draft`:``,a=t.type.toUpperCase();return`
1648
- <div class="erd-column ${r} ${i}" data-col="${t.name}" data-index="${n}">
1649
- <div class="erd-col-left">
1650
- <span class="erd-reorder-handle" draggable="true" title="Drag to reorder column">
1651
- ${t.isPk?`<span class="erd-badge-pk" title="Primary Key">PK</span>`:t.fkTarget?`<span class="erd-badge-fk" title="Foreign Key to ${t.fkTarget.table}.${t.fkTarget.column}">FK</span>`:`<span class="erd-col-bullet"></span>`}
1652
- </span>
1653
- <span class="erd-col-name editable-field ${t.isPk?`is-pk`:``}" data-field="name">${t.name}</span>
1654
- ${t.nullable?`<span class="erd-col-nullable" title="Nullable column">?</span>`:``}
1655
- </div>
1656
- <div class="erd-col-right">
1657
- <span class="erd-col-type editable-field" data-field="type">${a}</span>
1658
- ${(t.isDraft||e.isDraft)&&!t.isPk?`<div class="erd-drag-handle" title="Drag to create Foreign Key"></div>`:``}
1659
- </div>
1660
- </div>
1661
- `}).join(``);return r.innerHTML=`
1662
- <div class="erd-node-header" title="${e.isDraft?`Double-click to rename table`:`Double-click to open Schema view`}">
1663
- <div class="erd-node-header-left">
1664
- <div class="erd-node-icon-box">
1665
- <span class="material-symbols-outlined erd-node-icon">table_chart</span>
1666
- </div>
1667
- <span class="erd-node-name">${e.table}</span>
1668
- ${e.isDraft?`<span class="erd-badge-draft">DRAFT</span>`:``}
1669
- </div>
1670
- <div class="erd-node-header-right">
1671
- <span class="erd-col-count" title="${e.columns.length} columns">${e.columns.length}</span>
1672
- <span class="material-symbols-outlined erd-node-drag-handle">drag_indicator</span>
1673
- </div>
1674
- </div>
1675
- <div class="erd-node-body">
1676
- ${i}
1677
- </div>
1678
- <div class="erd-add-col-btn">
1679
- <span class="material-symbols-outlined" style="font-size: 15px;">add</span>
1680
- <span>Add Column</span>
1681
- </div>
1682
- `,r}function rt(){let e=document.getElementById(`erd-svg-layer`);if(!e)return;let t=``;G.erdData.forEach(e=>{let n=e.table,r=document.querySelector(`.erd-node[data-table="${n}"]`);r&&e.columns.forEach(e=>{if(e.fkTarget){let i=e.fkTarget.table,a=e.fkTarget.column,o=document.querySelector(`.erd-node[data-table="${i}"]`);if(!o)return;let s=r.querySelector(`.erd-column[data-col="${e.name}"]`),c=o.querySelector(`.erd-column[data-col="${a}"]`);if(!s||!c)return;let l=s.getBoundingClientRect(),u=c.getBoundingClientRect(),d=document.getElementById(`erd-workspace`).getBoundingClientRect(),f,p,m,h;l.right<u.left+20?(f=(l.right-d.left)/G.zoom,m=(u.left-d.left)/G.zoom):(f=(l.left-d.left)/G.zoom,m=(u.right-d.left)/G.zoom),p=(l.top+l.height/2-d.top)/G.zoom,h=(u.top+u.height/2-d.top)/G.zoom;let g=Math.abs(m-f),_=Math.max(g*.5,50),v=f<m?f+_:f-_,y=p,b=f<m?m-_:m+_,x=h,S=e.nullable?`url(#zero-or-one)`:`url(#one-and-only-one)`;t+=`
1683
- <path
1684
- class="erd-relationship-path"
1685
- d="M ${f} ${p} C ${v} ${y}, ${b} ${x}, ${m} ${h}"
1686
- fill="none"
1687
- marker-start="url(#crows-foot-zero-many)"
1688
- marker-end="${S}"
1689
- data-from-table="${n}"
1690
- data-from-col="${e.name}"
1691
- data-to-table="${i}"
1692
- data-to-col="${a}"
1693
- />
1694
- `}})}),e.innerHTML=`
1695
- <defs>
1696
- <!-- FK side: Zero or Many (0<) -->
1697
- <marker id="crows-foot-zero-many" viewBox="0 0 16 10" refX="16" refY="5" markerWidth="10" markerHeight="10" orient="auto-start-reverse">
1698
- <path d="M 16 0 L 8 5 M 16 10 L 8 5 M 16 5 L 8 5 M 4 5 L 0 5" fill="none" stroke="currentColor" stroke-width="1.5" />
1699
- <circle cx="5" cy="5" r="3" fill="var(--color-bg-primary)" stroke="currentColor" stroke-width="1.5" />
1700
- </marker>
1701
-
1702
- <!-- PK side: One and Only One (||) -->
1703
- <marker id="one-and-only-one" viewBox="0 0 16 10" refX="13" refY="5" markerWidth="10" markerHeight="10" orient="auto">
1704
- <path d="M 10 0 L 10 10 M 6 0 L 6 10" fill="none" stroke="currentColor" stroke-width="1.5" />
1705
- </marker>
1706
-
1707
- <!-- PK side: Zero or One (0|) -->
1708
- <marker id="zero-or-one" viewBox="0 0 16 10" refX="13" refY="5" markerWidth="10" markerHeight="10" orient="auto">
1709
- <path d="M 10 0 L 10 10 M 6 0 L 6 10" fill="none" stroke="currentColor" stroke-width="1.5" />
1710
- <circle cx="5" cy="5" r="3" fill="var(--color-bg-primary)" stroke="currentColor" stroke-width="1.5" />
1711
- </marker>
1712
- </defs>
1713
- ${t}
1714
- `}function it(e,t){if(!e||!t||t.length===0)return{mapW:180,mapH:120,scale:.1,offsetX:0,offsetY:0,minX:0,minY:0,vW:800,vH:600,vLeft:0,vTop:0};let n=e.clientWidth/G.zoom,r=e.clientHeight/G.zoom,i=e.scrollLeft/G.zoom,a=e.scrollTop/G.zoom,o=1/0,s=1/0,c=-1/0,l=-1/0;t.forEach(e=>{let t=parseFloat(e.style.left)||0,n=parseFloat(e.style.top)||0,r=e.offsetWidth||290,i=e.offsetHeight||150;t<o&&(o=t),n<s&&(s=n),t+r>c&&(c=t+r),n+i>l&&(l=n+i)}),o===1/0&&(o=0,s=0,c=800,l=600),o-=200,s-=200,c+=200,l+=200;let u=c-o,d=l-s,f=Math.max(u,n),p=Math.max(d,r),m=o+u/2,h=s+d/2,g=m-f/2,_=h-p/2,v=Math.min(180/f,120/p);return{mapW:180,mapH:120,scale:v,offsetX:(180-f*v)/2,offsetY:(120-p*v)/2,minX:g,minY:_,vW:n,vH:r,vLeft:i,vTop:a}}function at(){let e=document.getElementById(`erd-minimap-content`);if(!e)return;e.innerHTML=``;let t=document.getElementById(`erd-nodes-layer`),n=document.getElementById(`erd-wrapper`);if(!t||!n)return;let r=Array.from(t.querySelectorAll(`.erd-node`));if(r.length===0)return;let{scale:i,offsetX:a,offsetY:o,minX:s,minY:c,vW:l,vH:u,vLeft:d,vTop:f}=it(n,r);r.forEach(t=>{let n=parseFloat(t.style.left)||0,r=parseFloat(t.style.top)||0,l=t.offsetWidth||290,u=t.offsetHeight||150,d=t.dataset.table,f=document.createElement(`div`);f.style.position=`absolute`,f.style.left=`${a+(n-s)*i}px`,f.style.top=`${o+(r-c)*i}px`,f.style.width=`${Math.max(l*i,8)}px`,f.style.height=`${Math.max(u*i,6)}px`,f.style.backgroundColor=`var(--color-primary)`,f.style.opacity=G.selectedTables.has(d)?`1`:`0.6`,f.style.borderRadius=`2px`,e.appendChild(f)});let p=document.getElementById(`erd-minimap-viewport`);p&&(p.style.left=`${a+(d-s)*i}px`,p.style.top=`${o+(f-c)*i}px`,p.style.width=`${l*i}px`,p.style.height=`${u*i}px`)}function ot(e=120,t=120){let n=`new_table_${Math.floor(Math.random()*1e3)}`,r={table:n,isDraft:!0,columns:[{name:`id`,type:`INTEGER`,isPk:!0,isDraft:!0,nullable:!1}]},i=K();i[n]={x:e,y:t},Re(i),G.pushUndoState(),G.erdData.push(r),G.setSelection(n),J()}async function st(e){let t=document.getElementById(`table-name`);t&&(t.textContent=`Entity Relationship Diagram`);let n=document.getElementById(`table-icon`);n&&(n.textContent=`account_tree`),document.getElementById(`erd-main-viewport`)||(e.innerHTML=`
1715
- <div id="erd-main-viewport" class="erd-main-viewport">
1716
- <!-- Canvas Scrollable Workspace -->
1717
- <div id="erd-wrapper" class="erd-grid-bg erd-workspace-container">
1718
- <div id="erd-workspace" class="erd-workspace-canvas">
1719
- <svg id="erd-svg-layer" class="erd-svg-layer"></svg>
1720
- <div id="erd-nodes-layer" class="erd-nodes-layer"></div>
1721
- </div>
1722
- </div>
1723
-
1724
- <!-- Top Floating Action HUD -->
1725
- <div class="erd-top-hud">
1726
- <div class="erd-hud-left">
1727
- <div class="erd-search-box">
1728
- <span class="material-symbols-outlined search-icon">search</span>
1729
- <input type="text" id="erd-search-input" placeholder="Search tables..." autocomplete="off">
1730
- <kbd class="erd-search-shortcut">/</kbd>
1731
- <div id="erd-search-results" class="erd-search-results hidden"></div>
1732
- </div>
1733
- </div>
1734
-
1735
- <div class="erd-hud-right">
1736
- <button type="button" id="btn-erd-add-table" class="erd-hud-btn" title="Add a new draft table">
1737
- <span class="material-symbols-outlined icon-18">add</span>
1738
- <span>Add Table</span>
1739
- </button>
1740
-
1741
- <button type="button" id="btn-erd-auto-layout" class="erd-hud-btn" title="Auto arrange tables neatly">
1742
- <span class="material-symbols-outlined icon-18">account_tree</span>
1743
- <span>Auto Layout</span>
1744
- </button>
1745
-
1746
- <button type="button" id="btn-erd-snap-grid" class="erd-hud-btn active" title="Toggle magnetic grid snapping">
1747
- <span class="material-symbols-outlined icon-18">grid_4x4</span>
1748
- <span>Snap</span>
1749
- </button>
1750
-
1751
- <div class="erd-hud-divider"></div>
1752
-
1753
- <button type="button" id="btn-erd-save-drafts" class="erd-hud-btn primary disabled" title="Save draft tables and columns (Ctrl+S)">
1754
- <span class="material-symbols-outlined icon-18">save</span>
1755
- <span>Save</span>
1756
- <span class="erd-draft-count-badge" style="display: none;">0</span>
1757
- </button>
1758
- </div>
1759
- </div>
1760
-
1761
- <!-- Bottom-Left Shortcuts Helper -->
1762
- <div class="erd-shortcuts-helper">
1763
- <span class="erd-shortcut-item"><kbd>Alt</kbd>+Drag Pan</span>
1764
- <span class="erd-shortcut-item"><kbd>Ctrl</kbd>+Scroll Zoom</span>
1765
- <span class="erd-shortcut-item"><kbd>Ctrl</kbd>+<kbd>S</kbd> Save</span>
1766
- </div>
1767
-
1768
- <!-- Bottom-Right Floating Navigation Dock -->
1769
- <div class="erd-bottom-dock">
1770
- <!-- Collapsible Minimap -->
1771
- <div id="erd-minimap-container" class="erd-minimap-container">
1772
- <div class="erd-minimap-header">
1773
- <span class="erd-minimap-title">
1774
- <span class="material-symbols-outlined icon-14">map</span>
1775
- Minimap
1776
- </span>
1777
- <button type="button" id="btn-erd-close-minimap" class="icon-btn" title="Hide minimap">
1778
- <span class="material-symbols-outlined icon-14">close</span>
1779
- </button>
1780
- </div>
1781
- <div id="erd-minimap" class="erd-minimap">
1782
- <div id="erd-minimap-content" class="erd-minimap-content"></div>
1783
- <div id="erd-minimap-viewport" class="erd-minimap-viewport"></div>
1784
- </div>
1785
- </div>
1786
-
1787
- <!-- Zoom HUD Controls -->
1788
- <div class="erd-zoom-controls">
1789
- <button type="button" id="btn-erd-zoom-out" class="erd-dock-btn" title="Zoom Out">
1790
- <span class="material-symbols-outlined icon-18">remove</span>
1791
- </button>
1792
- <button type="button" id="btn-erd-zoom-reset" class="erd-dock-btn erd-zoom-value" title="Reset Zoom to 100%">
1793
- 100%
1794
- </button>
1795
- <button type="button" id="btn-erd-zoom-in" class="erd-dock-btn" title="Zoom In">
1796
- <span class="material-symbols-outlined icon-18">add</span>
1797
- </button>
1798
- <button type="button" id="btn-erd-fit-view" class="erd-dock-btn" title="Fit tables to view">
1799
- <span class="material-symbols-outlined icon-18">crop_free</span>
1800
- </button>
1801
- <div class="erd-dock-divider"></div>
1802
- <button type="button" id="btn-erd-toggle-minimap" class="erd-dock-btn active" title="Toggle Minimap">
1803
- <span class="material-symbols-outlined icon-18">map</span>
1804
- </button>
1805
- </div>
1806
- </div>
1807
- </div>
1808
- `,document.getElementById(`erd-wrapper`).addEventListener(`contextmenu`,e=>{if(e.preventDefault(),e.target.closest(`.erd-node`))return;let t=document.getElementById(`erd-workspace`).getBoundingClientRect(),n=(e.clientX-t.left)/G.zoom,r=(e.clientY-t.top)/G.zoom;w(e,[{label:`Add Table`,icon:`table_chart`,action:()=>ot(n,r)},{label:`Paste`,icon:`content_paste`,action:We}])}),et());let r=document.getElementById(`erd-nodes-layer`);r&&(r.innerHTML=`<div class="erd-loading-state"><span class="material-symbols-outlined animate-spin icon-24">progress_activity</span><span>Loading Schema Diagrams...</span></div>`),G.erdData=await Le()||[],G.erdData.forEach(e=>{e.columns.sort((e,t)=>e.isPk&&!t.isPk?-1:!e.isPk&&t.isPk||e.fkTarget&&!t.fkTarget?1:!e.fkTarget&&t.fkTarget?-1:0),e.columns.forEach(e=>{e.isPk&&(e.nullable=!1)})}),G.undoStack=[],G.clearSelection(),J(),setTimeout(()=>{let e=document.getElementById(`erd-wrapper`);if(e&&e.scrollLeft===0&&e.scrollTop===0){let t=document.querySelectorAll(`.erd-node`);if(t.length>0){let n=1/0,r=1/0,i=-1/0,a=-1/0;t.forEach(e=>{let t=parseFloat(e.style.left)||0,o=parseFloat(e.style.top)||0,s=e.offsetWidth||290,c=e.offsetHeight||150;t<n&&(n=t),o<r&&(r=o),t+s>i&&(i=t+s),o+c>a&&(a=o+c)});let o=(n+i)/2,s=(r+a)/2;e.scrollLeft=o*G.zoom-e.clientWidth/2,e.scrollTop=s*G.zoom-e.clientHeight/2}}},40)}function ct(e){if(!e||e<=0)return`N/A`;let t=Math.floor(e/(3600*24)),n=Math.floor(e%(3600*24)/3600),r=Math.floor(e%3600/60),i=``;return t>0&&(i+=`${t}d `),n>0&&(i+=`${n}h `),i+=`${r}m`,i||`< 1m`}function lt(e){if(e===0||!e)return`0 B`;let t=1024,n=[`B`,`KB`,`MB`,`GB`,`TB`],r=Math.floor(Math.log(e)/Math.log(t));return parseFloat((e/t**r).toFixed(2))+` `+n[r]}var ut=null,dt=null;function Y(e,t,n,r,i=``,a=null){let o=``;return a!==null&&!isNaN(a)&&(o=`
1809
- <div class="status-card-progress">
1810
- <div class="status-card-progress-bar" style="width: ${Math.min(100,Math.max(0,a))}%"></div>
1811
- </div>
1812
- `),`
1813
- <div class="status-card ${r}">
1814
- <div class="status-card-header">
1815
- <span class="status-card-title">${t}</span>
1816
- <div class="status-card-icon">
1817
- <span class="material-symbols-outlined">${e}</span>
1818
- </div>
1819
- </div>
1820
- <div class="status-card-body">
1821
- <div class="status-card-value">${n}</div>
1822
- ${o}
1823
- ${i?`<div class="status-card-subtext">${i}</div>`:``}
1824
- </div>
1825
- </div>
1826
- `}function ft(e){let t=document.getElementById(`status-cards-container`);if(!t)return;let n=``,r=Date.now();if(n+=Y(`table_chart`,`Total Tables`,e.totalTables==null?`0`:e.totalTables,`card-blue`,`Active schemas & base tables`),n+=Y(`hard_drive`,`Storage Used`,lt(e.sizeBytes),`card-amber`,e.dbName?`File / DB: ${e.dbName}`:`Disk footprint`),n+=Y(`hub`,`Active Connections`,e.activeConnections||`1`,`card-emerald`,e.dbType===`sqlite`?`Single-process runtime`:`Connected clients`),e.uptime&&(n+=Y(`schedule`,`Server Uptime`,ct(e.uptime),`card-indigo`,`Continuous runtime`)),e.dbType===`mysql`){let t=0;if(ut&&dt&&e.queries){let n=(r-dt)/1e3,i=e.queries-ut.queries;t=Math.max(0,i/n)}n+=Y(`speed`,`Query Throughput`,`${t.toFixed(1)} /s`,`card-blue`,`Total: ${(e.queries||0).toLocaleString()} queries`)}else if(e.dbType===`postgres`){let t=0;if(ut&&dt&&e.transactions){let n=(r-dt)/1e3,i=e.transactions-ut.transactions;t=Math.max(0,i/n)}n+=Y(`receipt_long`,`Transactions Rate`,`${t.toFixed(1)} /s`,`card-blue`,`Total: ${(e.transactions||0).toLocaleString()} commits/rollbacks`)}else if(e.dbType===`sqlite`){let t=e.osMemTotal?(e.osMemUsed/e.osMemTotal*100).toFixed(1):0;n+=Y(`memory`,`Host Memory`,`${t}%`,`card-blue`,`${lt(e.osMemUsed)} / ${lt(e.osMemTotal)}`,parseFloat(t)),n+=Y(`memory_alt`,`Host CPU Load`,(e.osCpuUsage||0).toFixed(2),`card-indigo`,`1-min system load average`)}t.innerHTML=n,ut=e,dt=r}window.navigateToTableData=function(e){let t=document.querySelector(`.table-btn[data-table="${e}"]`);t&&(window.AppState.currentTable=e,window.AppState.currentTableBtnElement=t,window.handleSwitchTab(`data-btn`))};function pt(e){let t=document.getElementById(`status-distribution-container`),n=document.getElementById(`status-toptables-container`);if(!t||!n)return;let r=Object.entries(e).sort((e,t)=>t[1]-e[1]);if(r.length===0){n.classList.add(`hidden`),t.classList.add(`hidden`);return}let i=r.slice(0,5),a=r.reduce((e,[t,n])=>e+n,0),o=[`#3b82f6`,`#10b981`,`#f59e0b`,`#8b5cf6`,`#ec4899`],s=`
1827
- <div class="analytics-panel-header">
1828
- <h3 class="analytics-panel-title">Top Largest Tables</h3>
1829
- <span class="analytics-panel-badge">${r.length} tables</span>
1830
- </div>
1831
- <div class="top-tables-list">
1832
- `,c=i[0]?Math.max(i[0][1],1):1;i.forEach(([e,t],n)=>{let r=(t/c*100).toFixed(1),i=a>0?(t/a*100).toFixed(1):0;s+=`
1833
- <div class="top-table-item hover-clickable" onclick="window.navigateToTableData('${e}')" title="Click to view ${e} data">
1834
- <div class="top-table-bar-fill" style="width: ${r}%; background-color: ${o[n]}"></div>
1835
- <div class="top-table-content">
1836
- <div class="top-table-left">
1837
- <span class="top-table-rank">#${n+1}</span>
1838
- <span class="top-table-name">${e}</span>
1839
- </div>
1840
- <div class="top-table-right">
1841
- <span class="top-table-pct">${i}%</span>
1842
- <span class="top-table-rows">${t.toLocaleString()} rows</span>
1843
- <span class="material-symbols-outlined top-table-arrow">arrow_forward</span>
1844
- </div>
1845
- </div>
1846
- </div>
1847
- `}),s+=`</div>`,n.innerHTML=s,n.classList.remove(`hidden`),n.classList.add(`block`);let l=`
1848
- <div class="analytics-panel-header">
1849
- <h3 class="analytics-panel-title">Row Count Distribution</h3>
1850
- <span class="analytics-panel-badge">${a.toLocaleString()} total</span>
1851
- </div>
1852
- <div class="distribution-content">
1853
- `,u=`<div class="distribution-legend">`,d=[],f=0;if(a>0){let e=0;i.forEach(([t,n],r)=>{let i=n/a*100;e+=n;let s=f,c=f+i;d.push(`${o[r]} ${s.toFixed(2)}% ${c.toFixed(2)}%`),f=c,u+=`
1854
- <div class="legend-item" onclick="window.navigateToTableData('${t}')" style="cursor: pointer;" title="View ${t}">
1855
- <div class="legend-color" style="background: ${o[r]}"></div>
1856
- <span class="legend-name">${t}</span>
1857
- <span class="legend-pct">${i.toFixed(1)}%</span>
1858
- </div>
1859
- `});let t=a-e;if(t>0&&r.length>5){let e=t/a*100,n=f,i=f+e;d.push(`#64748b ${n.toFixed(2)}% ${i.toFixed(2)}%`),f=i,u+=`
1860
- <div class="legend-item">
1861
- <div class="legend-color" style="background: #64748b"></div>
1862
- <span class="legend-name">Others (${r.length-5})</span>
1863
- <span class="legend-pct">${e.toFixed(1)}%</span>
1864
- </div>
1865
- `}l+=`
1866
- <div class="distribution-pie-wrapper">
1867
- <div class="distribution-pie" style="background: conic-gradient(${d.join(`, `)});"></div>
1868
- <div class="donut-center-info">
1869
- <span class="donut-total-num">${a>999999?(a/1e6).toFixed(1)+`M`:a.toLocaleString()}</span>
1870
- <span class="donut-total-label">Total Rows</span>
1871
- </div>
1872
- </div>
1873
- `}else l+=`
1874
- <div class="distribution-pie-wrapper">
1875
- <div class="distribution-pie" style="background: var(--color-border);"></div>
1876
- <div class="donut-center-info">
1877
- <span class="donut-total-num">0</span>
1878
- <span class="donut-total-label">No Rows</span>
1879
- </div>
1880
- </div>
1881
- `,u+=`<div class="text-soft" style="font-size: 13px;">No data rows found</div>`;u+=`</div>`,l+=u+`</div>`,t.innerHTML=l,t.classList.remove(`hidden`),t.classList.add(`block`)}function mt(){gt()}async function ht(e){e.innerHTML=`
1882
- <div class="status-dashboard">
1883
- <!-- 1. Hero Overview Panel -->
1884
- <div class="hero-overview-panel">
1885
- <div class="status-db-icon-container">
1886
- <span class="material-symbols-outlined status-db-icon" id="status-db-icon">database</span>
1887
- <span id="connection-dot" class="connection-dot green"></span>
1888
- </div>
1889
- <div class="hero-info">
1890
- <div class="hero-title-row">
1891
- <h1 id="header-db-name" class="hero-db-name">-</h1>
1892
- <span id="header-status-badge" class="status-badge connected">
1893
- <span class="status-badge-dot"></span>
1894
- <span id="header-status-text">Connected</span>
1895
- </span>
1896
- </div>
1897
- <div class="hero-subinfo">
1898
- <span id="header-db-type" class="db-type-badge">-</span>
1899
- <span class="hero-dot-separator">•</span>
1900
- <span id="header-db-version" class="hero-db-version">-</span>
1901
- <span class="hero-dot-separator">•</span>
1902
- <span id="header-last-updated" class="hero-update-text">Just now</span>
1903
- </div>
1904
- </div>
1905
- <div class="hero-actions">
1906
- <button id="status-live-toggle" class="header-btn secondary" title="Toggle Auto-refresh">
1907
- <span class="material-symbols-outlined" style="font-size: 15px; color: var(--color-success);">radio_button_checked</span>
1908
- <span id="status-live-text">Live (3s)</span>
1909
- </button>
1910
- <button id="status-refresh-btn" class="header-btn secondary" title="Refresh Now">
1911
- <span class="material-symbols-outlined" style="font-size: 16px;">refresh</span>
1912
- <span>Refresh</span>
1913
- </button>
1914
- </div>
1915
- </div>
1916
-
1917
- <!-- 2. Metric Cards Grid -->
1918
- <div class="status-grid" id="status-cards-container">
1919
- <!-- Cards will be injected here -->
1920
- </div>
1921
-
1922
- <!-- 3. Analytics Split View (Top Tables & Donut Chart) -->
1923
- <div class="analytics-split-view">
1924
- <div id="status-toptables-container" class="top-tables-container hidden">
1925
- <!-- Top tables injected here -->
1926
- </div>
1927
- <div id="status-distribution-container" class="distribution-container hidden">
1928
- <!-- Distribution Bar injected here -->
1929
- </div>
1930
- </div>
1931
-
1932
- <!-- 4. Quick Actions Panel -->
1933
- <div class="quick-actions-panel">
1934
- <div class="analytics-panel-header">
1935
- <h3 class="analytics-panel-title">Quick Operations</h3>
1936
- <span class="analytics-panel-badge">Database Tools</span>
1937
- </div>
1938
- <div class="quick-actions-grid">
1939
- <button type="button" class="quick-action-card" id="btn-quick-export-backup" title="Export full database backup (.sql)">
1940
- <div class="quick-action-icon" style="color: var(--color-primary); background: rgba(59, 130, 246, 0.1);">
1941
- <span class="material-symbols-outlined">download</span>
1942
- </div>
1943
- <div class="quick-action-info">
1944
- <div class="quick-action-title">Export Database Backup</div>
1945
- <div class="quick-action-desc">Download complete SQL schema & data dump</div>
1946
- </div>
1947
- </button>
1948
-
1949
- <button type="button" class="quick-action-card" id="btn-quick-export-dict" title="Download database markdown data dictionary">
1950
- <div class="quick-action-icon" style="color: #10b981; background: rgba(16, 185, 129, 0.1);">
1951
- <span class="material-symbols-outlined">menu_book</span>
1952
- </div>
1953
- <div class="quick-action-info">
1954
- <div class="quick-action-title">Data Dictionary</div>
1955
- <div class="quick-action-desc">Generate detailed markdown schema documentation</div>
1956
- </div>
1957
- </button>
1958
-
1959
- <button type="button" class="quick-action-card" id="btn-quick-open-console" title="Open SQL Query Console">
1960
- <div class="quick-action-icon" style="color: #f59e0b; background: rgba(245, 158, 11, 0.1);">
1961
- <span class="material-symbols-outlined">terminal</span>
1962
- </div>
1963
- <div class="quick-action-info">
1964
- <div class="quick-action-title">SQL Console</div>
1965
- <div class="quick-action-desc">Write, execute and test arbitrary SQL queries</div>
1966
- </div>
1967
- </button>
1968
-
1969
- <button type="button" class="quick-action-card" id="btn-quick-open-erd" title="View Entity Relationship Diagram">
1970
- <div class="quick-action-icon" style="color: #8b5cf6; background: rgba(139, 92, 246, 0.1);">
1971
- <span class="material-symbols-outlined">hub</span>
1972
- </div>
1973
- <div class="quick-action-info">
1974
- <div class="quick-action-title">Visual ERD</div>
1975
- <div class="quick-action-desc">Explore schema relationships & table foreign keys</div>
1976
- </div>
1977
- </button>
1978
- </div>
1979
- </div>
1980
-
1981
- <!-- 5. Feature Prompts (if any) -->
1982
- <div id="feature-prompts-container" class="flex-col gap-4 hidden">
1983
- <!-- Feature prompts will be injected here -->
1984
- </div>
1985
- </div>
1986
- `,await gt();let t=document.getElementById(`btn-quick-export-backup`);t&&t.addEventListener(`click`,()=>{window.open(`/api/database/export`,`_blank`)});let n=document.getElementById(`btn-quick-export-dict`);n&&n.addEventListener(`click`,()=>{window.open(`/api/database/dictionary`,`_blank`)});let r=document.getElementById(`btn-quick-open-console`);r&&r.addEventListener(`click`,()=>{typeof window.handleSwitchTab==`function`&&window.handleSwitchTab(`console-btn`)});let i=document.getElementById(`btn-quick-open-erd`);i&&i.addEventListener(`click`,()=>{typeof window.handleSwitchTab==`function`&&window.handleSwitchTab(`erd-btn`)});let a=document.getElementById(`status-refresh-btn`);a&&a.addEventListener(`click`,()=>{let e=a.querySelector(`.material-symbols-outlined`);e&&(e.style.transition=`transform 0.4s ease`,e.style.transform=`rotate(${(e.dataset.rot||0)*1+360}deg)`,e.dataset.rot=(e.dataset.rot||0)*1+360),gt()});let o=!0,s=document.getElementById(`status-live-toggle`);s&&s.addEventListener(`click`,()=>{o=!o;let e=s.querySelector(`.material-symbols-outlined`),t=document.getElementById(`status-live-text`);o?(e&&(e.style.color=`var(--color-success)`),t&&(t.textContent=`Live (3s)`)):(e&&(e.style.color=`var(--color-text-soft)`),t&&(t.textContent=`Paused`))}),window.statusPollingInterval&&clearInterval(window.statusPollingInterval),window.statusPollingInterval=setInterval(()=>{window.AppState.currentTab===`status-btn`&&o&&gt()},3e3)}async function gt(){let e=performance.now();try{let[t,n]=await Promise.all([fetch(`/api/status`),fetch(`/api/tables/stats`)]),r=await t.json(),i=await n.json(),a=Math.round(performance.now()-e);r.success?_t(r.data,i.data,a):vt(r.error)}catch(e){vt(e.message)}}function _t(e,t={},n=null){let r=document.getElementById(`status-db-icon`),i=document.getElementById(`connection-dot`),a=document.getElementById(`header-status-badge`),o=document.getElementById(`header-status-text`);if(!r||!i)return;if(e.status===`connected`)r.textContent=`database`,i.className=`connection-dot green`,a&&(a.className=`status-badge connected`),o&&(o.textContent=`Connected`);else{r.textContent=`database_off`,i.className=`connection-dot red`,a&&(a.className=`status-badge disconnected`),o&&(o.textContent=`Disconnected`);return}let s=document.getElementById(`header-db-name`),c=document.getElementById(`header-db-type`),l=document.getElementById(`header-db-version`),u=document.getElementById(`header-last-updated`);if(s&&(s.textContent=e.dbName||`Database`),c&&(c.textContent=e.dbType?e.dbType.toUpperCase():`UNKNOWN`,c.setAttribute(`data-db-type`,e.dbType||`unknown`)),l&&(l.textContent=e.version?`v${e.version}`:`N/A`),u){let e=new Date,t=`${e.getHours().toString().padStart(2,`0`)}:${e.getMinutes().toString().padStart(2,`0`)}:${e.getSeconds().toString().padStart(2,`0`)}`;u.textContent=n===null?t:`${t} (${n}ms)`}ft(e);let d=document.getElementById(`feature-prompts-container`);d&&(e.dbType===`sqlite`?(d.classList.add(`hidden`),d.classList.remove(`flex`),d.innerHTML=``):(d.innerHTML=`
1987
- <div class="feature-prompt">
1988
- <span class="material-symbols-outlined icon">info</span>
1989
- <div class="feature-prompt-content">
1990
- <div class="prompt-title">Avg Query Latency & Slow Queries</div>
1991
- <div class="prompt-desc">This metric requires database plugins to be enabled on your server (e.g. <code>pg_stat_statements</code> for PostgreSQL, or <code>Performance Schema</code> for MySQL).</div>
1992
- </div>
1993
- <button type="button" class="feature-prompt-close" onclick="this.closest('#feature-prompts-container').classList.add('hidden')" title="Dismiss">
1994
- <span class="material-symbols-outlined">close</span>
1995
- </button>
1996
- </div>
1997
- `,d.classList.remove(`hidden`),d.classList.add(`flex`))),pt(t)}function vt(e){let t=document.getElementById(`status-db-icon`),n=document.getElementById(`connection-dot`);t&&n&&(t.textContent=`database_off`,n.className=`connection-dot red`);let r=document.getElementById(`status-cards-container`);r&&(r.innerHTML=`<div class="text-red-500">Failed to load database status: ${e}</div>`)}function yt(e){let t=`sqlite`,n={sqlite:`create`,postgres:`existing`,mysql:`existing`},r={state:`idle`,message:`Click "Test Connection" to verify connection parameters before proceeding.`,latencyMs:null,error:``},i={sqlite:{name:`SQLite`,badge:`Local File`,icon:`description`,desc:`Zero-config local file database.`},postgres:{name:`PostgreSQL`,badge:`Server Instance`,icon:`database`,desc:`Enterprise object-relational database.`},mysql:{name:`MySQL`,badge:`Server Instance`,icon:`storage`,desc:`High-performance relational database.`}},a={sqlite:{create:{title:`About Create New (SQLite)`,desc:`Initializes a new, zero-configuration SQLite database file directly in your workspace directory root. No database server or credentials needed.`,action:`Will initialize a new SQLite database file and prepare schema.`},existing:{title:`About Connect Existing (SQLite)`,desc:`Connects to an existing SQLite database file (.sqlite, .db, .sqlite3) located in your workspace or file system to inspect schema and browse data.`,action:`Will verify existing file on disk and inspect tables.`}},postgres:{create:{title:`About Create New (PostgreSQL)`,desc:`Connects to your PostgreSQL server instance using administrative credentials and automatically executes "CREATE DATABASE" to set up your project database.`,action:`Will connect to PostgreSQL server and run CREATE DATABASE.`},existing:{title:`About Connect Existing (PostgreSQL)`,desc:`Connects to an already running PostgreSQL database instance via standard connection URI. Automatically introspects schemas, tables, and foreign keys.`,action:`Will establish client connection and map schema tables.`}},mysql:{create:{title:`About Create New (MySQL)`,desc:`Connects to your MySQL / MariaDB server instance using administrative credentials and executes "CREATE DATABASE" to initialize your new database.`,action:`Will connect to MySQL server and run CREATE DATABASE.`},existing:{title:`About Connect Existing (MySQL)`,desc:`Connects to an existing MySQL / MariaDB database service via standard connection URI. Instantly maps tables and relation structures.`,action:`Will connect to MySQL database and inspect tables.`}}},o={sqlite:[{label:`drixio.sqlite`,val:`drixio.sqlite`,path:`./drixio.sqlite`},{label:`data.db`,val:`data.db`,path:`./data.db`}],postgres:[{label:`Default`,uri:`postgresql://postgres:postgres@localhost:5432/postgres`},{label:`Localhost`,uri:`postgresql://localhost:5432/mydb`}],mysql:[{label:`Default`,uri:`mysql://root:password@localhost:3306/mysql`},{label:`No Pass`,uri:`mysql://root:@localhost:3306/mydb`}]},s=e=>{if(!e||typeof e!=`string`)return null;let n=e.trim();n=n.replace(/^["']|["']$/g,``),n.includes(`=`)&&(n=n.split(`=`).slice(1).join(`=`).trim().replace(/^["']|["']$/g,``));try{let e=n;n.includes(`://`)||(e=`${t}://${n}`);let r=new URL(e),i=r.protocol.replace(`:`,``).toLowerCase(),a=i.startsWith(`post`)?`postgres`:i.startsWith(`my`)?`mysql`:i,o=r.hostname||`localhost`,s=a===`mysql`?`3306`:`5432`,c=r.port||s,l=a===`mysql`?`root`:`postgres`,u=decodeURIComponent(r.username||l),d=decodeURIComponent(r.password||``),f=r.pathname.replace(/^\//,``),p=a===`mysql`?`mysql`:`postgres`;return{dialect:a,host:o,port:c,user:u,password:d,dbName:decodeURIComponent(f.split(`?`)[0]||p)}}catch{return null}},c=n=>{let i=s(n);if(!i)return window.showToast&&window.showToast(`Invalid connection string format.`,`error`),!1;i.dialect&&i.dialect!==t&&[`postgres`,`mysql`].includes(i.dialect)&&(t=i.dialect,h());let a=e.querySelector(`#connect-server-host`),o=e.querySelector(`#connect-server-port`),c=e.querySelector(`#connect-server-user`),l=e.querySelector(`#connect-server-pass`),u=e.querySelector(`#connect-server-dbname`),d=e.querySelector(`#connect-smart-uri-input`);return a&&(a.value=i.host),o&&(o.value=i.port),c&&(c.value=i.user),l&&(l.value=i.password),u&&(u.value=i.dbName),d&&d.value.trim()!==n.trim()&&(d.value=n.trim()),f(),r.state!==`idle`&&(r={state:`idle`,message:``,latencyMs:null,error:``},p()),window.showToast&&window.showToast(`Parameters auto-filled from connection URL!`,`success`),!0},l=()=>{let r=n[t];if(t===`sqlite`){if(r===`create`){let t=e.querySelector(`#connect-sqlite-name`)?.value.trim()||`drixio.sqlite`;return!t.endsWith(`.sqlite`)&&!t.endsWith(`.db`)&&!t.endsWith(`.sqlite3`)&&(t+=`.sqlite`),`./${t}`}return e.querySelector(`#connect-sqlite-path`)?.value.trim()||`./drixio.sqlite`}if(t===`postgres`||t===`mysql`){let n=e.querySelector(`#connect-server-host`)?.value.trim()||`localhost`,i=t===`postgres`?`5432`:`3306`,a=e.querySelector(`#connect-server-port`)?.value.trim()||i,o=t===`postgres`?`postgres`:`root`,s=e.querySelector(`#connect-server-user`)?.value.trim()||o,c=e.querySelector(`#connect-server-pass`)?.value||``,l=r===`create`?`new_db`:t===`postgres`?`postgres`:`mysql`,u=e.querySelector(`#connect-server-dbname`)?.value.trim()||l,d=c?`${encodeURIComponent(s)}:${encodeURIComponent(c)}@`:s?`${encodeURIComponent(s)}@`:``;return`${t}://${d}${n}:${a}/${u}`}return``},u=()=>{let r=n[t];if(t===`sqlite`)return r===`create`?e.querySelector(`#connect-sqlite-name`)?.value.trim()||`drixio.sqlite`:(e.querySelector(`#connect-sqlite-path`)?.value.trim()||`drixio.sqlite`).split(/[/\\]/).pop()||`drixio.sqlite`;let i=r===`create`?`new_db`:t===`postgres`?`postgres`:`mysql`;return e.querySelector(`#connect-server-dbname`)?.value.trim()||i},d=()=>{if(t===`sqlite`)return`<workspace root>/${u()}`;let n=e.querySelector(`#connect-server-host`)?.value.trim()||`localhost`,r=t===`postgres`?`5432`:`3306`;return`${n}:${e.querySelector(`#connect-server-port`)?.value.trim()||r}`},f=()=>{let t=e.querySelector(`#preview-conn-url`),n=e.querySelector(`#preview-db-name`),r=e.querySelector(`#preview-location`),i=e.querySelector(`#preview-env-output`),a=e.querySelector(`#connect-save-env`),o=l();t&&(t.textContent=o),n&&(n.textContent=u()),r&&(r.textContent=d()),i&&(a?.checked?(i.textContent=`DATABASE_URL="${o}"`,i.classList.remove(`disabled`)):(i.textContent=`(Will not write to .env)`,i.classList.add(`disabled`)))},p=()=>{let t=e.querySelector(`#test-status-badge`),n=e.querySelector(`#test-status-msg`),i=e.querySelector(`#run-test-btn`);!t||!n||!i||(r.state===`testing`?(t.className=`test-badge testing`,t.innerHTML=`
1998
- <span class="material-symbols-outlined animate-spin" style="font-size: 13px;">sync</span>
1999
- <span>Testing Connection...</span>
2000
- `,n.textContent=`Contacting database target...`,i.disabled=!0):r.state===`success`?(t.className=`test-badge success`,t.innerHTML=`
2001
- <span class="status-dot green"></span>
2002
- <span>Reachable ${r.latencyMs===null?``:`(${r.latencyMs}ms)`}</span>
2003
- `,n.textContent=r.message,i.disabled=!1):r.state===`error`?(t.className=`test-badge error`,t.innerHTML=`
2004
- <span class="status-dot red"></span>
2005
- <span>Connection Failed</span>
2006
- `,n.textContent=r.error||`Unable to establish connection.`,i.disabled=!1):(t.className=`test-badge idle`,t.innerHTML=`
2007
- <span class="status-dot gray"></span>
2008
- <span>Not Tested</span>
2009
- `,n.textContent=`Click "Test Connection" to verify connectivity before proceeding.`,i.disabled=!1))},m=async()=>{r={state:`testing`,message:``,latencyMs:null,error:``},p();let i=n[t],a=null;if(i===`create`&&(t===`postgres`||t===`mysql`)){let n=e.querySelector(`#connect-server-host`)?.value.trim()||`localhost`,r=e.querySelector(`#connect-server-port`)?.value.trim()||(t===`mysql`?`3306`:`5432`),i=e.querySelector(`#connect-server-user`)?.value.trim()||(t===`mysql`?`root`:`postgres`),o=e.querySelector(`#connect-server-pass`)?.value||``,s=e.querySelector(`#connect-server-dbname`)?.value.trim()||`new_db`;a={mode:`create`,dbType:t,host:n,port:r,user:i,password:o,dbName:s}}else if(t===`sqlite`&&i===`create`){let t=e.querySelector(`#connect-sqlite-name`),n=t?t.value.trim():`drixio.sqlite`;!n.endsWith(`.sqlite`)&&!n.endsWith(`.db`)&&!n.endsWith(`.sqlite3`)&&(n+=`.sqlite`),a={mode:`create`,dbType:`sqlite`,url:`./${n}`}}else{let n=``;n=t===`sqlite`?e.querySelector(`#connect-sqlite-path`)?.value.trim()||`./drixio.sqlite`:l(),a={mode:`existing`,dbType:t,url:n}}try{let e=await(await fetch(`/api/test-connect`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(a)})).json();if(!e.success)throw Error(e.error||`Connection test failed`);r={state:`success`,message:e.data?.message||`Database target reachable and verified.`,latencyMs:e.data?.latencyMs??1,error:``}}catch(e){r={state:`error`,message:``,latencyMs:null,error:e.message}}p()},h=()=>{let r=n[t];i[t];let s=a[t]?.[r]||{title:``,desc:``,action:``},c=o[t]||[];e.innerHTML=`
2010
- <div class="connect-fullpage">
2011
- <!-- True 2-Column Workbench: Left = Configuration, Right = Live Diagnostics & Preview -->
2012
- <div class="connect-workbench-layout">
2013
- <!-- Left Column: Unified Configuration Panel -->
2014
- <section class="connect-config-pane">
2015
- <!-- Step 1: Engine Selection -->
2016
- <div class="connect-form-section">
2017
- <div class="connect-section-header">
2018
- <span class="connect-step-tag">1</span>
2019
- <span>Select Database Dialect</span>
2020
- </div>
2021
- <div class="connect-engine-grid">
2022
- ${[`sqlite`,`postgres`,`mysql`].map(e=>{let n=i[e],r=t===e;return`
2023
- <div class="connect-engine-card ${r?`active`:``}" data-type="${e}">
2024
- <div class="connect-engine-card-top">
2025
- <div class="connect-engine-card-icon">
2026
- <span class="material-symbols-outlined">${n.icon}</span>
2027
- </div>
2028
- <div class="connect-engine-card-radio">
2029
- <span class="material-symbols-outlined">${r?`radio_button_checked`:`radio_button_unchecked`}</span>
2030
- </div>
2031
- </div>
2032
- <div class="connect-engine-card-content">
2033
- <div class="connect-engine-card-header">
2034
- <span class="engine-title">${n.name}</span>
2035
- <span class="engine-badge">${n.badge}</span>
2036
- </div>
2037
- <span class="engine-desc">${n.desc}</span>
2038
- </div>
2039
- </div>
2040
- `}).join(``)}
2041
- </div>
2042
- </div>
2043
-
2044
- <!-- Step 2: Configuration & Parameters with Integrated Mode Tabs -->
2045
- <div class="connect-form-section">
2046
- <div class="connect-section-header-row">
2047
- <div class="connect-section-header">
2048
- <span class="connect-step-tag">2</span>
2049
- <span>Configuration & Parameters</span>
2050
- </div>
2051
-
2052
- <div class="connect-mode-segmented">
2053
- <button type="button" class="connect-mode-btn ${r===`existing`?`active`:``}" data-mode="existing">
2054
- <span class="material-symbols-outlined">link</span>
2055
- <span>Connect Existing</span>
2056
- </button>
2057
- <button type="button" class="connect-mode-btn ${r===`create`?`active`:``}" data-mode="create">
2058
- <span class="material-symbols-outlined">add_circle</span>
2059
- <span>Create New</span>
2060
- </button>
2061
- </div>
2062
- </div>
2063
-
2064
- <!-- Inline Mode Explanation Callout -->
2065
- <div class="connect-mode-callout">
2066
- <span class="material-symbols-outlined icon-info">info</span>
2067
- <span class="callout-desc">${s.desc}</span>
2068
- </div>
2069
-
2070
- <div class="connect-form-body">
2071
- ${g(t,r)}
2072
-
2073
- <!-- Quick Presets -->
2074
- ${c.length>0?`
2075
- <div class="connect-presets-group">
2076
- <span class="connect-presets-label">Presets:</span>
2077
- <div class="connect-presets-chips">
2078
- ${c.map(e=>`
2079
- <button type="button" class="connect-preset-chip" data-fill="${e.uri||(r===`create`?e.val:e.path)}">
2080
- <code>${e.label}</code>
2081
- </button>
2082
- `).join(``)}
2083
- </div>
2084
- </div>
2085
- `:``}
2086
-
2087
- <!-- Save to .env Checkbox -->
2088
- <label class="connect-checkbox-row">
2089
- <input type="checkbox" id="connect-save-env" checked />
2090
- <div class="connect-checkbox-info">
2091
- <span class="checkbox-title">Save connection string to <code class="env-code">.env</code> (DATABASE_URL)</span>
2092
- <span class="checkbox-desc">Automatically saves URL so Drixio CLI and Studio load this database on startup.</span>
2093
- </div>
2094
- </label>
2095
-
2096
- <!-- Error Box -->
2097
- <div id="connect-error-msg" class="connect-msg-box error hidden"></div>
2098
-
2099
- <!-- Submit Action Button -->
2100
- <div class="connect-action-row">
2101
- <button type="button" id="connect-submit-btn" class="connect-submit-btn">
2102
- <span class="material-symbols-outlined">${r===`create`?`rocket_launch`:`cable`}</span>
2103
- <span>${r===`create`?`Create & Connect Database`:`Connect to Database`}</span>
2104
- </button>
2105
- </div>
2106
- </div>
2107
- </div>
2108
- </section>
2109
-
2110
- <!-- Right Column: Live Diagnostics & Preview -->
2111
- <section class="connect-preview-pane">
2112
- <!-- Card 1: Connection Health & Live Test -->
2113
- <div class="connect-card-box">
2114
- <div class="card-box-header">
2115
- <div class="card-box-title">
2116
- <span class="material-symbols-outlined icon-blue">network_check</span>
2117
- <span>Connection Diagnostics</span>
2118
- </div>
2119
- <button type="button" id="run-test-btn" class="connect-test-btn">
2120
- <span class="material-symbols-outlined">bolt</span>
2121
- <span>Test Connection</span>
2122
- </button>
2123
- </div>
2124
- <div class="card-box-body">
2125
- <div class="test-status-row">
2126
- <div id="test-status-badge" class="test-badge idle">
2127
- <span class="status-dot gray"></span>
2128
- <span>Not Tested</span>
2129
- </div>
2130
- </div>
2131
- <p id="test-status-msg" class="test-status-text">Click "Test Connection" to verify database reachability before proceeding.</p>
2132
- </div>
2133
- </div>
2134
-
2135
- <!-- Card 2: Pre-flight Resolved Details -->
2136
- <div class="connect-card-box">
2137
- <div class="card-box-header">
2138
- <div class="card-box-title">
2139
- <span class="material-symbols-outlined icon-blue">tune</span>
2140
- <span>Pre-flight Overview</span>
2141
- </div>
2142
- <button type="button" id="copy-preview-btn" class="connect-copy-icon-btn" title="Copy Connection URL">
2143
- <span class="material-symbols-outlined">content_copy</span>
2144
- </button>
2145
- </div>
2146
-
2147
- <div class="card-box-body">
2148
- <div class="overview-item">
2149
- <span class="overview-label">Target URL</span>
2150
- <pre class="overview-code-box"><code id="preview-conn-url">${l()}</code></pre>
2151
- </div>
2152
-
2153
- <div class="overview-grid-2">
2154
- <div class="overview-item">
2155
- <span class="overview-label">Target Database</span>
2156
- <span id="preview-db-name" class="overview-val">${u()}</span>
2157
- </div>
2158
- <div class="overview-item">
2159
- <span class="overview-label">Storage / Host</span>
2160
- <span id="preview-location" class="overview-val">${d()}</span>
2161
- </div>
2162
- </div>
2163
-
2164
- <div class="overview-item">
2165
- <span class="overview-label">Planned Operation</span>
2166
- <span class="overview-action-badge">${s.action}</span>
2167
- </div>
2168
-
2169
- <div class="overview-item">
2170
- <span class="overview-label">.env Integration</span>
2171
- <code id="preview-env-output" class="overview-env-code">DATABASE_URL="${l()}"</code>
2172
- </div>
2173
- </div>
2174
- </div>
2175
- </section>
2176
- </div>
2177
- </div>
2178
- `,_()},g=(e,t)=>{if(e===`sqlite`)return t===`create`?`
2179
- <div class="connect-field">
2180
- <label class="connect-label" for="connect-sqlite-name">Database File Name</label>
2181
- <div class="connect-input-wrap">
2182
- <input type="text" id="connect-sqlite-name" class="connect-input mono" placeholder="drixio.sqlite" value="drixio.sqlite" />
2183
- </div>
2184
- <p class="connect-hint">New database file will be created in workspace root (e.g. <code>./drixio.sqlite</code>).</p>
2185
- </div>
2186
- `:`
2187
- <div class="connect-field">
2188
- <label class="connect-label" for="connect-sqlite-path">Database Path / URI</label>
2189
- <div class="connect-input-wrap">
2190
- <input type="text" id="connect-sqlite-path" class="connect-input mono" placeholder="./drixio.sqlite" value="./drixio.sqlite" />
2191
- </div>
2192
- <p class="connect-hint">Relative or absolute path (e.g. <code>./drixio.sqlite</code> or <code>file:./data.db</code>).</p>
2193
- </div>
2194
- `;if(e===`postgres`||e===`mysql`){let n=e===`postgres`,r=n?`5432`:`3306`,i=n?`postgres`:`root`,a=t===`create`,o=a?`my_project`:n?`postgres`:`mysql`;return`
2195
- <!-- Smart Connection String Parser Bar -->
2196
- <div class="connect-smart-parser">
2197
- <div class="smart-parser-header">
2198
- <div class="smart-parser-title">
2199
- <span class="material-symbols-outlined icon-magic">auto_awesome</span>
2200
- <span>Quick Import from Connection String</span>
2201
- </div>
2202
- <span class="smart-parser-badge">Auto-detect</span>
2203
- </div>
2204
- <div class="smart-parser-input-row">
2205
- <div class="smart-parser-input-wrap">
2206
- <span class="material-symbols-outlined smart-input-icon">link</span>
2207
- <input type="text" id="connect-smart-uri-input" class="connect-input mono" placeholder="Paste full URI or DATABASE_URL=... (e.g. ${n?`postgresql://postgres:password@localhost:5432/mydb`:`mysql://root:password@localhost:3306/mydb`})" />
2208
- </div>
2209
- <button type="button" id="connect-smart-parse-btn" class="smart-parse-btn" title="Parse connection string and auto-fill individual fields">
2210
- <span class="material-symbols-outlined">auto_fix_high</span>
2211
- <span>Auto-fill</span>
2212
- </button>
2213
- </div>
2214
- <p class="connect-hint">Paste a complete URI or <code>DATABASE_URL</code> to automatically decompose and populate the parameters below.</p>
2215
- </div>
2216
-
2217
- <!-- Divider between Smart Parser and Fields -->
2218
- <div class="connect-fields-divider">
2219
- <span class="divider-line"></span>
2220
- <span class="divider-text">OR CONFIGURE INDIVIDUAL PARAMETERS</span>
2221
- <span class="divider-line"></span>
2222
- </div>
2223
-
2224
- <!-- Host & Port Grid -->
2225
- <div class="connect-grid-2">
2226
- <div class="connect-field">
2227
- <label class="connect-label" for="connect-server-host">Server Host</label>
2228
- <div class="connect-input-wrap">
2229
- <input type="text" id="connect-server-host" class="connect-input mono" placeholder="localhost" value="localhost" />
2230
- </div>
2231
- <p class="connect-hint">Database server hostname (use <code>localhost</code> for local machine).</p>
2232
- </div>
2233
- <div class="connect-field">
2234
- <label class="connect-label" for="connect-server-port">Port</label>
2235
- <div class="connect-input-wrap">
2236
- <input type="text" id="connect-server-port" class="connect-input mono" placeholder="${r}" value="${r}" />
2237
- </div>
2238
- <p class="connect-hint">Default ${n?`PostgreSQL`:`MySQL`} port is <code>${r}</code>.</p>
2239
- </div>
2240
- </div>
2241
-
2242
- <!-- Username & Password Grid -->
2243
- <div class="connect-grid-2">
2244
- <div class="connect-field">
2245
- <label class="connect-label" for="connect-server-user">Username</label>
2246
- <div class="connect-input-wrap">
2247
- <input type="text" id="connect-server-user" class="connect-input mono" placeholder="${i}" value="${i}" />
2248
- </div>
2249
- <p class="connect-hint">Database user account name.</p>
2250
- </div>
2251
- <div class="connect-field">
2252
- <label class="connect-label" for="connect-server-pass">Password</label>
2253
- <div class="connect-input-wrap has-action">
2254
- <input type="password" id="connect-server-pass" class="connect-input mono" placeholder="Leave empty if none" />
2255
- <button type="button" class="connect-input-eye-btn" id="toggle-pass-visibility" title="Toggle password visibility">
2256
- <span class="material-symbols-outlined">visibility</span>
2257
- </button>
2258
- </div>
2259
- <p class="connect-hint">Account password. Leave empty if none is configured.</p>
2260
- </div>
2261
- </div>
2262
-
2263
- <!-- Database Name -->
2264
- <div class="connect-field">
2265
- <label class="connect-label" for="connect-server-dbname">${a?`New Database Name to Create`:`Target Database Name`}</label>
2266
- <div class="connect-input-wrap">
2267
- <input type="text" id="connect-server-dbname" class="connect-input mono" placeholder="e.g. ${o}" value="${o}" />
2268
- </div>
2269
- <p class="connect-hint">${a?`Connects to server instance to execute <code>CREATE DATABASE</code>.`:`Name of the existing database to inspect and manage.`}</p>
2270
- </div>
2271
- `}return``},_=()=>{e.querySelectorAll(`.connect-engine-card`).forEach(e=>{e.onclick=()=>{t=e.dataset.type,r={state:`idle`,message:``,latencyMs:null,error:``},h()}}),e.querySelectorAll(`.connect-mode-btn`).forEach(e=>{e.onclick=()=>{n[t]=e.dataset.mode,r={state:`idle`,message:``,latencyMs:null,error:``},h()}}),e.querySelectorAll(`.connect-input`).forEach(e=>{e.oninput=()=>{f(),r.state!==`idle`&&(r={state:`idle`,message:``,latencyMs:null,error:``},p())}});let i=e.querySelector(`#connect-smart-parse-btn`),a=e.querySelector(`#connect-smart-uri-input`);i&&a&&(i.onclick=()=>{let e=a.value.trim();if(!e){window.showToast&&window.showToast(`Please paste a connection URL first.`,`warning`);return}c(e)},a.onkeydown=e=>{if(e.key===`Enter`){e.preventDefault();let t=a.value.trim();t&&c(t)}},a.onpaste=()=>{setTimeout(()=>{let e=a.value.trim();e&&c(e)},40)});let o=e.querySelector(`#toggle-pass-visibility`),s=e.querySelector(`#connect-server-pass`);o&&s&&(o.onclick=()=>{let e=s.type===`password`;s.type=e?`text`:`password`;let t=o.querySelector(`.material-symbols-outlined`);t&&(t.textContent=e?`visibility_off`:`visibility`)}),e.querySelectorAll(`.connect-preset-chip`).forEach(i=>{i.onclick=()=>{let a=i.dataset.fill,o=n[t];if(t===`sqlite`)if(o===`create`){let t=e.querySelector(`#connect-sqlite-name`);t&&(t.value=a.replace(`./`,``))}else{let t=e.querySelector(`#connect-sqlite-path`);t&&(t.value=a)}else{c(a);let t=e.querySelector(`#connect-smart-uri-input`);t&&(t.value=a)}f(),r.state!==`idle`&&(r={state:`idle`,message:``,latencyMs:null,error:``},p())}});let u=e.querySelector(`#connect-save-env`);u&&(u.onchange=()=>{f()});let d=e.querySelector(`#run-test-btn`);d&&(d.onclick=()=>{m()});let g=e.querySelector(`#copy-preview-btn`);g&&(g.onclick=()=>{let e=l();navigator.clipboard&&(navigator.clipboard.writeText(e),window.showToast&&window.showToast(`Copied connection URL to clipboard!`,`info`))});let _=e.querySelector(`#connect-submit-btn`),v=e.querySelector(`#connect-error-msg`);_&&(_.onclick=async()=>{let r=n[t],i=null;if(r===`create`&&(t===`postgres`||t===`mysql`)){let n=e.querySelector(`#connect-server-host`)?.value.trim()||`localhost`,r=e.querySelector(`#connect-server-port`)?.value.trim()||(t===`mysql`?`3306`:`5432`),a=e.querySelector(`#connect-server-user`)?.value.trim()||(t===`mysql`?`root`:`postgres`),o=e.querySelector(`#connect-server-pass`)?.value||``,s=e.querySelector(`#connect-server-dbname`)?.value.trim();if(!s){v&&(v.textContent=`Please enter a new database name to create.`,v.classList.remove(`hidden`));return}i={mode:`create`,dbType:t,host:n,port:r,user:a,password:o,dbName:s,saveToEnv:u?.checked??!1}}else if(t===`sqlite`&&r===`create`){let t=e.querySelector(`#connect-sqlite-name`),n=t?t.value.trim():``;n||=`drixio.sqlite`,!n.endsWith(`.sqlite`)&&!n.endsWith(`.db`)&&!n.endsWith(`.sqlite3`)&&(n+=`.sqlite`),i={url:`./${n}`,createIfNotExist:!0,saveToEnv:u?.checked??!1}}else{let n=``;if(n=t===`sqlite`?e.querySelector(`#connect-sqlite-path`)?.value.trim()||``:l(),!n){v&&(v.textContent=`Please enter connection parameters.`,v.classList.remove(`hidden`));return}i={url:n,createIfNotExist:!1,saveToEnv:u?.checked??!1}}let a=_.innerHTML;_.disabled=!0,_.innerHTML=`
2272
- <span class="material-symbols-outlined animate-spin" style="font-size: 18px;">sync</span>
2273
- <span>Connecting...</span>
2274
- `,v&&v.classList.add(`hidden`);try{let e=await(await fetch(`/api/connect`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(i)})).json();if(!e.success)throw Error(e.error||`Failed to connect`);window.showToast&&window.showToast(`Database connected successfully!`,`success`),window.initSidebar&&await window.initSidebar(!0),window.setStudioConnectionMode&&window.setStudioConnectionMode(!0),window.handleSwitchTab&&window.handleSwitchTab(`data-btn`)}catch(e){_.disabled=!1,_.innerHTML=a,v&&(v.textContent=e.message,v.classList.remove(`hidden`)),window.showToast&&window.showToast(e.message,`error`)}})};h()}var bt=()=>{let e=localStorage.getItem(`drixio-theme`),t=window.matchMedia&&window.matchMedia(`(prefers-color-scheme: dark)`).matches,n=e=>{let t=document.getElementById(`theme-icon`),n=document.getElementById(`theme-text`);t&&(t.textContent=e?`light_mode`:`dark_mode`),n&&(n.textContent=e?`Light`:`Dark`)};e===`dark`||!e&&t?(document.documentElement.setAttribute(`data-theme`,`dark`),n(!0)):n(!1);let r=document.getElementById(`theme-toggle`);r&&r.addEventListener(`click`,()=>{let e=document.documentElement.getAttribute(`data-theme`)===`dark`,t=e?`light`:`dark`;document.documentElement.setAttribute(`data-theme`,t),localStorage.setItem(`drixio-theme`,t),n(!e)})},xt=()=>{window.showToast=function(e,t=`success`){let n=document.getElementById(`toast-container`);if(!n)return;let r=document.createElement(`div`);r.className=`toast ${t}`,r.innerHTML=`<span class="material-symbols-outlined">${t===`success`?`check_circle`:`error`}</span> <span>${e}</span>`,n.appendChild(r),setTimeout(()=>{r.parentNode&&r.parentNode.removeChild(r)},3e3)}},St=()=>{document.addEventListener(`mouseup`,()=>{window.DataGrid&&window.DataGrid.selection&&(window.DataGrid.selection.isDragging=!1),window.SchemaGrid&&window.SchemaGrid.selection&&(window.SchemaGrid.selection.isDragging=!1)}),document.addEventListener(`contextmenu`,e=>{let t=window.AppState?.currentTab===`data-btn`,n=window.AppState?.currentTab===`schema-btn`;if(!t&&!n||t&&!window.DataGrid||n&&!window.SchemaGrid)return;let r=e.target.closest(`td.row-header, td.data-cell`);if(!r||e.target.tagName===`INPUT`||e.target.tagName===`SELECT`||e.target.tagName===`TEXTAREA`)return;e.preventDefault();let i=parseInt(r.dataset.rowIdx);if(isNaN(i))return;let a=[i],o=t?window.DataGrid:window.SchemaGrid;if(o&&o.selection&&o.selection.startRow!==-1){let e=o.selection,t=Math.min(e.startRow,e.endRow),n=Math.max(e.startRow,e.endRow);if(i>=t&&i<=n){a=[];for(let e=t;e<=n;e++)a.push(e)}}let s=e=>{if(!t||!window.DataGrid)return null;let n=window.DataGrid.rows||[],i=window.DataGrid.pkColumn,a=null;if(e<n.length&&n[e]){a={...n[e]};let t=i?a[i]:null;t!=null&&window.DataGrid.pendingEdits?.[t]&&Object.assign(a,window.DataGrid.pendingEdits[t])}else{let t=r.dataset?.insertIndex===void 0?e-n.length:parseInt(r.dataset.insertIndex);window.DataGrid.pendingInserts?.[t]&&(a={...window.DataGrid.pendingInserts[t]})}return a},c=async e=>{let t=e.map(e=>s(e)).filter(Boolean);if(t.length===0)return;let n=t.length===1?t[0]:t,r=JSON.stringify(n,null,2);try{await navigator.clipboard.writeText(r)}catch{let e=document.createElement(`textarea`);e.value=r,document.body.appendChild(e),e.select(),document.execCommand(`copy`),e.remove()}window.showToast?.(`Copied ${t.length} row(s) as JSON!`,`success`)},l=async e=>{let t=e.map(e=>s(e)).filter(Boolean);if(t.length===0)return;let n=window.AppState?.currentTable||`table_name`,r=window.AppState?.dbType||`sqlite`,i=e=>r===`mysql`?`\`${e}\``:`"${e}"`,a=e=>{if(e==null)return`NULL`;if(typeof e==`number`)return String(e);if(typeof e==`boolean`)return e?`TRUE`:`FALSE`;if(typeof e==`object`)return`'${JSON.stringify(e).replace(/'/g,`''`)}'`;let t=String(e);return t.toUpperCase()===`NULL`?`NULL`:`'${t.replace(/'/g,`''`)}'`},o=window.DataGrid?.schema||[],c=o.length>0?o.map(e=>e.name):Object.keys(t[0]),l=t.map(e=>{let t=c.map(t=>a(e[t]));return`INSERT INTO ${i(n)} (${c.map(i).join(`, `)}) VALUES (${t.join(`, `)});`}).join(`
2275
- `);try{await navigator.clipboard.writeText(l)}catch{let e=document.createElement(`textarea`);e.value=l,document.body.appendChild(e),e.select(),document.execCommand(`copy`),e.remove()}window.showToast?.(`Copied ${t.length} row(s) as SQL INSERT!`,`success`)},u=async()=>{let e=r.textContent||``;try{await navigator.clipboard.writeText(e)}catch{let t=document.createElement(`textarea`);t.value=e,document.body.appendChild(t),t.select(),document.execCommand(`copy`),t.remove()}window.showToast?.(`Copied cell value!`,`success`)},d=e=>{t?(window.DataGrid.currentTransaction=[],e===`delete`?a.forEach(e=>ie(e)):e===`duplicate`&&(oe(a,window.DataGrid.schema.map(e=>e.name)),setTimeout(()=>{let e=document.getElementById(`data-grid-container-${window.AppState.currentTable}`);e&&(e.scrollTop=e.scrollHeight)},50)),window.DataGrid.currentTransaction.length>0&&window.DataGrid.history.push(window.DataGrid.currentTransaction),window.DataGrid.currentTransaction=null):n&&(window.SchemaGrid.currentTransaction=[],e===`delete`?a.forEach(e=>he(e)):e===`duplicate`&&(_e(a,[`name`,`type`,`isPk`,`nullable`,`defaultValue`,`Index`]),setTimeout(()=>{let e=document.getElementById(`schema-grid-container-${window.AppState.currentTable}`);e&&(e.scrollTop=e.scrollHeight)},50)),window.SchemaGrid.currentTransaction.length>0&&window.SchemaGrid.history.push(window.SchemaGrid.currentTransaction),window.SchemaGrid.currentTransaction=null)},f=[];r.classList.contains(`data-cell`)&&r.textContent?.trim()&&(f.push({label:`Copy Cell Value`,icon:`content_paste`,action:()=>u()}),f.push(`divider`)),t&&f.push({label:a.length>1?`Copy ${a.length} Rows as JSON`:`Copy Row as JSON`,icon:`data_object`,action:()=>c(a)},{label:a.length>1?`Copy ${a.length} Rows as SQL`:`Copy Row as SQL (INSERT)`,icon:`code`,action:()=>l(a)},`divider`),f.push({label:a.length>1?`Duplicate ${a.length} Rows`:`Duplicate Row`,icon:`content_copy`,action:()=>d(`duplicate`)},`divider`,{label:a.length>1?`Delete ${a.length} Rows`:`Delete Row`,icon:`delete`,danger:!0,action:()=>d(`delete`)}),w(e,f)}),document.addEventListener(`keydown`,e=>{let t=window.AppState?.currentTab===`data-btn`,n=window.AppState?.currentTab===`schema-btn`;if(e.key===`F5`){t&&window.DataGrid?.refreshData?(e.preventDefault(),window.DataGrid.refreshData()):n&&document.getElementById(`btn-refresh-schema`)&&(e.preventDefault(),document.getElementById(`btn-refresh-schema`).click());return}if(!t&&!n)return;if(e.key.toLowerCase()===`s`&&(e.ctrlKey||e.metaKey)){e.preventDefault(),(e.target.tagName===`INPUT`||e.target.tagName===`SELECT`||e.target.tagName===`TEXTAREA`)&&e.target.blur(),setTimeout(()=>{t&&window.saveDataGridEdits?.(),n&&window.saveSchemaEdits?.()},50);return}if(e.target.tagName===`INPUT`||e.target.tagName===`SELECT`||e.target.tagName===`TEXTAREA`)return;if([`ArrowUp`,`ArrowDown`,`ArrowLeft`,`ArrowRight`].includes(e.key)){let n=t?window.DataGrid:window.SchemaGrid;if(!n||!n.selection)return;e.preventDefault();let r=n.selection;if(r.startRow===-1)return;let i=t?n.schema.length:5,a=window.AppState?.currentTable,o=document.querySelectorAll(t?`#data-grid-table-${a} tbody tr`:`#schema-grid-table-${a} tbody tr`),s=o.length>0?o.length-1:0,c=e.shiftKey?r.endRow:r.startRow,l=e.shiftKey?r.endCol:r.startCol;e.key===`ArrowUp`&&(c=Math.max(0,c-1)),e.key===`ArrowDown`&&(c=Math.min(s,c+1)),e.key===`ArrowLeft`&&(l=Math.max(0,l-1)),e.key===`ArrowRight`&&(l=Math.min(i-1,l+1)),e.shiftKey?(r.endRow=c,r.endCol=l):(r.startRow=c,r.endRow=c,r.startCol=l,r.endCol=l),I(t?`data-grid-table-${a}`:`schema-grid-table-${a}`,n),setTimeout(()=>{let e=document.querySelector(t?`#data-grid-table-${a} td.data-cell[data-row-idx="${c}"][data-col-idx="${l}"]`:`#schema-grid-table-${a} td.data-cell[data-row-idx="${c}"][data-col-idx="${l}"]`);if(e){let n=document.getElementById(t?`data-grid-container-${a}`:`schema-grid-container-${a}`);if(n){let t=e.getBoundingClientRect(),r=n.getBoundingClientRect();t.bottom>r.bottom?n.scrollTop+=t.bottom-r.bottom+5:t.top<r.top+30&&(n.scrollTop-=r.top+30-t.top),t.right>r.right?n.scrollLeft+=t.right-r.right+5:t.left<r.left+50&&(n.scrollLeft-=r.left+50-t.left)}}},5);return}let r=e.key.length===1&&!e.ctrlKey&&!e.metaKey&&!e.altKey,i=e.key===`F2`;if(e.key===`Enter`||r||i){let n=t?window.DataGrid:window.SchemaGrid;if(!n||!n.selection||n.selection.startRow===-1)return;let a=window.AppState?.currentTable;if(!a)return;let o=Math.min(n.selection.startRow,n.selection.endRow),s=Math.min(n.selection.startCol,n.selection.endCol),c=document.querySelector(t?`#data-grid-table-${a} td.data-cell[data-row-idx="${o}"][data-col-idx="${s}"]`:`#schema-grid-table-${a} td.data-cell[data-row-idx="${o}"][data-col-idx="${s}"]`);if(c&&!c.classList.contains(`manage-indexes-cell`)&&!c.querySelector(`input, select`)){e.preventDefault();let t=new MouseEvent(`dblclick`,{bubbles:!0,cancelable:!0,view:window});c.dispatchEvent(t);let n=c.querySelector(`input`);if(n){if(r)n.value=e.key,n.setSelectionRange(e.key.length,e.key.length);else if(i){let e=n.value.length;n.setSelectionRange(e,e)}}else{let t=c.querySelector(`select`);if(t&&r){let n=Array.from(t.options).find(t=>t.text.toLowerCase().startsWith(e.key.toLowerCase()));n&&(t.value=n.value,t.dispatchEvent(new Event(`change`,{bubbles:!0})))}}return}}if(e.key===`Tab`){let n=t?window.DataGrid:window.SchemaGrid;if(!n||!n.selection||n.selection.startRow===-1)return;e.preventDefault();let r=window.AppState?.currentTable,i=Math.min(n.selection.startRow,n.selection.endRow),a=Math.min(n.selection.startCol,n.selection.endCol),o=t?n.schema.length:5,s=document.querySelectorAll(t?`#data-grid-table-${r} tbody tr`:`#schema-grid-table-${r} tbody tr`),c=s.length>0?s.length-1:0;e.shiftKey?(--a,a<0&&(a=o-1,i=Math.max(0,i-1))):(a+=1,a>=o&&(a=0,i=Math.min(c,i+1))),n.selection.startRow=i,n.selection.endRow=i,n.selection.startCol=a,n.selection.endCol=a,I(t?`data-grid-table-${r}`:`schema-grid-table-${r}`,n),setTimeout(()=>{let e=document.querySelector(t?`#data-grid-table-${r} td.data-cell[data-row-idx="${i}"][data-col-idx="${a}"]`:`#schema-grid-table-${r} td.data-cell[data-row-idx="${i}"][data-col-idx="${a}"]`);if(e){let n=document.getElementById(t?`data-grid-container-${r}`:`schema-grid-container-${r}`);if(n){let t=e.getBoundingClientRect(),r=n.getBoundingClientRect();t.bottom>r.bottom?n.scrollTop+=t.bottom-r.bottom+5:t.top<r.top+30&&(n.scrollTop-=r.top+30-t.top),t.right>r.right?n.scrollLeft+=t.right-r.right+5:t.left<r.left+50&&(n.scrollLeft-=r.left+50-t.left)}}},5);return}if(e.key===`Delete`||e.key===`Backspace`){let e=t?window.DataGrid:window.SchemaGrid;if(!e||!e.selection||e.selection.startRow===-1)return;let r=e.selection,i=Math.min(r.startRow,r.endRow),a=Math.max(r.startRow,r.endRow),o=Math.min(r.startCol,r.endCol),s=Math.max(r.startCol,r.endCol),c=t?window.DataGrid?.schema?.length||1:6,l=r.isRowSelection===!0||o===0&&s>=c-1;if(t)if(l){window.DataGrid.currentTransaction=[];for(let e=i;e<=a;e++)ie(e);window.DataGrid.currentTransaction.length>0&&window.DataGrid.history.push(window.DataGrid.currentTransaction),window.DataGrid.currentTransaction=null,window.updateSidebarDirtyState?.()}else window.DataGrid.currentTransaction=[],document.querySelectorAll(`#data-grid-table-${window.AppState.currentTable} .cell-in-range`).forEach(e=>re(e,``,window.DataGrid.schema.map(e=>e.name))),window.DataGrid.currentTransaction.length>0&&window.DataGrid.history.push(window.DataGrid.currentTransaction),window.DataGrid.currentTransaction=null;else if(n)if(l){window.SchemaGrid.currentTransaction=[];for(let e=i;e<=a;e++)he(e);window.SchemaGrid.currentTransaction.length>0&&window.SchemaGrid.history.push(window.SchemaGrid.currentTransaction),window.SchemaGrid.currentTransaction=null,window.updateSidebarDirtyState?.()}else{window.SchemaGrid.currentTransaction=[];let e=[`name`,`type`,`isPk`,`nullable`,`defaultValue`];document.querySelectorAll(`#schema-grid-table-${window.AppState.currentTable} .cell-in-range`).forEach(t=>{(t.dataset.insertIndex!==void 0||t.dataset.colKey===`name`)&&W(t,``,e)}),window.SchemaGrid.currentTransaction.length>0&&window.SchemaGrid.history.push(window.SchemaGrid.currentTransaction),window.SchemaGrid.currentTransaction=null}}if(e.key.toLowerCase()===`s`&&(e.ctrlKey||e.metaKey)&&(e.preventDefault(),t&&window.saveDataGridEdits(),n&&window.saveSchemaEdits()),e.key.toLowerCase()===`c`&&(e.ctrlKey||e.metaKey)){let e=(t?window.DataGrid:window.SchemaGrid).selection;if(e.startRow===-1)return;let n=Math.min(e.startRow,e.endRow),r=Math.max(e.startRow,e.endRow),i=Math.min(e.startCol,e.endCol),a=Math.max(e.startCol,e.endCol),o=``;for(let e=n;e<=r;e++){let n=[];for(let r=i;r<=a;r++){let i=document.querySelector(t?`#data-grid-table-${window.AppState.currentTable} td.data-cell[data-row-idx="${e}"][data-col-idx="${r}"]`:`#schema-grid-table-${window.AppState.currentTable} td.data-cell[data-row-idx="${e}"][data-col-idx="${r}"]`);if(i){let e=i.textContent.replace(/^null$|^\+ New$/,``);n.push(e)}}o+=n.join(` `)+`
2276
- `}navigator.clipboard.writeText(o.trimEnd()),document.querySelectorAll(`.cell-in-range`).forEach(e=>{e.style.backgroundColor=`var(--color-copy-flash)`,setTimeout(()=>e.style.backgroundColor=``,150)})}if(e.key===`-`&&(e.ctrlKey||e.metaKey)){e.preventDefault();let r=t?window.DataGrid:window.SchemaGrid,i=r.selection;if(i.startRow===-1)return;let a=Math.min(i.startRow,i.endRow),o=Math.max(i.startRow,i.endRow);if(t){r.currentTransaction=[];for(let e=a;e<=o;e++)ie(e);r.currentTransaction.length>0&&r.history.push(r.currentTransaction),r.currentTransaction=null}else if(n){r.currentTransaction=[];for(let e=a;e<=o;e++)he(e);r.currentTransaction.length>0&&r.history.push(r.currentTransaction),r.currentTransaction=null}}if(e.key.toLowerCase()===`z`&&(e.ctrlKey||e.metaKey)){if(e.preventDefault(),t){let e=window.DataGrid.history?.pop();if(e){let t=window.DataGrid.schema.map(e=>e.name);for(let n=e.length-1;n>=0;n--){let r=e[n];r.type===`delete`?ae(r.rowIdx,r.pk):re(r.td,r.oldVal,t,!1)}}}else if(n){let e=window.SchemaGrid.history?.pop();if(e){let t=[`name`,`type`,`isPk`,`nullable`,`defaultValue`];for(let n=e.length-1;n>=0;n--){let r=e[n];r.type===`delete`?ge(r.rowIdx,r.pk):W(r.td,r.oldVal,t,!1)}}}}}),document.addEventListener(`paste`,e=>{let t=window.AppState?.currentTab===`data-btn`,n=window.AppState?.currentTab===`schema-btn`;if(!t&&!n)return;let r=t?window.DataGrid:window.SchemaGrid;if(!r||e.target.tagName===`INPUT`||e.target.tagName===`SELECT`||e.target.tagName===`TEXTAREA`)return;let i=r.selection;if(i.startRow===-1)return;let a=e.clipboardData.getData(`text`);if(!a)return;e.preventDefault();let o=a.split(/\r?\n/).map(e=>e.split(` `)),s=Math.min(i.startRow,i.endRow),c=Math.min(i.startCol,i.endCol);if(r.currentTransaction=[],t){let e=r.schema.map(e=>e.name);o.forEach(t=>{t.forEach((t,n)=>{let r=c+n,i=document.querySelector(`#data-grid-table-${window.AppState.currentTable} td.data-cell[data-row-idx="${s}"][data-col-idx="${r}"]`);i&&re(i,t,e)}),s++}),r.currentTransaction.length>0&&r.history.push(r.currentTransaction),r.currentTransaction=null}else if(n){let e=[`name`,`type`,`isPk`,`nullable`,`defaultValue`];o.forEach(t=>{t.forEach((t,n)=>{let r=c+n,i=document.querySelector(`#schema-grid-table-${window.AppState.currentTable} td.data-cell[data-row-idx="${s}"][data-col-idx="${r}"]`);i&&(i.dataset.insertIndex!==void 0||i.dataset.colKey===`name`)&&W(i,t,e)}),s++}),r.currentTransaction.length>0&&r.history.push(r.currentTransaction),r.currentTransaction=null}})},Ct=document.getElementById(`header-container`),wt=document.getElementById(`sidebar-container`),Tt=document.getElementById(`tab-container`);Ct.innerHTML=e,wt.innerHTML=t,Tt.innerHTML=n;var X=document.getElementById(`export-btn`),Z=document.getElementById(`export-dropdown`),Q=document.getElementById(`import-btn`),$=document.getElementById(`import-dropdown`);if(Q&&$){Q.addEventListener(`click`,e=>{e.stopPropagation();let t=$.classList.contains(`hidden`);$.classList.toggle(`hidden`),Q.classList.toggle(`is-open`,t),Z&&(Z.classList.add(`hidden`),X?.classList.remove(`is-open`))});let e=e=>{$.classList.add(`hidden`),Q.classList.remove(`is-open`);let t=window.AppState.currentTable;if(!t){window.showToast(`Please select a table from the sidebar first.`,`error`);return}let n=document.createElement(`input`);n.type=`file`,n.accept=e===`json`?`.json`:`.csv`,n.onchange=async n=>{let r=n.target.files[0];if(!r||!confirm(`⚠️ IMPORT DATA CONFIRMATION\n\nTarget Table: ${t}\nFile: ${r.name} (${(r.size/1024).toFixed(1)} KB)\nFormat: ${e.toUpperCase()}\n\nAre you sure you want to insert records from this file into '${t}'?`))return;let i=new FormData;i.append(`file`,r),i.append(`format`,e),window.showToast(`Importing ${r.name} into '${t}'...`,`success`);try{let e=await(await fetch(`/api/tables/${encodeURIComponent(t)}/import`,{method:`POST`,body:i})).json();if(e.success){if(window.showToast(e.message||`Import completed successfully`,`success`),window.AppState.currentTab===`data-btn`){let e=`view-data-btn-${t}`,n=document.getElementById(e);n&&(n.innerHTML=``),window.renderCurrentView(``,!0)}}else window.showToast(e.error||`Import failed`,`error`)}catch{window.showToast(`Network error during data import`,`error`)}},n.click()};document.getElementById(`import-data-csv-btn`)?.addEventListener(`click`,()=>{e(`csv`)}),document.getElementById(`import-data-json-btn`)?.addEventListener(`click`,()=>{e(`json`)}),document.getElementById(`import-erd-json-btn`)?.addEventListener(`click`,()=>{$.classList.add(`hidden`),Q.classList.remove(`is-open`);let e=document.createElement(`input`);e.type=`file`,e.accept=`.json`,e.onchange=async e=>{let t=e.target.files[0];if(t&&confirm(`⚠️ IMPORT ERD LAYOUT CONFIRMATION\n\nFile: ${t.name}\n\nAre you sure you want to import this ERD layout? This will update table coordinates on your diagram canvas.`))try{let e=await t.text(),n=JSON.parse(e);if(typeof n!=`object`||!n)throw Error(`Invalid ERD layout JSON structure.`);let r=n.positions||n;localStorage.setItem(`drixio-erd-positions`,JSON.stringify(r)),window.showToast(`ERD layout imported successfully!`,`success`);let i=document.getElementById(`view-erd-btn`);i&&(i.innerHTML=``,window.AppState.currentTab===`erd-btn`&&window.renderCurrentView())}catch(e){window.showToast(`Failed to parse ERD layout: ${e.message}`,`error`)}},e.click()}),document.getElementById(`import-db-sql-btn`)?.addEventListener(`click`,()=>{$.classList.add(`hidden`),Q.classList.remove(`is-open`);let e=document.createElement(`input`);e.type=`file`,e.accept=`.sql,.json`,e.onchange=async e=>{let t=e.target.files[0];if(!t||!confirm(`🚨 HIGH RISK ACTION: DATABASE RESTORE\n\nFile: ${t.name} (${(t.size/1024).toFixed(1)} KB)\n\nExecuting this SQL script directly against the database may modify, truncate, or drop existing tables and records.\n\nAre you sure you want to proceed?`))return;let n=new FormData;n.append(`file`,t),window.showToast(`Executing ${t.name} on database...`,`success`);try{let e=await(await fetch(`/api/database/import`,{method:`POST`,body:n})).json();e.success?(window.showToast(e.message||`Database restored successfully`,`success`),setTimeout(()=>location.reload(),1500)):window.showToast(e.error||`Import failed`,`error`)}catch{window.showToast(`Network error during database import`,`error`)}},e.click()})}if(X&&Z){X.addEventListener(`click`,e=>{e.stopPropagation();let t=Z.classList.contains(`hidden`);Z.classList.toggle(`hidden`),X.classList.toggle(`is-open`,t),$&&($.classList.add(`hidden`),Q?.classList.remove(`is-open`));let n=window.AppState.currentTab===`data-btn`,r=window.AppState.currentTab===`console-btn`;document.querySelectorAll(`.data-tab-only`).forEach(e=>{e.style.display=n?`list-item`:`none`}),document.querySelectorAll(`.console-tab-only`).forEach(e=>{e.style.display=r?`list-item`:`none`})}),document.addEventListener(`click`,e=>{!X.contains(e.target)&&!Z.contains(e.target)&&(!Q||!Q.contains(e.target))&&(!$||!$.contains(e.target))&&(Z.classList.add(`hidden`),X.classList.remove(`is-open`),$&&($.classList.add(`hidden`),Q?.classList.remove(`is-open`)))});let e=(e,t,n=!1)=>{if(Z.classList.add(`hidden`),X.classList.remove(`is-open`),e===`/api/query/export`){let e=document.querySelector(`#sql-editor textarea`)||document.querySelector(`#sql-editor`),n=``;if(e&&e.value&&(n=e.value),!n&&window.ViewCache&&window.ViewCache.lastQuery&&(n=window.ViewCache.lastQuery),!n){alert(`No SQL query found to export.`);return}fetch(`/api/query/export?format=${t}`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({sql:n})}).then(e=>e.ok?e.blob():e.json().then(e=>{throw Error(e.error||`Export failed`)})).then(e=>{let n=URL.createObjectURL(e),r=document.createElement(`a`);r.href=n,r.download=`query_result_${new Date().getTime()}.${t}`,document.body.appendChild(r),r.click(),r.remove()}).catch(e=>alert(`Failed to export query: `+e.message));return}let r=`${e}?format=${t}`;if(n&&window.DataGrid){let e=z?z():``,t=window.DataGrid.sortState?.col||``,n=window.DataGrid.sortState?.asc??!0;r+=`&where=${encodeURIComponent(e)}&orderCol=${t}&orderAsc=${n}`}window.open(r,`_blank`)};document.getElementById(`export-data-csv-btn`)?.addEventListener(`click`,()=>{window.AppState.currentTable&&e(`/api/tables/${window.AppState.currentTable}/export`,`csv`)}),document.getElementById(`export-data-json-btn`)?.addEventListener(`click`,()=>{window.AppState.currentTable&&e(`/api/tables/${window.AppState.currentTable}/export`,`json`)}),document.getElementById(`export-data-filtered-csv-btn`)?.addEventListener(`click`,()=>{window.AppState.currentTable&&e(`/api/tables/${window.AppState.currentTable}/export`,`csv`,!0)}),document.getElementById(`export-data-filtered-json-btn`)?.addEventListener(`click`,()=>{window.AppState.currentTable&&e(`/api/tables/${window.AppState.currentTable}/export`,`json`,!0)}),document.getElementById(`export-schema-dict-btn`)?.addEventListener(`click`,()=>{window.open(`/api/database/dictionary`,`_blank`),Z.classList.add(`hidden`),X.classList.remove(`is-open`)}),document.getElementById(`export-schema-sql-btn`)?.addEventListener(`click`,()=>{window.open(`/api/database/schema-only`,`_blank`),Z.classList.add(`hidden`),X.classList.remove(`is-open`)}),document.getElementById(`export-schema-snapshot-btn`)?.addEventListener(`click`,()=>{window.open(`/api/schema/snapshot`,`_blank`),Z.classList.add(`hidden`),X.classList.remove(`is-open`)}),document.getElementById(`export-console-csv-btn`)?.addEventListener(`click`,()=>{e(`/api/query/export`,`csv`)}),document.getElementById(`export-console-json-btn`)?.addEventListener(`click`,()=>{e(`/api/query/export`,`json`)}),document.getElementById(`export-erd-json-btn`)?.addEventListener(`click`,()=>{let e=localStorage.getItem(`drixio_erd_drafts`);if(!e){alert(`No ERD layout found to export.`);return}let t=new Blob([e],{type:`application/json`}),n=URL.createObjectURL(t),r=document.createElement(`a`);r.href=n,r.download=`erd_layout_${new Date().getTime()}.json`,r.click(),Z.classList.add(`hidden`),X.classList.remove(`is-open`)}),document.getElementById(`export-db-sql-btn`)?.addEventListener(`click`,()=>{window.open(`/api/database/export`,`_blank`),Z.classList.add(`hidden`),X.classList.remove(`is-open`)})}window.AppState={currentTable:null,currentTab:`data-btn`,currentTableBtnElement:null,dbType:null},window.setStudioConnectionMode=function(e){let t=document.getElementById(`data-btn`),n=document.getElementById(`schema-btn`),r=document.getElementById(`erd-btn`),i=document.getElementById(`status-btn`),a=document.getElementById(`connect-btn`),o=document.getElementById(`export-btn`)?.closest(`.header-dropdown-wrap`),s=document.getElementById(`import-btn`)?.closest(`.header-dropdown-wrap`),c=document.getElementById(`slash`),l=document.getElementById(`current-table`),u=document.getElementById(`db-name`);e?(t?.classList.remove(`hidden`),n?.classList.remove(`hidden`),r?.classList.remove(`hidden`),i?.classList.remove(`hidden`),a?.classList.add(`hidden`),o&&o.classList.remove(`hidden`),s&&s.classList.remove(`hidden`),c&&c.classList.remove(`hidden`),l&&l.classList.remove(`hidden`),window.AppState.currentTab===`connect-btn`&&window.handleSwitchTab(`data-btn`)):(t?.classList.add(`hidden`),n?.classList.add(`hidden`),r?.classList.add(`hidden`),i?.classList.add(`hidden`),a?.classList.remove(`hidden`),o&&o.classList.add(`hidden`),s&&s.classList.add(`hidden`),c&&c.classList.add(`hidden`),l&&l.classList.add(`hidden`),u&&(u.textContent=`No Database`),window.AppState.currentTab!==`connect-btn`&&window.AppState.currentTab!==`sql-btn`&&window.handleSwitchTab(`connect-btn`))},a().then(e=>{if(e&&e.success&&e.data){if(window.AppState.dbType=e.data.dbType,e.data.dbName){let t=document.getElementById(`db-name`);t&&(t.textContent=e.data.dbName)}e.data.connected&&e.data.dbType&&e.data.dbType!==`none`?window.setStudioConnectionMode(!0):window.setStudioConnectionMode(!1)}else window.setStudioConnectionMode(!1)}),window.TableStates={},window.ViewCache={},window.updateSidebarDirtyState=function(){document.querySelectorAll(`.table-btn`).forEach(e=>{let t=e.dataset.table,n=e.querySelector(`span`).textContent,r=t||n.replace(/\s*\*$/,``),i=window.TableStates[r],a=!1;if(i){if(i.dataGrid){let e=i.dataGrid;(Object.keys(e.pendingEdits||{}).length>0||e.pendingInserts&&e.pendingInserts.some(e=>Object.keys(e).length>0)||e.pendingDeletes&&e.pendingDeletes.size>0)&&(a=!0)}if(i.schemaGrid){let e=i.schemaGrid,t=e.pendingIndexEdits&&(e.pendingIndexEdits.added.length>0||e.pendingIndexEdits.dropped.length>0);(Object.keys(e.pendingEdits||{}).length>0||e.pendingInserts&&e.pendingInserts.some(e=>Object.keys(e).length>0)||e.pendingDeletes&&e.pendingDeletes.size>0||t)&&(a=!0)}}let o=e.querySelector(`span`),s=o.textContent.replace(/\s*\*$/,``),c=e.querySelector(`.dirty-indicator-dot`);o.textContent=s,a?(o.style.fontWeight=`600`,c&&c.classList.remove(`hidden`)):(o.style.fontWeight=`500`,c&&c.classList.add(`hidden`))})},window.handleSwitchTab=function(e){document.querySelectorAll(`.tab-btn`).forEach(e=>e.classList.remove(`isCurrentTab`));let t=document.getElementById(e);if(t&&t.classList.add(`isCurrentTab`),window.AppState.currentTab=e,e===`erd-btn`||e===`sql-btn`||e===`status-btn`||e===`connect-btn`)window.AppState.currentTable=null,window.AppState.currentTableBtnElement=null,document.querySelectorAll(`.table-btn`).forEach(e=>e.classList.remove(`active`));else if(e===`data-btn`||e===`schema-btn`)if(window.AppState.currentTable)window.updateSidebarActiveTable?.(window.AppState.currentTable);else{let e=document.querySelector(`.table-btn`);e&&(window.AppState.currentTable=e.dataset.table||e.querySelector(`span`).textContent.replace(/\s*\*$/,``),window.AppState.currentTableBtnElement=e,e.classList.add(`active`))}window.renderCurrentView()},window.renderEmptyState=function(e){e.innerHTML=`
2277
- <div class="empty-state">
2278
- <div class="icon-container">
2279
- <span class="material-symbols-outlined">database</span>
2280
- </div>
2281
- <h2>No Table Selected</h2>
2282
- <p>Select a table from the sidebar to view its data, schema, or run SQL queries.</p>
2283
- <button type="button" id="btn-empty-create-table" class="header-btn primary" style="margin-top: 18px; padding: 8px 18px; gap: 6px; font-size: 13px; cursor: pointer;">
2284
- <span class="material-symbols-outlined" style="font-size: 18px;">add</span>
2285
- <span>Create Table</span>
2286
- </button>
2287
- </div>
2288
- `;let t=e.querySelector(`#btn-empty-create-table`);t&&(t.onclick=()=>{window.openCreateTableModal?.()})},window.renderCurrentView=function(e=``,t=!1){let n=[`erd-btn`,`sql-btn`,`status-btn`,`connect-btn`].includes(window.AppState.currentTab),r=document.getElementById(`main-content`);document.querySelectorAll(`.view-container`).forEach(e=>e.style.display=`none`);let i=n?`view-${window.AppState.currentTab}`:`view-${window.AppState.currentTab}-${window.AppState.currentTable}`,a=document.getElementById(i);if(a||(a=document.createElement(`div`),a.id=i,a.className=`view-container`,a.style.width=`100%`,a.style.flex=`1`,a.style.display=`flex`,a.style.flexDirection=`column`,a.style.overflow=`hidden`,r.appendChild(a)),a.style.display=`flex`,!window.AppState.currentTable&&!n){window.renderEmptyState(a);return}let o=window.AppState.currentTable;o&&!window.TableStates[o]&&(window.TableStates[o]={dataGrid:null,schemaGrid:null}),window.AppState.currentTab===`data-btn`?(window.TableStates[o].dataGrid&&(window.DataGrid=window.TableStates[o].dataGrid),!window.TableStates[o].dataGrid||!a.hasChildNodes()?U(o,window.AppState.currentTableBtnElement,e,t,a):window.updateSidebarActiveTable?.(o)):window.AppState.currentTab===`schema-btn`?(window.TableStates[o].schemaGrid&&(window.SchemaGrid=window.TableStates[o].schemaGrid),!window.TableStates[o].schemaGrid||!a.hasChildNodes()?Ce(o,window.AppState.currentTableBtnElement,a):window.updateSidebarActiveTable?.(o)):window.AppState.currentTab===`sql-btn`?a.hasChildNodes()||Ie(o,window.AppState.currentTableBtnElement,a):window.AppState.currentTab===`erd-btn`?a.hasChildNodes()||st(a):window.AppState.currentTab===`status-btn`?a.hasChildNodes()?mt():ht(a):window.AppState.currentTab===`connect-btn`&&(a.hasChildNodes()||yt(a));let s=document.getElementById(`table-icon`),c=document.getElementById(`table-name`);s&&c&&(o?(s.textContent=`table_chart`,c.textContent=o):window.AppState.currentTab===`erd-btn`?(s.textContent=`account_tree`,c.textContent=`ER Diagram`):window.AppState.currentTab===`sql-btn`?(s.textContent=`terminal`,c.textContent=`SQL Console`):window.AppState.currentTab===`status-btn`?(s.textContent=`monitoring`,c.textContent=`Dashboard`):window.AppState.currentTab===`connect-btn`?(s.textContent=`cable`,c.textContent=`Connect Database`):(s.textContent=`table_chart`,c.textContent=`none`)),document.querySelectorAll(`.table-btn`).forEach(e=>e.classList.remove(`active`)),window.AppState.currentTableBtnElement&&window.AppState.currentTableBtnElement.classList.add(`active`)},window.saveDataGridEdits=ne,window.saveSchemaEdits=me,document.addEventListener(`DOMContentLoaded`,()=>{k(),bt(),xt(),St();let e=document.getElementById(`header-refresh-btn`);e&&e.addEventListener(`click`,()=>{let t=e.querySelector(`.material-symbols-outlined`);t?.classList.add(`animate-spin`);let n=window.AppState.currentTab,r=window.AppState.currentTable;if(r){let e=`view-${n}-${r}`,t=document.getElementById(e);t&&(t.innerHTML=``)}window.renderCurrentView(``,!0),setTimeout(()=>{t?.classList.remove(`animate-spin`),window.showToast&&window.showToast(`Data refreshed`,`success`)},350)})});