simplemdg-dev-cli 2.5.0 → 2.7.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.
Files changed (69) hide show
  1. package/README.md +33 -0
  2. package/USER_GUIDE.md +58 -1
  3. package/dist/commands/cache.command.d.ts +2 -0
  4. package/dist/commands/cache.command.js +129 -0
  5. package/dist/commands/cache.command.js.map +1 -0
  6. package/dist/commands/cf.command.js +201 -122
  7. package/dist/commands/cf.command.js.map +1 -1
  8. package/dist/commands/gitlab.command.js +33 -23
  9. package/dist/commands/gitlab.command.js.map +1 -1
  10. package/dist/core/cache/smart-cache-events.d.ts +3 -0
  11. package/dist/core/cache/smart-cache-events.js +20 -0
  12. package/dist/core/cache/smart-cache-events.js.map +1 -0
  13. package/dist/core/cache/smart-cache-manager.d.ts +20 -0
  14. package/dist/core/cache/smart-cache-manager.js +148 -0
  15. package/dist/core/cache/smart-cache-manager.js.map +1 -0
  16. package/dist/core/cache/smart-cache-store.d.ts +8 -0
  17. package/dist/core/cache/smart-cache-store.js +74 -0
  18. package/dist/core/cache/smart-cache-store.js.map +1 -0
  19. package/dist/core/cache/smart-cache.d.ts +18 -0
  20. package/dist/core/cache/smart-cache.js +117 -0
  21. package/dist/core/cache/smart-cache.js.map +1 -0
  22. package/dist/core/cache/smart-cache.types.d.ts +62 -0
  23. package/dist/core/cache/smart-cache.types.js +17 -0
  24. package/dist/core/cache/smart-cache.types.js.map +1 -0
  25. package/dist/core/cf/cf-target-cache.d.ts +7 -0
  26. package/dist/core/cf/cf-target-cache.js +58 -0
  27. package/dist/core/cf/cf-target-cache.js.map +1 -0
  28. package/dist/core/cf/cf-target.types.d.ts +11 -0
  29. package/dist/core/cf/cf-target.types.js +11 -0
  30. package/dist/core/cf/cf-target.types.js.map +1 -0
  31. package/dist/core/db/db-studio-client.d.ts +1 -1
  32. package/dist/core/db/db-studio-client.js +250 -55
  33. package/dist/core/db/db-studio-client.js.map +1 -1
  34. package/dist/core/db/db-studio-server.js +171 -0
  35. package/dist/core/db/db-studio-server.js.map +1 -1
  36. package/dist/core/db/db-studio-styles.d.ts +1 -1
  37. package/dist/core/db/db-studio-styles.js +63 -0
  38. package/dist/core/db/db-studio-styles.js.map +1 -1
  39. package/dist/core/db/db-types.d.ts +54 -0
  40. package/dist/core/db/studio/sql-formatter.d.ts +25 -0
  41. package/dist/core/db/studio/sql-formatter.js +139 -0
  42. package/dist/core/db/studio/sql-formatter.js.map +1 -0
  43. package/dist/core/db/studio/studio-settings.d.ts +4 -0
  44. package/dist/core/db/studio/studio-settings.js +39 -0
  45. package/dist/core/db/studio/studio-settings.js.map +1 -0
  46. package/dist/core/db/studio/workspace-cache.d.ts +3 -0
  47. package/dist/core/db/studio/workspace-cache.js +51 -0
  48. package/dist/core/db/studio/workspace-cache.js.map +1 -0
  49. package/dist/index.js +3 -1
  50. package/dist/index.js.map +1 -1
  51. package/package.json +1 -1
  52. package/src/commands/cache.command.ts +159 -0
  53. package/src/commands/cf.command.ts +232 -129
  54. package/src/commands/gitlab.command.ts +37 -21
  55. package/src/core/cache/smart-cache-events.ts +20 -0
  56. package/src/core/cache/smart-cache-manager.ts +169 -0
  57. package/src/core/cache/smart-cache-store.ts +83 -0
  58. package/src/core/cache/smart-cache.ts +97 -0
  59. package/src/core/cache/smart-cache.types.ts +79 -0
  60. package/src/core/cf/cf-target-cache.ts +61 -0
  61. package/src/core/cf/cf-target.types.ts +17 -0
  62. package/src/core/db/db-studio-client.ts +250 -55
  63. package/src/core/db/db-studio-server.ts +156 -1
  64. package/src/core/db/db-studio-styles.ts +63 -0
  65. package/src/core/db/db-types.ts +61 -0
  66. package/src/core/db/studio/sql-formatter.ts +139 -0
  67. package/src/core/db/studio/studio-settings.ts +36 -0
  68. package/src/core/db/studio/workspace-cache.ts +51 -0
  69. package/src/index.ts +3 -1
@@ -26,16 +26,24 @@ var ICONS = {
26
26
  gear:"M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8z|M19.4 13a7.9 7.9 0 0 0 0-2l2-1.5-2-3.4-2.3 1a8 8 0 0 0-1.7-1l-.4-2.6h-4l-.4 2.6a8 8 0 0 0-1.7 1l-2.3-1-2 3.4L4.6 11a7.9 7.9 0 0 0 0 2l-2 1.5 2 3.4 2.3-1a8 8 0 0 0 1.7 1l.4 2.6h4l.4-2.6a8 8 0 0 0 1.7-1l2.3 1 2-3.4z",
27
27
  home:"M3 11l9-8 9 8|M5 10v10h14V10",
28
28
  table2:"M4 4h16v16H4z|M4 9h16|M9 4v16",
29
- col:"M5 4v16|M12 4v16|M19 4v16"
29
+ col:"M5 4v16|M12 4v16|M19 4v16",
30
+ trash:"M4 7h16|M9 7V4h6v3|M6 7l1 13h10l1-13|M10 11v6|M14 11v6",
31
+ chevL:"M15 6l-6 6 6 6",
32
+ chevR:"M9 6l6 6-6 6",
33
+ filter:"M3 5h18l-7 8v6l-4 2v-8z",
34
+ undo:"M9 7L4 12l5 5|M4 12h11a5 5 0 0 1 0 10h-3"
30
35
  };
31
36
  function svgFor(name){var d=ICONS[name]||"";return '<svg class="ic" viewBox="0 0 24 24">'+d.split("|").map(function(p){return '<path d="'+p+'"></path>';}).join("")+'</svg>';}
32
37
  function icEl(name,cls){var s=document.createElement("span");s.className="ticon "+(cls||"");s.innerHTML=svgFor(name);return s;}
38
+ function gbtn(icon,title,onClick,extra){var b=el("button",{class:"gbtn "+(extra||""),title:title,html:svgFor(icon)});b.addEventListener("click",function(e){onClick(e);});return b;}
33
39
 
34
40
  /* ---------- dom helpers ---------- */
35
41
  function $(id){return document.getElementById(id);}
36
42
  function el(tag,attrs,kids){var n=document.createElement(tag);if(attrs)for(var k in attrs){var v=attrs[k];if(v==null)continue;if(k==="class")n.className=v;else if(k==="text")n.textContent=v;else if(k==="html")n.innerHTML=v;else if(k.slice(0,2)==="on"&&typeof v==="function")n.addEventListener(k.slice(2),v);else n.setAttribute(k,v);}if(kids!=null){(Array.isArray(kids)?kids:[kids]).forEach(function(c){if(c==null)return;n.appendChild(typeof c==="string"||typeof c==="number"?document.createTextNode(String(c)):c);});}return n;}
37
43
  function clear(n){while(n&&n.firstChild)n.removeChild(n.firstChild);return n;}
38
44
  function esc(v){return String(v==null?"":v).replace(/[&<>"']/g,function(s){return {"&":"&amp;","<":"&lt;",">":"&gt;","\\"":"&quot;","'":"&#39;"}[s];});}
45
+ function highlightMatch(text,search){var t=String(text==null?"":text);var s=String(search==null?"":search).trim();if(!s)return esc(t);var lt=t.toLowerCase(),ls=s.toLowerCase(),out="",i=0,idx;while((idx=lt.indexOf(ls,i))>=0){out+=esc(t.slice(i,idx))+'<mark class="hl">'+esc(t.slice(idx,idx+s.length))+'</mark>';i=idx+s.length;}out+=esc(t.slice(i));return out;}
46
+ function wireSearch(input,onRun,onClear){input.addEventListener("keydown",function(e){if(e.key==="Enter"){e.preventDefault();onRun();}else if(e.key==="Escape"){e.preventDefault();input.value="";if(onClear)onClear();else onRun();}});}
39
47
  function debounce(fn,ms){var t;return function(){var a=arguments,c=this;clearTimeout(t);t=setTimeout(function(){fn.apply(c,a);},ms||220);};}
40
48
  function topSpin(on){$("topSpin").className=on?"spin":"spin hidden";}
41
49
 
@@ -50,7 +58,7 @@ function setConnStatus(text,kind){$("stConn").innerHTML="";$("stConn").appendChi
50
58
  function setRun(on){$("stConn").firstChild.className="st-dot "+(on?"run":"ok");}
51
59
 
52
60
  /* ---------- global state ---------- */
53
- var S = { connections:[], activeConnId:"", connType:"", activeSchema:"", readOnly:RO_DEFAULT, tabs:[], activeTabId:"", seq:0, savedQueries:[] };
61
+ var S = { connections:[], activeConnId:"", connType:"", activeSchema:"", readOnly:RO_DEFAULT, tabs:[], activeTabId:"", seq:0, savedQueries:[], settings:{ restoreWorkspace:true, defaultRowLimit:100, defaultSchema:"", readOnlyByDefault:RO_DEFAULT, queryTimeoutMs:30000, autoFormatGeneratedSql:true, autoSaveDelayMs:500, maxHistoryItems:300, showProductionWarning:true, theme:"dark" } };
54
62
  function activeConn(){return S.connections.filter(function(c){return c.id===S.activeConnId;})[0];}
55
63
 
56
64
  /* ====================================================================
@@ -71,10 +79,10 @@ function closeModal(){$("modalRoot").classList.add("hidden");clear($("modalRoot"
71
79
  CONNECTIONS
72
80
  ==================================================================== */
73
81
  function loadConnections(){var box=$("connList");box.innerHTML='<div class="skel"></div><div class="skel"></div>';return api("GET","/api/connections").then(function(r){S.connections=r.connections||[];renderConnections();}).catch(function(e){box.innerHTML='<div class="empty">'+esc(e.message)+'</div>';});}
74
- function renderConnections(){var q=($("connSearch").value||"").toLowerCase();var box=clear($("connList"));var rows=S.connections.filter(function(c){return (c.name+" "+c.type+" "+(c.org||"")+" "+(c.app||"")+" "+(c.environment||"")).toLowerCase().indexOf(q)>=0;});rows.sort(function(a,b){return (b.isFavorite?1:0)-(a.isFavorite?1:0);});if(!rows.length){box.appendChild(el("div",{class:"empty",text:S.connections.length?"No match.":"No connections yet. Click + New or Import."}));return;}rows.forEach(function(c){box.appendChild(connCard(c));});}
75
- function connCard(c){var color=c.color||ENV_COLORS[c.environment]||"#64748b";var card=el("div",{class:"conn-card"+(c.id===S.activeConnId?" active":""),style:"border-left-color:"+color,oncontextmenu:function(e){e.preventDefault();connMenu(e,c);}});card.addEventListener("click",function(){activateConnection(c.id);});
82
+ function renderConnections(){var raw=($("connSearch").value||"");var q=raw.toLowerCase();var box=clear($("connList"));var rows=S.connections.filter(function(c){return (c.name+" "+c.type+" "+(c.org||"")+" "+(c.app||"")+" "+(c.environment||"")).toLowerCase().indexOf(q)>=0;});rows.sort(function(a,b){return (b.isFavorite?1:0)-(a.isFavorite?1:0);});if(!rows.length){box.appendChild(el("div",{class:"empty",text:S.connections.length?"No results found":"No connections yet. Click + New or Import."}));return;}rows.forEach(function(c){box.appendChild(connCard(c,raw));});}
83
+ function connCard(c,q){var color=c.color||ENV_COLORS[c.environment]||"#64748b";var card=el("div",{class:"conn-card"+(c.id===S.activeConnId?" active":""),style:"border-left-color:"+color,oncontextmenu:function(e){e.preventDefault();connMenu(e,c);}});card.addEventListener("click",function(){activateConnection(c.id);});
76
84
  var star=el("span",{class:"star"+(c.isFavorite?" on":""),title:"Favorite",onclick:function(e){e.stopPropagation();toggleFavorite(c);}});star.innerHTML=svgFor("star");
77
- card.appendChild(el("div",{class:"conn-top"},[icEl("db","db"),el("span",{class:"conn-name",text:c.name,title:c.name}),star]));
85
+ card.appendChild(el("div",{class:"conn-top"},[icEl("db","db"),el("span",{class:"conn-name",html:highlightMatch(c.name,q),title:c.name}),star]));
78
86
  var sub=[c.org,c.space].filter(Boolean).join(" / ")||c.host;
79
87
  card.appendChild(el("div",{class:"conn-sub",text:sub,title:sub}));
80
88
  var tags=el("div",{class:"conn-tags"});
@@ -110,7 +118,7 @@ function editConnModal(c){var sel={color:c.color||"",env:c.environment||""};var
110
118
  ==================================================================== */
111
119
  function treeNode(opts){
112
120
  var chev=el("span",{class:"tchev"+(opts.leaf?" leaf":""),html:"\\u203a"});
113
- var label=el("span",{class:"tlabel",text:opts.label,title:opts.label});
121
+ var label=el("span",{class:"tlabel",title:opts.label});if(opts.labelHtml)label.innerHTML=opts.labelHtml;else label.textContent=opts.label;
114
122
  var badge=el("span",{class:"tbadge"});
115
123
  var spin=el("span",{class:"hidden"});
116
124
  var row=el("div",{class:"trow"},[chev,icEl(opts.icon,opts.iconCls),label,badge,spin]);
@@ -150,11 +158,11 @@ function folderNode(schema,label,kind,iconCls){return treeNode({label:label,icon
150
158
  var search=el("input",{class:"input",placeholder:"Search "+label.toLowerCase()+"..."});
151
159
  var sb=el("div",{class:"searchbox tsearch"},[el("span",{html:svgFor("search")}),search]);
152
160
  kids.appendChild(sb);kids.appendChild(listBox);
153
- var run=function(){var sp=el("span",{class:"spin"});clear(listBox).appendChild(el("div",{class:"tnote"},[sp," loading..."]));api("GET","/api/catalog/objects?"+qstr({connectionId:S.activeConnId,schema:schema,kinds:kind,search:search.value||""})).then(function(r){var objs=r.objects||[];setBadge(objs.length);clear(listBox);if(!objs.length){listBox.appendChild(el("div",{class:"tnote",text:"None."}));return;}objs.forEach(function(o){listBox.appendChild(objectNode(schema,o,iconCls));});}).catch(function(e){clear(listBox).appendChild(el("div",{class:"tnote",text:e.message}));});};
154
- search.addEventListener("input",debounce(run,250));
161
+ var run=function(){var sp=el("span",{class:"spin"});clear(listBox).appendChild(el("div",{class:"tnote"},[sp," loading..."]));var q=search.value||"";api("GET","/api/catalog/objects?"+qstr({connectionId:S.activeConnId,schema:schema,kinds:kind,search:q})).then(function(r){var objs=r.objects||[];setBadge(objs.length);clear(listBox);if(!objs.length){listBox.appendChild(el("div",{class:"tnote",text:q?"No results found":"None."}));return;}objs.forEach(function(o){listBox.appendChild(objectNode(schema,o,iconCls,q));});}).catch(function(e){clear(listBox).appendChild(el("div",{class:"tnote",text:e.message}));});};
162
+ search.addEventListener("input",debounce(run,250));wireSearch(search,run);
155
163
  run();
156
164
  }});}
157
- function objectNode(schema,o,iconCls){var canData=o.kind==="table"||o.kind==="view"||o.kind==="column-view";return treeNode({label:o.name,icon:iconCls,iconCls:iconCls,leaf:true,
165
+ function objectNode(schema,o,iconCls,q){var canData=o.kind==="table"||o.kind==="view"||o.kind==="column-view";return treeNode({label:o.name,labelHtml:highlightMatch(o.name,q),icon:iconCls,iconCls:iconCls,leaf:true,
158
166
  onClick:function(){selectTreeRow(this);},
159
167
  onDblClick:canData?function(){openDataTab(schema,o.name);}:null,
160
168
  onMenu:canData?function(e){objectMenu(e,schema,o);}:function(e){objectMenu(e,schema,o,true);}
@@ -163,19 +171,64 @@ var _selRow=null;
163
171
  function selectTreeRow(node){if(_selRow)_selRow._row.classList.remove("sel");_selRow=node;node._row.classList.add("sel");}
164
172
  function objectMenu(e,schema,o,limited){var qn='"'+schema+'"."'+o.name+'"';var items=[];if(!limited){items.push({label:"Open Data",icon:"table2",onClick:function(){openDataTab(schema,o.name);}});items.push({label:"Open Structure",icon:"col",onClick:function(){openStructureTab(schema,o.name);}});items.push({sep:true});items.push({label:"Generate SELECT",icon:"sql",onClick:function(){api("POST","/api/table/sql",{connectionId:S.activeConnId,schema:schema,table:o.name,limit:100}).then(function(r){openSqlTab(r.select,o.name);});}});items.push({label:"Generate COUNT",icon:"sql",onClick:function(){api("POST","/api/table/sql",{connectionId:S.activeConnId,schema:schema,table:o.name}).then(function(r){openSqlTab(r.count,o.name);});}});}
165
173
  items.push({label:"Copy Full Name",icon:"col",onClick:function(){navigator.clipboard.writeText(qn);logMsg("Copied "+qn,"ok");}});
174
+ items.push({label:"Copy Name",icon:"col",onClick:function(){navigator.clipboard.writeText(o.name);logMsg("Copied "+o.name,"ok");}});
175
+ if(!limited){items.push({sep:true});
176
+ items.push({label:"Copy SELECT",icon:"sql",onClick:function(){api("POST","/api/table/generate-sql",{connectionId:S.activeConnId,schema:schema,table:o.name,limit:100}).then(function(r){navigator.clipboard.writeText(r.select);logMsg("Copied SELECT","ok");}).catch(function(er){logMsg(er.message,"err");});}});
177
+ items.push({label:"Copy INSERT template",icon:"sql",onClick:function(){api("POST","/api/table/generate-sql",{connectionId:S.activeConnId,schema:schema,table:o.name}).then(function(r){navigator.clipboard.writeText(r.insert);logMsg("Copied INSERT template","ok");}).catch(function(er){logMsg(er.message,"err");});}});
178
+ items.push({label:"Copy UPDATE template",icon:"sql",onClick:function(){api("POST","/api/table/generate-sql",{connectionId:S.activeConnId,schema:schema,table:o.name}).then(function(r){navigator.clipboard.writeText(r.update);logMsg("Copied UPDATE template","ok");}).catch(function(er){logMsg(er.message,"err");});}});
179
+ }
166
180
  showCtx(e.clientX,e.clientY,items);}
167
181
 
168
182
  /* ====================================================================
169
183
  WORKSPACE TABS
170
184
  ==================================================================== */
171
- function renderTabBar(){var bar=clear($("tabbar"));S.tabs.forEach(function(tab){var chip=el("div",{class:"wtab"+(tab.id===S.activeTabId?" active":""),onclick:function(){switchTab(tab.id);}});chip.appendChild(el("span",{class:"t-ico",html:svgFor(tab.icon||"sql")}));chip.appendChild(el("span",{class:"t-title",text:tab.title,title:tab.title}));if(tab.dirty)chip.appendChild(el("span",{class:"dot"}));if(tab.closable!==false)chip.appendChild(el("span",{class:"x",html:svgFor("x"),onclick:function(e){e.stopPropagation();closeTab(tab.id);}}));bar.appendChild(chip);});}
172
- function openTab(spec){var ex=S.tabs.filter(function(t){return t.key===spec.key;})[0];if(ex){switchTab(ex.id);return ex;}var id="wt"+(++S.seq);var pane=el("div",{class:"tabpane hidden"});$("tabcontent").appendChild(pane);var tab={id:id,key:spec.key,kind:spec.kind,title:spec.title,icon:spec.icon,dirty:false,closable:spec.closable!==false,el:pane,state:{}};S.tabs.push(tab);spec.build(pane,tab);renderTabBar();switchTab(id);return tab;}
173
- function switchTab(id){S.activeTabId=id;S.tabs.forEach(function(t){t.el.classList.toggle("hidden",t.id!==id);});renderTabBar();var tab=tabById(id);if(tab&&tab.onShow)tab.onShow();updatePendingStatus();}
185
+ var _dragTabId=null;
186
+ function orderedTabs(){return S.tabs.slice().sort(function(a,b){return (b.pinned?1:0)-(a.pinned?1:0);});}
187
+ function renderTabBar(){var bar=clear($("tabbar"));orderedTabs().forEach(function(tab){var chip=el("div",{class:"wtab"+(tab.id===S.activeTabId?" active":"")+(tab.pinned?" pinned":""),draggable:"true"});chip.addEventListener("click",function(){switchTab(tab.id);});chip.addEventListener("contextmenu",function(e){e.preventDefault();tabMenu(e,tab);});
188
+ chip.addEventListener("dragstart",function(e){_dragTabId=tab.id;chip.classList.add("dragging");if(e.dataTransfer)e.dataTransfer.effectAllowed="move";});
189
+ chip.addEventListener("dragend",function(){chip.classList.remove("dragging");Array.prototype.forEach.call(bar.children,function(x){x.classList.remove("dragover");});});
190
+ chip.addEventListener("dragover",function(e){e.preventDefault();chip.classList.add("dragover");});
191
+ chip.addEventListener("dragleave",function(){chip.classList.remove("dragover");});
192
+ chip.addEventListener("drop",function(e){e.preventDefault();chip.classList.remove("dragover");if(_dragTabId&&_dragTabId!==tab.id)reorderTab(_dragTabId,tab.id);});
193
+ chip.appendChild(el("span",{class:"t-ico"+(tab.pinned?" pin":""),html:svgFor(tab.pinned?"star":(tab.icon||"sql"))}));
194
+ chip.appendChild(el("span",{class:"t-title",text:tab.title,title:tab.title}));
195
+ if(tab.dirty)chip.appendChild(el("span",{class:"dot"}));
196
+ if(tab.closable!==false)chip.appendChild(el("span",{class:"x",html:svgFor("x"),onclick:function(e){e.stopPropagation();closeTab(tab.id);}}));
197
+ bar.appendChild(chip);});}
198
+ function reorderTab(srcId,targetId){var src=tabById(srcId);var ti=-1;for(var i=0;i<S.tabs.length;i++)if(S.tabs[i].id===targetId)ti=i;if(!src||ti<0)return;S.tabs=S.tabs.filter(function(t){return t.id!==srcId;});var idx=-1;for(var j=0;j<S.tabs.length;j++)if(S.tabs[j].id===targetId)idx=j;S.tabs.splice(idx,0,src);renderTabBar();scheduleWorkspaceSave();}
199
+ function tabMenu(e,tab){showCtx(e.clientX,e.clientY,[
200
+ {label:"Close",icon:"x",onClick:function(){closeTab(tab.id);}},
201
+ {label:"Close Others",icon:"x",onClick:function(){closeOtherTabs(tab.id);}},
202
+ {label:"Close Tabs to the Right",icon:"x",onClick:function(){closeTabsToRight(tab.id);}},
203
+ {sep:true},
204
+ {label:tab.pinned?"Unpin Tab":"Pin Tab",icon:"star",onClick:function(){tab.pinned=!tab.pinned;renderTabBar();scheduleWorkspaceSave();}},
205
+ {label:"Rename Tab",icon:"gear",onClick:function(){var n=prompt("Tab name",tab.title);if(n){tab.title=n;renderTabBar();scheduleWorkspaceSave();}}},
206
+ {label:"Duplicate Tab",icon:"plus",onClick:function(){duplicateTab(tab);}}
207
+ ]);}
208
+ function duplicateTab(tab){if(tab.kind==="sql"&&tab.state.editor)openSqlTab(tab.state.editor.value,tab.title);else if(tab.kind==="data"&&tab.meta)openDataTab(tab.meta.schema,tab.meta.table);else if(tab.kind==="structure"&&tab.meta)openStructureTab(tab.meta.schema,tab.meta.table);}
209
+ function closeOtherTabs(keepId){var others=S.tabs.filter(function(t){return t.id!==keepId&&t.closable!==false;});if(others.some(function(t){return t.dirty;})&&!confirm("Close other tabs? Unsaved changes will be lost."))return;others.forEach(function(t){t.el.remove();});S.tabs=S.tabs.filter(function(t){return t.id===keepId||t.closable===false;});switchTab(keepId);renderTabBar();scheduleWorkspaceSave();}
210
+ function closeTabsToRight(fromId){var ord=orderedTabs();var i=0;for(var k=0;k<ord.length;k++)if(ord[k].id===fromId)i=k;var toClose=ord.slice(i+1).filter(function(t){return t.closable!==false;});if(toClose.some(function(t){return t.dirty;})&&!confirm("Close tabs to the right? Unsaved changes will be lost."))return;var ids={};toClose.forEach(function(t){ids[t.id]=1;t.el.remove();});S.tabs=S.tabs.filter(function(t){return !ids[t.id];});if(ids[S.activeTabId])switchTab(fromId);renderTabBar();scheduleWorkspaceSave();}
211
+ function nextTab(dir){var ord=orderedTabs();if(ord.length<2)return;var i=0;for(var k=0;k<ord.length;k++)if(ord[k].id===S.activeTabId)i=k;var ni=(i+dir+ord.length)%ord.length;switchTab(ord[ni].id);}
212
+ function openTab(spec){var ex=S.tabs.filter(function(t){return t.key===spec.key;})[0];if(ex){switchTab(ex.id);return ex;}var id=spec.restoreId||("wt"+(++S.seq));var pane=el("div",{class:"tabpane hidden"});$("tabcontent").appendChild(pane);var now=new Date().toISOString();var tab={id:id,key:spec.key,kind:spec.kind,title:spec.title,icon:spec.icon,dirty:false,closable:spec.closable!==false,el:pane,state:{},connectionId:spec.connectionId||"",meta:spec.meta||null,pinned:!!spec.pinned,openedAt:now};S.tabs.push(tab);spec.build(pane,tab);renderTabBar();switchTab(id);scheduleWorkspaceSave();return tab;}
213
+ function switchTab(id){S.activeTabId=id;S.tabs.forEach(function(t){t.el.classList.toggle("hidden",t.id!==id);});renderTabBar();var tab=tabById(id);if(tab&&tab.onShow)tab.onShow();updatePendingStatus();scheduleWorkspaceSave();}
174
214
  function tabById(id){return S.tabs.filter(function(t){return t.id===id;})[0];}
175
215
  function setDirty(tab,on){tab.dirty=on;renderTabBar();updatePendingStatus();}
176
- function closeTab(id){var tab=tabById(id);if(!tab)return;if(tab.dirty&&!confirm("'"+tab.title+"' has unsaved changes. Close anyway?"))return;tab.el.remove();var idx=S.tabs.indexOf(tab);S.tabs=S.tabs.filter(function(t){return t.id!==id;});if(S.activeTabId===id){var next=S.tabs[Math.max(0,idx-1)];if(next)switchTab(next.id);else openWelcome();}renderTabBar();}
216
+ function closeTab(id){var tab=tabById(id);if(!tab)return;if(tab.dirty&&!confirm("'"+tab.title+"' has unsaved changes. Close anyway?"))return;tab.el.remove();var idx=S.tabs.indexOf(tab);S.tabs=S.tabs.filter(function(t){return t.id!==id;});if(S.activeTabId===id){var next=S.tabs[Math.max(0,idx-1)];if(next)switchTab(next.id);else openWelcome();}renderTabBar();scheduleWorkspaceSave();}
177
217
  function updatePendingStatus(){var tab=tabById(S.activeTabId);var n=tab&&tab.state&&tab.state.g?pendingCount(tab.state.g):0;$("stPending").textContent=n>0?(n+" pending change"+(n>1?"s":"")):"";$("stPending").className=n>0?"st-item st-pending":"st-item";}
178
218
 
219
+ /* ---- workspace persistence ---- */
220
+ function kindToType(k){return k==="data"?"data-grid":k==="structure"?"metadata":k;}
221
+ function serializeWorkspace(){var tabs=S.tabs.filter(function(t){return t.kind!=="welcome";}).map(function(t){var st={id:t.id,type:kindToType(t.kind),title:t.title,pinned:!!t.pinned,dirty:!!t.dirty,connectionId:t.connectionId||undefined,openedAt:t.openedAt||new Date().toISOString(),updatedAt:new Date().toISOString()};if(t.meta){st.schema=t.meta.schema;st.objectName=t.meta.table;st.objectType=t.meta.objectType;}if(t.kind==="sql"&&t.state.editor)st.sql=t.state.editor.value;if(t.kind==="data"&&t.state.g){var g=t.state.g;st.filter=g.whereI.value;st.pageSize=parseInt(g.pageSel.value,10);st.pageIndex=g.offset;st.sort=g.orderBy?[{column:g.orderBy,direction:g.orderDir}]:[];}return st;});return {version:1,activeTabId:S.activeTabId,tabs:tabs,tabGroups:[],layout:{readOnly:S.readOnly,sidebarWidth:$("sidebar").offsetWidth},updatedAt:new Date().toISOString()};}
222
+ var _wsTimer=null;
223
+ function scheduleWorkspaceSave(){if(_wsTimer)clearTimeout(_wsTimer);_wsTimer=setTimeout(function(){api("PUT","/api/studio/workspace",serializeWorkspace()).catch(function(){});},Math.max(400,S.settings.autoSaveDelayMs||500));}
224
+ function loadSettings(){return api("GET","/api/studio/settings").then(function(r){if(r.settings)S.settings=r.settings;}).catch(function(){});}
225
+ function restoreWorkspace(){if(!S.settings.restoreWorkspace)return Promise.resolve();return api("GET","/api/studio/workspace").then(function(r){var ws=r.workspace;if(!ws||!ws.tabs||!ws.tabs.length)return;var active=null;ws.tabs.forEach(function(st){try{
226
+ if(st.type==="sql"){if(st.connectionId){var c=byId(st.connectionId);if(c){S.activeConnId=c.id;S.connType=c.type;}}var t=openSqlTab(st.sql||"",null);t.title=st.title;t.pinned=!!st.pinned;if(st.id===ws.activeTabId)active=t.id;}
227
+ else if(st.type==="data-grid"){var cd=byId(st.connectionId);if(!cd){logMsg("Tab '"+st.title+"' not restored: connection removed.","warn");return;}S.activeConnId=cd.id;S.connType=cd.type;var dt=openDataTab(st.schema,st.objectName,{where:st.filter,pageSize:st.pageSize,sort:st.sort,offset:st.pageIndex});dt.pinned=!!st.pinned;if(st.id===ws.activeTabId)active=dt.id;}
228
+ else if(st.type==="metadata"){var cm=byId(st.connectionId);if(!cm)return;S.activeConnId=cm.id;S.connType=cm.type;var mt=openStructureTab(st.schema,st.objectName);mt.pinned=!!st.pinned;if(st.id===ws.activeTabId)active=mt.id;}
229
+ }catch(e){}});updateTopBadges();renderTabBar();if(active)switchTab(active);logMsg("Workspace restored ("+ws.tabs.length+" tab"+(ws.tabs.length>1?"s":"")+").","ok");}).catch(function(){});}
230
+ function byId(id){return S.connections.filter(function(c){return c.id===id;})[0];}
231
+
179
232
  /* ====================================================================
180
233
  WELCOME
181
234
  ==================================================================== */
@@ -199,20 +252,29 @@ function wcard(icon,title,desc,onClick){return el("div",{class:"wcard",onclick:o
199
252
  SQL CONSOLE TAB
200
253
  ==================================================================== */
201
254
  var DANGER=/\\b(drop|truncate|alter|grant|revoke)\\b/i;
202
- function openSqlTab(sql,nameHint){var title="SQL"+(nameHint?": "+nameHint:" Console");openTab({key:"sql:"+(++S.seq),kind:"sql",title:title,icon:"sql",build:function(pane,tab){buildSqlPane(pane,tab,sql||"select * from DUMMY");}});}
255
+ function openSqlTab(sql,nameHint,queryId){var title="SQL"+(nameHint?": "+nameHint:" Console");return openTab({key:"sql:"+(++S.seq),kind:"sql",title:title,icon:"sql",connectionId:S.activeConnId,meta:{queryId:queryId||null},build:function(pane,tab){tab.connectionId=S.activeConnId;buildSqlPane(pane,tab,sql!=null?sql:"select * from DUMMY");}});}
203
256
  function buildSqlPane(pane,tab,initialSql){
204
257
  var editor=el("textarea",{class:"editor",spellcheck:"false"});editor.value=initialSql;tab.state.editor=editor;
205
- editor.addEventListener("input",function(){setDirty(tab,true);});
206
- editor.addEventListener("keydown",function(e){if((e.ctrlKey||e.metaKey)&&e.key==="Enter"){e.preventDefault();runSqlTab(tab);}});
207
- var limitSel=el("select",{class:"select",style:"width:auto"});["100","500","1000","5000","0"].forEach(function(v){limitSel.appendChild(el("option",{value:v,text:v==="0"?"No limit":v}));});tab.state.limit=limitSel;
208
- var runBtn=el("button",{class:"btn",onclick:function(){runSqlTab(tab);}},[el("span",{html:svgFor("run")})," Run"]);tab.state.runBtn=runBtn;
209
- var tb=el("div",{class:"toolbar"},[runBtn,el("button",{class:"btn sec",text:"Format",onclick:function(){editor.value=formatSql(editor.value);}}),el("button",{class:"btn ghost",text:"Explain",onclick:function(){explainSqlTab(tab);}}),el("span",{class:"note",text:"Limit"}),limitSel,el("button",{class:"btn ghost",text:"Save",onclick:function(){saveQueryTab(tab);}}),el("button",{class:"btn ghost",text:"CSV",onclick:function(){exportResult(tab,"csv");}}),el("button",{class:"btn ghost",text:"JSON",onclick:function(){exportResult(tab,"json");}}),el("span",{class:"grow"}),el("span",{class:"note",id:"sqlmeta_"+tab.id})]);
258
+ var gutter=el("div",{class:"gutter",text:"1"});
259
+ function syncGutter(){var lines=editor.value.split("\\n").length;var s="";for(var i=1;i<=lines;i++)s+=i+"\\n";gutter.textContent=s;gutter.scrollTop=editor.scrollTop;}
260
+ tab.state.syncGutter=syncGutter;
261
+ editor.addEventListener("input",function(){setDirty(tab,true);syncGutter();scheduleWorkspaceSave();});
262
+ editor.addEventListener("scroll",function(){gutter.scrollTop=editor.scrollTop;});
263
+ editor.addEventListener("keydown",function(e){if((e.ctrlKey||e.metaKey)&&e.key==="Enter"){e.preventDefault();runMode(tab,"selected");}else if(e.key==="F5"){e.preventDefault();runMode(tab,"all");}else if((e.ctrlKey||e.metaKey)&&(e.key==="s"||e.key==="S")){e.preventDefault();saveQueryTab(tab);}});
264
+ var editwrap=el("div",{class:"editwrap"},[gutter,editor]);
265
+ var limitSel=el("select",{class:"select",style:"width:auto"});["100","500","1000","5000","0"].forEach(function(v){limitSel.appendChild(el("option",{value:v,text:v==="0"?"No limit":v}));});limitSel.value=String(S.settings.defaultRowLimit||100);tab.state.limit=limitSel;
266
+ var runBtn=el("button",{class:"btn",onclick:function(){runMode(tab,"selected");}},[el("span",{html:svgFor("run")})," Run"]);tab.state.runBtn=runBtn;
267
+ var runMenu=el("button",{class:"btn",style:"padding:6px 7px",html:"\\u25be",title:"Run options",onclick:function(e){showCtx(e.clientX,e.clientY,[{label:"Run Selected (Ctrl+Enter)",icon:"run",onClick:function(){runMode(tab,"selected");}},{label:"Run Current Statement",icon:"run",onClick:function(){runMode(tab,"current");}},{label:"Run All (F5)",icon:"run",onClick:function(){runMode(tab,"all");}},{label:"Explain",icon:"sql",onClick:function(){runMode(tab,"explain");}}]);}});
268
+ var tb=el("div",{class:"toolbar"},[runBtn,runMenu,el("button",{class:"btn sec",text:"Format",onclick:function(){api("POST","/api/sql/format",{sql:editor.value}).then(function(r){setEditorValue(tab,r.sql);}).catch(function(){setEditorValue(tab,formatSql(editor.value));});}}),el("span",{class:"note",text:"Limit"}),limitSel,el("button",{class:"btn ghost",text:"Save",title:"Ctrl+S",onclick:function(){saveQueryTab(tab);}}),el("button",{class:"btn ghost",text:"CSV",onclick:function(){exportResult(tab,"csv");}}),el("button",{class:"btn ghost",text:"JSON",onclick:function(){exportResult(tab,"json");}}),el("span",{class:"grow"}),el("span",{class:"note",id:"sqlmeta_"+tab.id})]);
210
269
  var body=el("div",{class:"pane-body"});var errBox=el("div",{class:"errbox hidden"});var grid=el("div",{class:"gridwrap"});tab.state.err=errBox;tab.state.grid=grid;
211
- body.appendChild(editor);body.appendChild(errBox);body.appendChild(el("div",{class:"note",text:"Result"}));body.appendChild(grid);
212
- pane.appendChild(tb);pane.appendChild(body);
270
+ body.appendChild(editwrap);body.appendChild(errBox);body.appendChild(el("div",{class:"note",text:"Result"}));body.appendChild(grid);
271
+ pane.appendChild(tb);pane.appendChild(body);syncGutter();
213
272
  }
214
- function currentSqlText(tab){var ed=tab.state.editor;if(ed.selectionStart!=ed.selectionEnd){var s=ed.value.substring(ed.selectionStart,ed.selectionEnd).trim();if(s)return s;}return ed.value.trim();}
215
- function runSqlTab(tab,confirmed){var sql=currentSqlText(tab);if(!sql)return logMsg("Editor is empty.","warn");if(!S.activeConnId)return logMsg("Select a connection first.","warn");
273
+ function setEditorValue(tab,v){tab.state.editor.value=v;if(tab.state.syncGutter)tab.state.syncGutter();setDirty(tab,true);scheduleWorkspaceSave();}
274
+ function splitRangesClient(sql){var ranges=[],buf="",start=-1,inStr=false,q="",inLine=false,inBlock=false;function push(e){if(buf.trim())ranges.push({sql:buf.trim(),start:start,end:e});buf="";start=-1;}for(var i=0;i<sql.length;i++){var ch=sql[i],nx=sql[i+1];if(start===-1&&!/\\s/.test(ch))start=i;if(inLine){buf+=ch;if(ch==="\\n")inLine=false;continue;}if(inBlock){buf+=ch;if(ch==="*"&&nx==="/"){buf+=nx;i++;inBlock=false;}continue;}if(inStr){buf+=ch;if(ch===q)inStr=false;continue;}if(ch==="-"&&nx==="-"){inLine=true;buf+=ch;continue;}if(ch==="/"&&nx==="*"){inBlock=true;buf+=ch;continue;}if(ch==="'"||ch==='"'){inStr=true;q=ch;buf+=ch;continue;}if(ch===";"){push(i);continue;}buf+=ch;}push(sql.length);return ranges;}
275
+ function statementAtCursor(sql,off){var r=splitRangesClient(sql);for(var i=0;i<r.length;i++)if(off>=r[i].start&&off<=r[i].end+1)return r[i].sql;return (r[r.length-1]||{}).sql||sql.trim();}
276
+ function runMode(tab,mode){var ed=tab.state.editor;var hasSel=ed.selectionStart!=ed.selectionEnd;var sel=hasSel?ed.value.substring(ed.selectionStart,ed.selectionEnd).trim():"";var sql;if(mode==="all")sql=ed.value.trim();else if(mode==="current")sql=statementAtCursor(ed.value,ed.selectionStart);else if(mode==="explain"){if(S.connType!=="postgresql")return logMsg("Explain currently supports PostgreSQL.","warn");sql="EXPLAIN "+(sel||statementAtCursor(ed.value,ed.selectionStart));}else sql=sel||statementAtCursor(ed.value,ed.selectionStart);if(!sql)return logMsg("Nothing to run.","warn");execSql(tab,sql);}
277
+ function execSql(tab,sql,confirmed){if(!S.activeConnId)return logMsg("Select a connection first.","warn");
216
278
  if(!confirmed&&DANGER.test(sql)){if(!confirm("This statement may modify or drop data:\\n\\n"+sql.slice(0,160)+"\\n\\nRun anyway?"))return;}
217
279
  tab.state.err.classList.add("hidden");tab.state.runBtn.disabled=true;tab.state.runBtn.innerHTML="";tab.state.runBtn.appendChild(el("span",{class:"spin"}));tab.state.runBtn.appendChild(document.createTextNode(" Running..."));setRun(true);setConnStatus("Running query...","run");
218
280
  var limit=parseInt(tab.state.limit.value,10);
@@ -225,49 +287,65 @@ function runSqlTab(tab,confirmed){var sql=currentSqlText(tab);if(!sql)return log
225
287
  $("stDuration").textContent=r.result.durationMs+"ms";$("stRows").textContent=r.result.rowCount+" rows";
226
288
  }).catch(function(e){tab.state.runBtn.innerHTML=svgFor("run")+" Run";tab.state.runBtn.disabled=false;setConnStatus("Connected","ok");tab.state.err.textContent=e.message;tab.state.err.classList.remove("hidden");});
227
289
  }
228
- function explainSqlTab(tab){if(S.connType!=="postgresql")return logMsg("Explain currently supports PostgreSQL.","warn");tab.state.editor.value="EXPLAIN "+currentSqlText(tab);runSqlTab(tab,true);}
229
- function saveQueryTab(tab){var sql=tab.state.editor.value.trim();if(!sql)return logMsg("Nothing to save.","warn");var name=prompt("Query name","Query "+new Date().toLocaleString());if(!name)return;api("POST","/api/queries",{name:name,sql:sql,connectionId:S.activeConnId,connectionType:S.connType}).then(function(){setDirty(tab,false);loadSavedQueries();logMsg("Query saved.","ok");}).catch(function(e){logMsg(e.message,"err");});}
290
+ function saveQueryTab(tab){var sql=tab.state.editor.value.trim();if(!sql)return logMsg("Nothing to save.","warn");var qid=tab.meta&&tab.meta.queryId;if(qid){api("PUT","/api/queries/"+encodeURIComponent(qid),{name:tab.title.replace(/^SQL: ?/,""),sql:sql}).then(function(){setDirty(tab,false);loadSavedQueries();logMsg("Query updated.","ok");}).catch(function(e){logMsg(e.message,"err");});return;}var name=prompt("Save query as","Query "+new Date().toLocaleString());if(!name)return;api("POST","/api/queries",{name:name,sql:sql,connectionId:S.activeConnId,connectionType:S.connType}).then(function(r){if(tab.meta)tab.meta.queryId=r.query.id;tab.title="SQL: "+name;setDirty(tab,false);renderTabBar();loadSavedQueries();logMsg("Query saved.","ok");}).catch(function(e){logMsg(e.message,"err");});}
230
291
  function exportResult(tab,fmt){var res=tab.state.lastResult;if(!res||!res.rows.length)return logMsg("No result to export.","warn");var fields=res.fields&&res.fields.length?res.fields:Object.keys(res.rows[0]);fetch(fmt==="csv"?"/api/export/csv":"/api/export/json",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({fields:fields,rows:res.rows})}).then(function(r){return r.blob();}).then(function(b){var a=document.createElement("a");a.href=URL.createObjectURL(b);a.download=fmt==="csv"?"result.csv":"result.json";a.click();logMsg("Exported "+fmt.toUpperCase(),"ok");});}
231
292
  function formatSql(sql){return sql.replace(/\\s+/g," ").replace(/\\b(select|from|where|and|or|order by|group by|having|limit|offset|inner join|left join|right join|join|on|union|values|set|insert into|update|delete from|create table|alter table)\\b/gi,function(m){return "\\n"+m.toUpperCase();}).trim();}
232
293
 
233
294
  /* generic read-only result grid (sql console) */
234
- function renderResultGrid(box,result,onSort){clear(box);if(!result||!result.rows||!result.rows.length){box.appendChild(el("div",{class:"empty",text:result&&result.affectedRows!=null?("Affected rows: "+result.affectedRows):"No rows."}));return;}var fields=result.fields&&result.fields.length?result.fields:Object.keys(result.rows[0]);var table=el("table",{class:"grid"});var thead=el("thead");var htr=el("tr");htr.appendChild(el("th",{class:"rowhdr",text:"#"}));fields.forEach(function(f){htr.appendChild(el("th",{text:f,title:f}));});thead.appendChild(htr);table.appendChild(thead);var tb=el("tbody");result.rows.forEach(function(row,ri){var tr=el("tr");tr.appendChild(el("td",{class:"rowhdr",text:ri+1}));fields.forEach(function(f){var v=row[f];var disp=v==null?"":typeof v==="object"?JSON.stringify(v):String(v);var td=el("td",{class:typeof v==="number"?"num":"",title:disp,text:disp.length>400?disp.slice(0,400)+"\\u2026":disp});td.addEventListener("dblclick",function(){navigator.clipboard.writeText(disp);setConnStatus("Cell copied","ok");});tr.appendChild(td);});tb.appendChild(tr);});table.appendChild(tb);box.appendChild(table);}
295
+ function renderResultGrid(box,result,onSort){clear(box);if(!result||!result.rows||!result.rows.length){box.appendChild(el("div",{class:"empty",text:result&&result.affectedRows!=null?("Affected rows: "+result.affectedRows):"No rows."}));return;}var fields=result.fields&&result.fields.length?result.fields:Object.keys(result.rows[0]);var table=el("table",{class:"grid"});var thead=el("thead");var htr=el("tr");htr.appendChild(el("th",{class:"rowhdr",text:"#"}));fields.forEach(function(f){htr.appendChild(el("th",{text:f,title:f}));});thead.appendChild(htr);table.appendChild(thead);var tb=el("tbody");result.rows.forEach(function(row,ri){var tr=el("tr");tr.appendChild(el("td",{class:"rowhdr",text:ri+1}));fields.forEach(function(f){var v=row[f];var disp=v==null?"":typeof v==="object"?JSON.stringify(v):String(v);var td=el("td",{class:typeof v==="number"?"num":"",title:disp,text:disp.length>400?disp.slice(0,400)+"\\u2026":disp});td.addEventListener("dblclick",function(){openCellViewer(v);});tr.appendChild(td);});tb.appendChild(tr);});table.appendChild(tb);box.appendChild(table);}
235
296
 
236
297
  /* ====================================================================
237
298
  DATA GRID TAB (editable, pending changes)
238
299
  ==================================================================== */
239
- function openDataTab(schema,table){openTab({key:"data:"+S.activeConnId+":"+schema+"."+table,kind:"data",title:table,icon:"table2",build:function(pane,tab){buildDataPane(pane,tab,schema,table);}});}
300
+ function openDataTab(schema,table,restore){return openTab({key:"data:"+S.activeConnId+":"+schema+"."+table,kind:"data",title:table,icon:"table2",connectionId:S.activeConnId,meta:{schema:schema,table:table,objectType:"table"},build:function(pane,tab){tab.connectionId=S.activeConnId;buildDataPane(pane,tab,schema,table,restore);}});}
301
+ var _pop=null;
302
+ function showPopover(node,anchor){closePop();_pop=node;node.style.position="fixed";document.body.appendChild(node);var r=anchor.getBoundingClientRect();node.style.left=Math.min(r.left,window.innerWidth-node.offsetWidth-10)+"px";node.style.top=(r.bottom+6)+"px";setTimeout(function(){document.addEventListener("mousedown",popOutside,true);},0);}
303
+ function popOutside(e){if(_pop&&!_pop.contains(e.target))closePop();}
304
+ function closePop(){if(_pop){_pop.remove();_pop=null;document.removeEventListener("mousedown",popOutside,true);}}
305
+ function showFilterSql(tab,anchor){var g=tab.state.g;api("POST","/api/sql/generate-table-query",{connectionId:S.activeConnId,schema:g.schema,table:g.table,where:g.whereI.value||"",sort:g.orderBy?[{column:g.orderBy,direction:g.orderDir}]:[],limit:parseInt(g.pageSel.value,10),offset:g.offset}).then(function(r){var pre=el("pre",{text:r.sql});var pop=el("div",{class:"popover"},[el("div",{class:"row",style:"margin-bottom:8px"},[el("b",{text:"Generated SQL"}),el("span",{style:"flex:1"}),el("button",{class:"btn sm sec",text:"Copy",onclick:function(){navigator.clipboard.writeText(r.sql);logMsg("Copied SQL","ok");}}),el("button",{class:"btn sm",text:"Open in Console",onclick:function(){closePop();openSqlTab(r.sql,g.table);}}),el("button",{class:"btn sm ghost",text:"Close",onclick:closePop})]),pre]);showPopover(pop,anchor);}).catch(function(e){logMsg(e.message,"err");});}
240
306
  function pendingCount(g){return Object.keys(g.edits).length+Object.keys(g.deletes).length+g.inserts.length;}
241
307
  function rowKeyOf(g,row){return g.pk.map(function(k){return String(row[k]);}).join("\\u0001");}
242
- function buildDataPane(pane,tab,schema,table){
243
- var g={schema:schema,table:table,pk:[],columns:[],rows:[],offset:0,pageSize:100,where:"",orderBy:"",orderDir:"asc",total:null,edits:{},deletes:{},inserts:[],errors:{},editable:false,selRow:null,iseq:0};tab.state.g=g;
244
- var whereI=el("input",{class:"input",style:"flex:1",placeholder:"WHERE clause, e.g. STATUS = 'A'"});g.whereI=whereI;
245
- var pageSel=el("select",{class:"select",style:"width:auto"});["100","500","1000"].forEach(function(v){pageSel.appendChild(el("option",{value:v,text:v}));});g.pageSel=pageSel;
246
- var pageInfo=el("span",{class:"note"});g.pageInfo=pageInfo;
247
- var tb1=el("div",{class:"toolbar"},[whereI,el("button",{class:"btn",text:"Apply",onclick:function(){g.where=whereI.value;g.offset=0;loadData(tab);}}),el("button",{class:"btn sec",text:"Refresh",onclick:function(){loadData(tab);}}),el("span",{class:"note",text:"Page"}),pageSel,el("button",{class:"btn sec",text:"Prev",onclick:function(){g.offset=Math.max(0,g.offset-parseInt(pageSel.value,10));loadData(tab);}}),el("button",{class:"btn sec",text:"Next",onclick:function(){g.offset+=parseInt(pageSel.value,10);loadData(tab);}}),pageInfo,el("span",{class:"grow"}),el("button",{class:"btn ghost",text:"CSV",onclick:function(){exportData(tab,"csv");}}),el("button",{class:"btn ghost",text:"JSON",onclick:function(){exportData(tab,"json");}})]);
248
- var saveBtn=el("button",{class:"btn",text:"Save changes",onclick:function(){saveDataChanges(tab);}});var revertBtn=el("button",{class:"btn ghost",text:"Revert all",onclick:function(){revertAll(tab);}});g.saveBtn=saveBtn;g.revertBtn=revertBtn;
249
- var editHint=el("span",{class:"note"});g.editHint=editHint;
250
- var tb2=el("div",{class:"toolbar"},[el("button",{class:"btn sec",text:"+ Insert row",onclick:function(){addInsertRow(tab);}}),el("button",{class:"btn ghost",text:"Delete selected",onclick:function(){toggleDeleteSelected(tab);}}),el("button",{class:"btn ghost",text:"Structure",onclick:function(){openStructureTab(schema,table);}}),saveBtn,revertBtn,editHint]);
308
+ function buildDataPane(pane,tab,schema,table,restore){
309
+ var g={schema:schema,table:table,pk:[],columns:[],rows:[],offset:0,pageSize:100,where:"",orderBy:"",orderDir:"asc",total:null,edits:{},deletes:{},inserts:[],errors:{},editable:false,sel:{},iseq:0,undo:[],redo:[]};tab.state.g=g;
310
+ var whereI=el("input",{spellcheck:"false",placeholder:"WHERE clause, e.g. STATUS = 'A' AND CREATEDBY LIKE '%admin%'"});g.whereI=whereI;
311
+ var clr=el("span",{class:"clr",html:svgFor("x"),title:"Clear filter"});
312
+ var whereBox=el("div",{class:"wherebox"},[el("span",{html:svgFor("filter")}),whereI,clr]);
313
+ var apply=function(){g.where=whereI.value;g.offset=0;applyBtn.classList.remove("on");loadData(tab);};
314
+ var applyBtn=gbtn("run","Apply filter (Enter)",apply);g.applyBtn=applyBtn;
315
+ clr.addEventListener("click",function(){whereI.value="";whereBox.classList.remove("has");apply();});
316
+ whereI.addEventListener("input",function(){whereBox.classList.toggle("has",!!whereI.value);applyBtn.classList.toggle("on",whereI.value!==g.where);});
317
+ whereI.addEventListener("keydown",function(e){if(e.key==="Enter"){e.preventDefault();apply();if(e.ctrlKey||e.metaKey)showFilterSql(tab,applyBtn);}else if(e.key==="Escape"){e.preventDefault();whereI.value="";whereBox.classList.remove("has");apply();}});
318
+ var insBtn=gbtn("plus","Insert row",function(){addInsertRow(tab);});g.insBtn=insBtn;
319
+ var delBtn=gbtn("trash","Mark selected rows for delete",function(){toggleDeleteSelected(tab);},"danger");g.delBtn=delBtn;
320
+ var tb=el("div",{class:"gtoolbar"},[whereBox,applyBtn,gbtn("sql","Show generated SQL",function(e){showFilterSql(tab,e.currentTarget);}),gbtn("refresh","Refresh data",function(e){var b=e.currentTarget;b.classList.add("spinning");loadData(tab,function(){b.classList.remove("spinning");});}),el("span",{class:"gsep"}),insBtn,delBtn,gbtn("col","Open structure",function(){openStructureTab(schema,table);}),el("span",{class:"gsep"}),gbtn("imp","Export data",function(e){openExportMenu(tab,e.currentTarget);})]);
321
+ var changeBar=el("div",{class:"changebar hidden"});g.changeBar=changeBar;
251
322
  var grid=el("div",{class:"gridwrap"});g.grid=grid;
252
- pane.appendChild(tb1);pane.appendChild(tb2);pane.appendChild(grid);
323
+ var pageSel=el("select");["100","500","1000"].forEach(function(v){pageSel.appendChild(el("option",{value:v,text:v}));});g.pageSel=pageSel;
324
+ pageSel.addEventListener("change",function(){g.offset=0;loadData(tab);});
325
+ var rangeSpan=el("span",{class:"note"});g.rangeSpan=rangeSpan;var durSpan=el("span",{class:"note"});g.durSpan=durSpan;
326
+ var footer=el("div",{class:"gridfoot"},[rangeSpan,el("span",{style:"flex:1"}),el("span",{class:"pg"},[gbtn("chevL","Previous page",function(){g.offset=Math.max(0,g.offset-parseInt(pageSel.value,10));loadData(tab);}),el("span",{class:"note",text:"Rows"}),pageSel,gbtn("chevR","Next page",function(){g.offset+=parseInt(pageSel.value,10);loadData(tab);})]),durSpan]);
327
+ pane.appendChild(crumbs((activeConn()||{}).name,schema,table));pane.appendChild(tb);pane.appendChild(changeBar);pane.appendChild(grid);pane.appendChild(footer);
253
328
  updateDirtyButtons(tab);
254
329
  api("GET","/api/catalog/columns?"+qstr({connectionId:S.activeConnId,schema:schema,table:table})).then(function(r){g.columns=r.columns||[];}).catch(function(){});
255
- api("GET","/api/catalog/primary-key?"+qstr({connectionId:S.activeConnId,schema:schema,table:table})).then(function(r){g.pk=(r.primaryKey&&r.primaryKey.columns)||[];g.editable=g.pk.length>0;editHint.textContent=g.editable?("Editable \\u00b7 double-click a cell \\u00b7 key: "+g.pk.join(", ")):"Read-only (no primary key detected)";renderGrid(tab);}).catch(function(){editHint.textContent="";});
330
+ api("GET","/api/catalog/primary-key?"+qstr({connectionId:S.activeConnId,schema:schema,table:table})).then(function(r){g.pk=(r.primaryKey&&r.primaryKey.columns)||[];g.editable=g.pk.length>0;insBtn.disabled=!g.editable;delBtn.disabled=!g.editable;insBtn.title=g.editable?"Insert row":"Read-only (no primary key)";renderGrid(tab);}).catch(function(){});
331
+ if(restore){g.where=restore.where||"";whereI.value=g.where;whereBox.classList.toggle("has",!!g.where);g.offset=restore.offset||0;if(restore.pageSize)pageSel.value=String(restore.pageSize);if(restore.sort&&restore.sort[0]){g.orderBy=restore.sort[0].column;g.orderDir=restore.sort[0].direction;}}
256
332
  loadData(tab);
257
333
  loadCount(tab);
258
334
  }
259
- function loadData(tab){var g=tab.state.g;g.pageSize=parseInt(g.pageSel.value,10);clear(g.grid).appendChild(el("div",{class:"empty"},[el("span",{class:"spin"})," loading data..."]));api("POST","/api/table/data",{connectionId:S.activeConnId,schema:g.schema,table:g.table,limit:g.pageSize,offset:g.offset,where:g.where,orderBy:g.orderBy,orderDirection:g.orderDir}).then(function(r){g.rows=r.result.rows;g.selRow=null;renderGrid(tab);g.pageInfo.textContent="Offset "+g.offset+" \\u00b7 "+r.result.rowCount+" rows \\u00b7 "+r.result.durationMs+"ms";$("stDuration").textContent=r.result.durationMs+"ms";$("stRows").textContent=(g.total!=null?g.total+" total":r.result.rowCount+" rows");}).catch(function(e){clear(g.grid).appendChild(el("div",{class:"errbox",text:"Cannot load data.\\nReason: "+e.message+"\\nAction: test the connection or refresh from BTP app env."}));});}
335
+ function selectedKeys(g){return Object.keys(g.sel);}
336
+ function loadData(tab,onDone){var g=tab.state.g;g.pageSize=parseInt(g.pageSel.value,10);clear(g.grid).appendChild(el("div",{class:"empty"},[el("span",{class:"spin"})," loading data..."]));$("stDuration").textContent="…";api("POST","/api/table/data",{connectionId:S.activeConnId,schema:g.schema,table:g.table,limit:g.pageSize,offset:g.offset,where:g.where,orderBy:g.orderBy,orderDirection:g.orderDir}).then(function(r){g.rows=r.result.rows;g.sel={};renderGrid(tab);var to=g.offset+r.result.rowCount;g.rangeSpan.textContent="Showing "+(r.result.rowCount?g.offset+1:0)+"-"+to+(g.total!=null?" of "+g.total.toLocaleString():"");g.durSpan.textContent="Duration: "+r.result.durationMs+"ms · Offset: "+g.offset;$("stDuration").textContent=r.result.durationMs+"ms";$("stRows").textContent=(g.total!=null?g.total+" total":r.result.rowCount+" rows");if(onDone)onDone();}).catch(function(e){clear(g.grid).appendChild(el("div",{class:"errbox",text:"Cannot load data.\\nReason: "+e.message+"\\nAction: test the connection or refresh from BTP app env."}));if(onDone)onDone();});}
260
337
  function loadCount(tab){var g=tab.state.g;api("POST","/api/table/count",{connectionId:S.activeConnId,schema:g.schema,table:g.table}).then(function(r){g.total=r.count;$("stRows").textContent=r.count+" total";}).catch(function(){});}
261
338
  function dataSortToggle(tab,field){var g=tab.state.g;if(g.orderBy===field)g.orderDir=g.orderDir==="asc"?"desc":"asc";else{g.orderBy=field;g.orderDir="asc";}g.offset=0;loadData(tab);}
262
339
  function renderGrid(tab){var g=tab.state.g;var box=clear(g.grid);if(!g.rows.length&&!g.inserts.length){box.appendChild(el("div",{class:"empty",text:"No rows."}));return;}
263
- var fields=g.columns.length?g.columns.map(function(c){return c.name;}):(g.rows[0]?Object.keys(g.rows[0]):[]);
340
+ var fields=g.columns.length?g.columns.map(function(c){return c.name;}):(g.rows[0]?Object.keys(g.rows[0]):[]);g.fields=fields;
264
341
  var table=el("table",{class:"grid"});var thead=el("thead");var htr=el("tr");htr.appendChild(el("th",{class:"rowhdr",text:"#"}));fields.forEach(function(f){var arrow=g.orderBy===f?(g.orderDir==="desc"?" \\u25BC":" \\u25B2"):"";var th=el("th",{title:"Click to sort",text:f+arrow});th.addEventListener("click",function(){dataSortToggle(tab,f);});htr.appendChild(th);});thead.appendChild(htr);table.appendChild(thead);
265
342
  var tbody=el("tbody");
266
- g.rows.forEach(function(row,ri){var key=rowKeyOf(g,row);var deleted=!!g.deletes[key];var edited=g.edits[key];var err=g.errors[key];var tr=el("tr",{class:(g.selRow===ri?"selrow ":"")+(deleted?"row-del ":"")+(err?"row-err ":"")});
343
+ g.rows.forEach(function(row,ri){var key=rowKeyOf(g,row);var deleted=!!g.deletes[key];var edited=g.edits[key];var err=g.errors[key];var tr=el("tr",{class:(g.sel[key]?"selrow ":"")+(deleted?"row-del ":"")+(err?"row-err ":""),"data-ri":ri});
344
+ tr.addEventListener("contextmenu",function(e){e.preventDefault();rowContextMenu(e,tab,row);});
267
345
  var flag=edited?'<span class="rowflag d"></span>':(deleted?'<span class="rowflag del"></span>':"");
268
- var num=el("td",{class:"rowhdr",html:flag+(g.offset+ri+1)});num.addEventListener("click",function(){g.selRow=(g.selRow===ri?null:ri);renderGrid(tab);});tr.appendChild(num);
269
- fields.forEach(function(f){var hasEdit=edited&&Object.prototype.hasOwnProperty.call(edited,f);var v=hasEdit?edited[f]:row[f];var disp=v==null?"":typeof v==="object"?JSON.stringify(v):String(v);var td=el("td",{class:(typeof v==="number"?"num ":"")+(hasEdit?"edited":""),title:disp,text:disp.length>400?disp.slice(0,400)+"\\u2026":disp});if(g.editable&&!deleted){td.addEventListener("dblclick",function(){startEdit(tab,td,ri,f,row);});}else{td.addEventListener("dblclick",function(){navigator.clipboard.writeText(disp);setConnStatus("Cell copied","ok");});}tr.appendChild(td);});
270
- if(err){var etr=tr;etr.title=err;}
346
+ var num=el("td",{class:"rowhdr",html:flag+(g.offset+ri+1),title:err||""});num.addEventListener("click",function(e){if(!(e.ctrlKey||e.metaKey||e.shiftKey))g.sel={};if(g.sel[key])delete g.sel[key];else g.sel[key]=true;renderGrid(tab);});tr.appendChild(num);
347
+ fields.forEach(function(f){var hasEdit=edited&&Object.prototype.hasOwnProperty.call(edited,f);var v=hasEdit?edited[f]:row[f];var disp=v==null?"":typeof v==="object"?JSON.stringify(v):String(v);var td=el("td",{class:(typeof v==="number"?"num ":"")+(hasEdit?"edited":""),title:disp,text:disp.length>400?disp.slice(0,400)+"\\u2026":disp});if(g.editable&&!deleted){td.addEventListener("dblclick",function(){startEdit(tab,td,ri,f,row);});}else{td.addEventListener("dblclick",function(){openCellViewer(v);});}tr.appendChild(td);});
348
+ if(err){tr.title=err;}
271
349
  tbody.appendChild(tr);});
272
350
  g.inserts.forEach(function(ins){var tr=el("tr",{class:"row-ins"});tr.appendChild(el("td",{class:"rowhdr",html:'<span class="rowflag ins"></span>'+"new",onclick:function(){g.inserts=g.inserts.filter(function(x){return x!==ins;});updateDirtyButtons(tab);renderGrid(tab);}}));
273
351
  fields.forEach(function(f){var inp=el("input",{class:"cellinput",value:ins.values[f]!=null?ins.values[f]:""});inp.addEventListener("input",function(){if(inp.value==="")delete ins.values[f];else ins.values[f]=inp.value;});var td=el("td");td.appendChild(inp);tr.appendChild(td);});
@@ -276,12 +354,64 @@ function renderGrid(tab){var g=tab.state.g;var box=clear(g.grid);if(!g.rows.leng
276
354
  table.appendChild(tbody);box.appendChild(table);
277
355
  updateDirtyButtons(tab);
278
356
  }
279
- function startEdit(tab,td,ri,field,row){if(td.querySelector("input"))return;var g=tab.state.g;var key=rowKeyOf(g,row);var cur=g.edits[key]&&Object.prototype.hasOwnProperty.call(g.edits[key],field)?g.edits[key][field]:row[field];var input=el("input",{class:"cellinput"});input.value=cur==null?"":typeof cur==="object"?JSON.stringify(cur):String(cur);clear(td).appendChild(input);input.focus();input.select();var done=false;function commit(){if(done)return;done=true;var origStr=row[field]==null?"":String(row[field]);if(input.value===origStr){if(g.edits[key]){delete g.edits[key][field];if(!Object.keys(g.edits[key]).length)delete g.edits[key];}}else{g.edits[key]=g.edits[key]||{};g.edits[key][field]=input.value;}renderGrid(tab);}input.addEventListener("keydown",function(e){if(e.key==="Enter"){e.preventDefault();commit();}else if(e.key==="Escape"){e.preventDefault();done=true;renderGrid(tab);}});input.addEventListener("blur",commit);}
280
- function toggleDeleteSelected(tab){var g=tab.state.g;if(g.selRow==null)return logMsg("Click a row number to select it first.","warn");if(!g.editable)return logMsg("Cannot delete: no primary key.","warn");var key=rowKeyOf(g,g.rows[g.selRow]);if(g.deletes[key])delete g.deletes[key];else g.deletes[key]=true;renderGrid(tab);}
281
- function addInsertRow(tab){var g=tab.state.g;g.inserts.push({iseq:++g.iseq,values:{}});renderGrid(tab);}
357
+ function startEdit(tab,td,ri,field,row){if(td.querySelector("input"))return;var g=tab.state.g;var colIdx=g.fields?g.fields.indexOf(field):-1;var key=rowKeyOf(g,row);var cur=g.edits[key]&&Object.prototype.hasOwnProperty.call(g.edits[key],field)?g.edits[key][field]:row[field];var input=el("input",{class:"cellinput"});input.value=cur==null?"":typeof cur==="object"?JSON.stringify(cur):String(cur);clear(td).appendChild(input);input.focus();input.select();var done=false;function commit(){if(done)return true;done=true;var origStr=row[field]==null?"":String(row[field]);if(input.value!==origStr){gridPushUndo(tab);g.edits[key]=g.edits[key]||{};g.edits[key][field]=input.value;}else if(g.edits[key]){delete g.edits[key][field];if(!Object.keys(g.edits[key]).length)delete g.edits[key];}return true;}
358
+ input.addEventListener("keydown",function(e){if(e.key==="Enter"){e.preventDefault();commit();renderGrid(tab);editAt(tab,ri+1,colIdx);}else if(e.key==="Tab"){e.preventDefault();commit();renderGrid(tab);editAt(tab,ri,colIdx+1);}else if(e.key==="Escape"){e.preventDefault();done=true;renderGrid(tab);}});
359
+ input.addEventListener("blur",function(){if(!done){commit();renderGrid(tab);}});}
360
+ function toastAction(msg,actionLabel,onAction){var t=el("div",{class:"toast"});t.appendChild(el("span",{text:msg+" "}));t.appendChild(el("a",{class:"link",text:actionLabel,onclick:function(){onAction();t.remove();}}));$("toasts").appendChild(t);setTimeout(function(){t.style.opacity="0";setTimeout(function(){t.remove();},250);},6000);}
361
+ function toggleDeleteSelected(tab){var g=tab.state.g;if(!g.editable)return logMsg("Cannot delete: table has no primary key.","warn");var keys=selectedKeys(g);if(!keys.length)return logMsg("Select one or more rows (click the row number).","warn");
362
+ if(keys.every(function(k){return g.deletes[k];})){gridPushUndo(tab);keys.forEach(function(k){delete g.deletes[k];});renderGrid(tab);return;}
363
+ var mark=function(){gridPushUndo(tab);keys.forEach(function(k){g.deletes[k]=true;});g.sel={};renderGrid(tab);toastAction(keys.length+" row"+(keys.length>1?"s":"")+" marked for delete. They are removed only when you Save Changes.","Undo",function(){gridUndo(tab);});};
364
+ if(keys.length>1){if(confirm("Mark "+keys.length+" selected rows for deletion?\\nThey will not be deleted until you click Save Changes."))mark();}else mark();}
365
+ function addInsertRow(tab){var g=tab.state.g;gridPushUndo(tab);g.inserts.push({iseq:++g.iseq,values:{}});renderGrid(tab);}
282
366
  function revertAll(tab){var g=tab.state.g;g.edits={};g.deletes={};g.inserts=[];g.errors={};renderGrid(tab);logMsg("Reverted pending changes.","ok");}
283
- function updateDirtyButtons(tab){var g=tab.state.g;var n=pendingCount(g);g.saveBtn.style.display=n>0?"":"none";g.revertBtn.style.display=n>0?"":"none";setDirty(tab,n>0);}
284
- function exportData(tab,fmt){var g=tab.state.g;if(!g.rows.length)return logMsg("No rows.","warn");var fields=g.columns.length?g.columns.map(function(c){return c.name;}):Object.keys(g.rows[0]);fetch(fmt==="csv"?"/api/export/csv":"/api/export/json",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({fields:fields,rows:g.rows})}).then(function(r){return r.blob();}).then(function(b){var a=document.createElement("a");a.href=URL.createObjectURL(b);a.download=fmt==="csv"?g.table+".csv":g.table+".json";a.click();});}
367
+ function updateDirtyButtons(tab){var g=tab.state.g;var nu=Object.keys(g.edits).length,nd=Object.keys(g.deletes).length,ni=g.inserts.length;var n=nu+nd+ni;if(g.saveBtn)g.saveBtn.style.display=n>0?"":"none";if(g.revertBtn)g.revertBtn.style.display=n>0?"":"none";setDirty(tab,n>0);if(g.changeBar){if(n>0){g.changeBar.classList.remove("hidden");clear(g.changeBar);g.changeBar.appendChild(el("span",{},["Pending: ",el("span",{class:"cnt-u",text:nu+" edit"+(nu===1?"":"s")})," · ",el("span",{class:"cnt-i",text:ni+" insert"+(ni===1?"":"s")})," · ",el("span",{class:"cnt-d",text:nd+" delete"+(nd===1?"":"s")})]));g.changeBar.appendChild(el("span",{style:"flex:1"}));g.changeBar.appendChild(el("button",{class:"btn sm",title:"Ctrl+S",text:"Save",onclick:function(){saveDataChanges(tab);}}));g.changeBar.appendChild(el("button",{class:"btn sm ghost",title:"Revert all (Ctrl+Z to undo)",text:"Revert",onclick:function(){revertAll(tab);}}));g.changeBar.appendChild(el("button",{class:"btn sm ghost",text:"Show changes",onclick:function(){showChanges(tab);}}));}else g.changeBar.classList.add("hidden");}}
368
+ function gridFields(g){return g.columns.length?g.columns.map(function(c){return c.name;}):(g.rows[0]?Object.keys(g.rows[0]):[]);}
369
+ function exportFilename(g,fmt,suffix){var conn=(activeConn()||{}).name||"db";var stamp=new Date().toISOString().replace(/[:.]/g,"-").slice(0,19);return (conn+"_"+g.schema+"_"+g.table+(suffix?"_"+suffix:"")+"_"+stamp+"."+fmt).replace(/[^a-z0-9._-]+/gi,"-");}
370
+ function exportRowsToFile(fields,rows,fmt,filename){return fetch(fmt==="csv"?"/api/export/csv":"/api/export/json",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({fields:fields,rows:rows})}).then(function(r){return r.blob();}).then(function(b){var a=document.createElement("a");a.href=URL.createObjectURL(b);a.download=filename;a.click();});}
371
+ function exportCurrentPage(tab,fmt){var g=tab.state.g;if(!g.rows.length)return logMsg("No rows to export.","warn");logMsg("Preparing export…","ok");exportRowsToFile(gridFields(g),g.rows,fmt,exportFilename(g,fmt,"page")).then(function(){logMsg("Exported current page as "+fmt.toUpperCase(),"ok");});}
372
+ function exportSelected(tab,fmt){var g=tab.state.g;var rows=g.rows.filter(function(r){return g.sel[rowKeyOf(g,r)];});if(!rows.length)return logMsg("Select rows first (click row numbers).","warn");exportRowsToFile(gridFields(g),rows,fmt,exportFilename(g,fmt,"selected")).then(function(){logMsg("Exported "+rows.length+" selected row(s) as "+fmt.toUpperCase(),"ok");});}
373
+ function exportViaApi(tab,source,fmt,extra){var g=tab.state.g;logMsg("Preparing export…","ok");var body={connectionId:S.activeConnId,schema:g.schema,objectName:g.table,objectType:"table",source:source,format:fmt,whereClause:g.where,limit:g.pageSize,offset:g.offset,sort:g.orderBy?[{column:g.orderBy,direction:g.orderDir}]:[]};if(extra)for(var k in extra)body[k]=extra[k];return fetch("/api/export/data",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(body)}).then(function(r){if(!r.ok)return r.text().then(function(t){throw new Error(t||("HTTP "+r.status));});return r.blob();}).then(function(b){var a=document.createElement("a");a.href=URL.createObjectURL(b);a.download=exportFilename(g,fmt,source);a.click();logMsg("Export completed ("+fmt.toUpperCase()+")","ok");}).catch(function(e){logMsg("Export failed: "+e.message,"err");});}
374
+ function openExportMenu(tab,anchor){var r=anchor.getBoundingClientRect();showCtx(r.left,r.bottom+4,[
375
+ {label:"Current page · CSV",icon:"imp",onClick:function(){exportCurrentPage(tab,"csv");}},
376
+ {label:"Current page · JSON",icon:"imp",onClick:function(){exportCurrentPage(tab,"json");}},
377
+ {sep:true},
378
+ {label:"Current query/filter · CSV",icon:"imp",onClick:function(){exportViaApi(tab,"current-query","csv");}},
379
+ {label:"Current query/filter · JSON",icon:"imp",onClick:function(){exportViaApi(tab,"current-query","json");}},
380
+ {sep:true},
381
+ {label:"Selected rows · CSV",icon:"imp",onClick:function(){exportSelected(tab,"csv");}},
382
+ {label:"Selected rows · JSON",icon:"imp",onClick:function(){exportSelected(tab,"json");}},
383
+ {sep:true},
384
+ {label:"Export custom…",icon:"gear",onClick:function(){exportCustomModal(tab);}}
385
+ ]);}
386
+ function exportCustomModal(tab){var g=tab.state.g;var cols=gridFields(g);
387
+ var src=el("select",{class:"select"});[["current-page","Current page"],["current-query","Current query/filter"],["selected-rows","Selected rows"],["whole-table","Whole table (can be large)"]].forEach(function(o){src.appendChild(el("option",{value:o[0],text:o[1]}));});
388
+ var fmt=el("select",{class:"select"});[["csv","CSV"],["json","JSON"]].forEach(function(f){fmt.appendChild(el("option",{value:f[0],text:f[1]}));});
389
+ var checks={};var list=el("div",{class:"fieldlist"});cols.forEach(function(c){var cb=el("input",{type:"checkbox"});cb.checked=true;checks[c]=cb;list.appendChild(el("label",{},[cb,c]));});
390
+ var warn=el("div",{class:"note"});src.addEventListener("change",function(){warn.textContent=src.value==="whole-table"?"Whole-table export can be large and may take a while.":"";});
391
+ var d=el("div",{class:"dialog"},[el("h3",{text:"Export data"}),
392
+ el("div",{class:"field"},[el("label",{text:"Source"}),src]),warn,
393
+ el("div",{class:"field"},[el("label",{text:"Columns"}),list]),
394
+ el("div",{class:"field"},[el("label",{text:"Format"}),fmt]),
395
+ el("div",{class:"row right"},[el("button",{class:"btn ghost",text:"Cancel",onclick:closeModal}),el("button",{class:"btn",text:"Export",onclick:function(){
396
+ var selectedColumns=cols.filter(function(c){return checks[c].checked;});if(!selectedColumns.length)return logMsg("Select at least one column.","warn");
397
+ var source=src.value,format=fmt.value;
398
+ if(source==="whole-table"&&!confirm("Export the whole table? This can be large."))return;
399
+ closeModal();
400
+ if(source==="current-page"){exportRowsToFile(selectedColumns,g.rows,format,exportFilename(g,format,"page")).then(function(){logMsg("Exported.","ok");});}
401
+ else if(source==="selected-rows"){var rows=g.rows.filter(function(r){return g.sel[rowKeyOf(g,r)];});if(!rows.length)return logMsg("No rows selected.","warn");exportRowsToFile(selectedColumns,rows,format,exportFilename(g,format,"selected")).then(function(){logMsg("Exported.","ok");});}
402
+ else{exportViaApi(tab,source,format,{selectedColumns:selectedColumns});}
403
+ }})])]);
404
+ openModal(d);}
405
+ function rowContextMenu(e,tab,row){var g=tab.state.g;showCtx(e.clientX,e.clientY,[
406
+ {label:"View row details",icon:"viw",onClick:function(){openCellViewer(row);}},
407
+ {label:"Copy row as JSON",icon:"col",onClick:function(){navigator.clipboard.writeText(JSON.stringify(row,null,2));logMsg("Copied row JSON","ok");}},
408
+ {label:"Copy INSERT statement",icon:"sql",onClick:function(){copyRowDml(g,row,"insert");}},
409
+ {label:"Copy UPDATE statement",icon:"sql",onClick:function(){copyRowDml(g,row,"update");}}
410
+ ]);}
411
+ function copyRowDml(g,row,kind){var fields=gridFields(g);var qn='"'+g.schema+'"."'+g.table+'"';function lit(v){return v==null?"NULL":typeof v==="number"?String(v):"'"+String(v).replace(/'/g,"''")+"'";}
412
+ var sql;if(kind==="insert"){sql="INSERT INTO "+qn+" ("+fields.map(function(f){return '"'+f+'"';}).join(", ")+")\\nVALUES ("+fields.map(function(f){return lit(row[f]);}).join(", ")+");";}
413
+ else{var setp=fields.filter(function(f){return g.pk.indexOf(f)<0;}).map(function(f){return '"'+f+'" = '+lit(row[f]);}).join(",\\n ");var wherep=(g.pk.length?g.pk:fields).map(function(f){return '"'+f+'" = '+lit(row[f]);}).join("\\n AND ");sql="UPDATE "+qn+"\\nSET\\n "+setp+"\\nWHERE\\n "+wherep+";";}
414
+ navigator.clipboard.writeText(sql);logMsg("Copied "+kind.toUpperCase()+" statement","ok");}
285
415
  function saveDataChanges(tab){var g=tab.state.g;if(S.readOnly)return logMsg("Read-only mode is on.","warn");
286
416
  var updates=Object.keys(g.edits).map(function(key){var row=g.rows.filter(function(r){return rowKeyOf(g,r)===key;})[0];var keyObj={};g.pk.forEach(function(k){keyObj[k]=row[k];});return {key:keyObj,changes:g.edits[key]};});
287
417
  var deletes=Object.keys(g.deletes).map(function(key){var row=g.rows.filter(function(r){return rowKeyOf(g,r)===key;})[0];var keyObj={};g.pk.forEach(function(k){keyObj[k]=row[k];});return {key:keyObj,_k:key};});
@@ -309,13 +439,14 @@ function rowKeyFromKeyObj(g,keyObj){return g.pk.map(function(k){return String(ke
309
439
  /* ====================================================================
310
440
  STRUCTURE / METADATA TAB
311
441
  ==================================================================== */
312
- function openStructureTab(schema,table){openTab({key:"struct:"+S.activeConnId+":"+schema+"."+table,kind:"structure",title:"Structure: "+table,icon:"col",build:function(pane,tab){buildStructure(pane,tab,schema,table);}});}
442
+ function openStructureTab(schema,table){return openTab({key:"struct:"+S.activeConnId+":"+schema+"."+table,kind:"structure",title:"Structure: "+table,icon:"col",connectionId:S.activeConnId,meta:{schema:schema,table:table,objectType:"table"},build:function(pane,tab){tab.connectionId=S.activeConnId;buildStructure(pane,tab,schema,table);}});}
313
443
  function buildStructure(pane,tab,schema,table){
314
444
  var subtabs=el("div",{class:"meta-tabs"});var body=el("div",{class:"pane-body"});
315
445
  var defs=[["columns","Columns"],["indexes","Indexes"],["ddl","DDL"],["info","Info"]];
316
446
  var active="columns";var data={};
317
447
  function render(){clear(body);if(active==="columns")renderCols();else if(active==="indexes")renderIdx();else if(active==="ddl")renderDdl();else renderInfo();Array.prototype.forEach.call(subtabs.children,function(ch,i){ch.classList.toggle("active",defs[i][0]===active);});}
318
448
  defs.forEach(function(d){subtabs.appendChild(el("div",{class:"meta-tab",text:d[1],onclick:function(){active=d[0];render();}}));});
449
+ pane.appendChild(crumbs((activeConn()||{}).name,schema,table));
319
450
  pane.appendChild(el("div",{class:"toolbar"},[el("b",{text:'"'+schema+'"."'+table+'"'}),el("span",{class:"grow"}),el("button",{class:"btn sec",text:"Open Data",onclick:function(){openDataTab(schema,table);}})]));
320
451
  pane.appendChild(subtabs);pane.appendChild(body);
321
452
  function renderCols(){body.appendChild(el("div",{class:"empty"},[el("span",{class:"spin"})," loading..."]));api("GET","/api/catalog/columns?"+qstr({connectionId:S.activeConnId,schema:schema,table:table})).then(function(r){data.columns=r.columns||[];if(active!=="columns")return;clear(body);var t=el("table",{class:"grid"});t.appendChild(el("thead",{html:"<tr><th>Name</th><th>Type</th><th>Length</th><th>Scale</th><th>Nullable</th><th>Key</th><th>Default</th><th>Comment</th></tr>"}));var tb=el("tbody");data.columns.forEach(function(c){tb.appendChild(el("tr",{html:"<td>"+esc(c.name)+"</td><td>"+esc(c.dataType)+'</td><td class="num">'+esc(c.length==null?"":c.length)+'</td><td class="num">'+esc(c.scale==null?"":c.scale)+"</td><td>"+(c.nullable?"YES":"NO")+"</td><td>"+(c.isPrimaryKey?'<span class="pill pk">PK</span>':"")+"</td><td>"+esc(c.defaultValue==null?"":c.defaultValue)+"</td><td>"+esc(c.comment==null?"":c.comment)+"</td>"}));});t.appendChild(tb);clear(body).appendChild(t);}).catch(function(e){clear(body).appendChild(el("div",{class:"errbox",text:e.message}));});}
@@ -329,7 +460,7 @@ function buildStructure(pane,tab,schema,table){
329
460
  SAVED QUERIES
330
461
  ==================================================================== */
331
462
  function loadSavedQueries(){return api("GET","/api/queries").then(function(r){S.savedQueries=r.queries||[];renderSavedQueries();}).catch(function(){});}
332
- function renderSavedQueries(){var q=($("querySearch").value||"").toLowerCase();var box=clear($("queryList"));var rows=S.savedQueries.filter(function(x){return (x.name+" "+(x.tags||[]).join(" ")).toLowerCase().indexOf(q)>=0;});if(!rows.length){box.appendChild(el("div",{class:"empty",text:"No saved queries."}));return;}rows.forEach(function(x){var item=el("div",{class:"wli",onclick:function(){openSqlTab(x.sql,x.name);},oncontextmenu:function(e){e.preventDefault();queryMenu(e,x);}});item.appendChild(el("b",{text:x.name}));item.appendChild(el("div",{class:"note",text:(x.connectionType||"")+" \\u00b7 "+new Date(x.updatedAt).toLocaleDateString()}));box.appendChild(item);});}
463
+ function renderSavedQueries(){var raw=($("querySearch").value||"");var q=raw.toLowerCase();var box=clear($("queryList"));var rows=S.savedQueries.filter(function(x){return (x.name+" "+(x.tags||[]).join(" ")).toLowerCase().indexOf(q)>=0;});if(!rows.length){box.appendChild(el("div",{class:"empty",text:S.savedQueries.length?"No results found":"No saved queries."}));return;}rows.forEach(function(x){var item=el("div",{class:"wli",onclick:function(){openSqlTab(x.sql,x.name,x.id);},oncontextmenu:function(e){e.preventDefault();queryMenu(e,x);}});item.appendChild(el("b",{html:highlightMatch(x.name,raw)}));item.appendChild(el("div",{class:"note",text:(x.connectionType||"")+" \\u00b7 "+new Date(x.updatedAt).toLocaleDateString()}));box.appendChild(item);});}
333
464
  function queryMenu(e,x){showCtx(e.clientX,e.clientY,[{label:"Open",icon:"sql",onClick:function(){openSqlTab(x.sql,x.name);}},{label:"Rename",icon:"gear",onClick:function(){var n=prompt("New name",x.name);if(n)api("PUT","/api/queries/"+encodeURIComponent(x.id),{name:n}).then(loadSavedQueries);}},{label:"Delete",icon:"x",danger:true,onClick:function(){if(confirm("Delete '"+x.name+"'?"))api("DELETE","/api/queries/"+encodeURIComponent(x.id)).then(loadSavedQueries);}}]);}
334
465
 
335
466
  /* ====================================================================
@@ -369,6 +500,68 @@ function openBtpWizard(){var stState={apps:[],services:[],app:"",svc:null,color:
369
500
  /* ====================================================================
370
501
  READ-ONLY + INIT
371
502
  ==================================================================== */
503
+ /* ====================================================================
504
+ KEYBOARD + COMMAND PALETTE + SETTINGS
505
+ ==================================================================== */
506
+ function isTyping(t){return t&&(t.tagName==="INPUT"||t.tagName==="TEXTAREA"||t.isContentEditable);}
507
+ function saveActive(){var t=tabById(S.activeTabId);if(!t)return;if(t.kind==="sql")saveQueryTab(t);else if(t.kind==="data")saveDataChanges(t);else logMsg("Nothing to save in this tab.","warn");}
508
+ function onGlobalKey(e){var k=e.key;var ctrl=e.ctrlKey||e.metaKey;
509
+ if(ctrl&&e.shiftKey&&(k==="P"||k==="p")){e.preventDefault();openCommandPalette();return;}
510
+ if(k==="Escape"){hideCtx();closePop();if(_palette){closePalette();return;}return;}
511
+ if(ctrl&&k==="Tab"){e.preventDefault();nextTab(e.shiftKey?-1:1);return;}
512
+ if(ctrl&&(k==="w"||k==="W")){e.preventDefault();if(S.activeTabId)closeTab(S.activeTabId);return;}
513
+ if(ctrl&&!e.shiftKey&&(k==="f"||k==="F")&&!isTyping(e.target)){e.preventDefault();$("topSearch").focus();return;}
514
+ if(k==="F5"&&!isTyping(e.target)){var ft=tabById(S.activeTabId);if(ft&&ft.kind==="sql"){e.preventDefault();runMode(ft,"all");}return;}
515
+ if(ctrl&&(k==="s"||k==="S")&&!isTyping(e.target)){e.preventDefault();saveActive();return;}
516
+ var at=tabById(S.activeTabId);
517
+ if(at&&at.kind==="data"&&at.state.g&&!isTyping(e.target)){var g=at.state.g;
518
+ if(ctrl&&(k==="s"||k==="S")){e.preventDefault();saveDataChanges(at);return;}
519
+ if(ctrl&&(k==="z"||k==="Z")){e.preventDefault();gridUndo(at);return;}
520
+ if(ctrl&&(k==="y"||k==="Y")){e.preventDefault();gridRedo(at);return;}
521
+ if(k==="Delete"){e.preventDefault();toggleDeleteSelected(at);return;}
522
+ }
523
+ }
524
+ var PALETTE=[
525
+ {label:"New SQL Console",run:function(){if(!S.activeConnId)logMsg("Select a connection first.","warn");else openSqlTab();}},
526
+ {label:"Run SQL (current tab)",run:function(){var t=tabById(S.activeTabId);if(t&&t.kind==="sql")runMode(t,"selected");}},
527
+ {label:"Save Query / Grid Changes",run:saveActive},
528
+ {label:"Import from BTP App",run:function(){openBtpWizard();}},
529
+ {label:"New direct connection",run:function(){newConnModal();}},
530
+ {label:"Focus Connections",run:function(){$("connSearch").focus();}},
531
+ {label:"Focus Object Explorer",run:function(){var s=document.querySelector("#secTree .tsearch input");if(s)s.focus();else $("topSearch").focus();}},
532
+ {label:"Toggle Read-only",run:function(){toggleReadOnly();}},
533
+ {label:"Open Active Table Structure",run:function(){var t=tabById(S.activeTabId);if(t&&t.meta&&t.meta.table)openStructureTab(t.meta.schema,t.meta.table);else logMsg("No active table.","warn");}},
534
+ {label:"Export Result CSV",run:function(){var t=tabById(S.activeTabId);if(t&&t.kind==="sql")exportResult(t,"csv");else if(t&&t.kind==="data")exportCurrentPage(t,"csv");}},
535
+ {label:"Close Active Tab",run:function(){if(S.activeTabId)closeTab(S.activeTabId);}},
536
+ {label:"Show Keyboard Shortcuts",run:function(){showShortcuts();}},
537
+ {label:"Open Settings",run:function(){openSettings();}}
538
+ ];
539
+ var _palette=null;
540
+ function openCommandPalette(){closePalette();var input=el("input",{placeholder:"Type a command..."});var list=el("div",{class:"pitems"});var sel=0;var filtered=PALETTE.slice();
541
+ function draw(){clear(list);var q=input.value.toLowerCase();filtered=PALETTE.filter(function(c){return c.label.toLowerCase().indexOf(q)>=0;});filtered.forEach(function(c,i){list.appendChild(el("div",{class:"pitem"+(i===sel?" sel":""),onclick:function(){closePalette();c.run();}},[el("span",{html:highlightMatch(c.label,input.value)})]));});}
542
+ input.addEventListener("input",function(){sel=0;draw();});
543
+ input.addEventListener("keydown",function(e){if(e.key==="ArrowDown"){e.preventDefault();sel=Math.min(filtered.length-1,sel+1);draw();}else if(e.key==="ArrowUp"){e.preventDefault();sel=Math.max(0,sel-1);draw();}else if(e.key==="Enter"){e.preventDefault();if(filtered[sel]){closePalette();filtered[sel].run();}}else if(e.key==="Escape"){e.preventDefault();closePalette();}});
544
+ var ov=el("div",{class:"modal",onclick:function(e){if(e.target===ov)closePalette();}},[el("div",{class:"palette"},[input,list])]);document.body.appendChild(ov);_palette=ov;draw();input.focus();}
545
+ function closePalette(){if(_palette){_palette.remove();_palette=null;}}
546
+ function showShortcuts(){var rows=[["Ctrl+Shift+P","Command palette"],["Ctrl+Tab","Next tab"],["Ctrl+Shift+Tab","Previous tab"],["Ctrl+W","Close active tab"],["Ctrl+S","Save query / grid changes"],["Ctrl+F","Focus search"],["Escape","Close popup / clear search"],["Ctrl+Enter","Run selected / current SQL"],["F5","Run all SQL"],["Ctrl+Z / Ctrl+Y","Undo / redo (editor & grid)"],["Delete","Mark grid row for delete"],["Enter / Tab","Confirm cell edit & move"],["Esc","Cancel cell edit"]];
547
+ var grid=el("div",{class:"shorts"});rows.forEach(function(r){grid.appendChild(el("div",{class:"srow"},[el("span",{text:r[1]}),el("span",{class:"kbd",text:r[0]})]));});
548
+ openModal(el("div",{class:"dialog"},[el("h3",{text:"Keyboard shortcuts"}),grid,el("div",{class:"row right",style:"margin-top:14px"},[el("button",{class:"btn",text:"Close",onclick:closeModal})])]));}
549
+ function openSettings(){var s=S.settings;var restore=el("input",{type:"checkbox"});restore.checked=s.restoreWorkspace;var ro=el("input",{type:"checkbox"});ro.checked=s.readOnlyByDefault;var prod=el("input",{type:"checkbox"});prod.checked=s.showProductionWarning;var fmt=el("input",{type:"checkbox"});fmt.checked=s.autoFormatGeneratedSql;var limit=el("input",{class:"input",value:String(s.defaultRowLimit)});var schema=el("input",{class:"input",value:s.defaultSchema||""});var timeout=el("input",{class:"input",value:String(s.queryTimeoutMs)});var maxh=el("input",{class:"input",value:String(s.maxHistoryItems)});var delay=el("input",{class:"input",value:String(s.autoSaveDelayMs)});
550
+ var d=el("div",{class:"dialog"},[el("h3",{text:"Studio settings"}),el("label",{class:"toggle"},[restore," Restore workspace on startup"]),el("label",{class:"toggle"},[ro," Read-only by default"]),el("label",{class:"toggle"},[prod," Show production warning"]),el("label",{class:"toggle"},[fmt," Auto-format generated SQL"]),el("div",{class:"field"},[el("label",{text:"Default row limit"}),limit]),el("div",{class:"field"},[el("label",{text:"Default schema"}),schema]),el("div",{class:"field"},[el("label",{text:"Query timeout (ms)"}),timeout]),el("div",{class:"field"},[el("label",{text:"Max history items"}),maxh]),el("div",{class:"field"},[el("label",{text:"Auto-save delay (ms)"}),delay]),el("div",{class:"row right"},[el("button",{class:"btn ghost",text:"Cancel",onclick:closeModal}),el("button",{class:"btn",text:"Save",onclick:function(){api("PUT","/api/studio/settings",{restoreWorkspace:restore.checked,readOnlyByDefault:ro.checked,showProductionWarning:prod.checked,autoFormatGeneratedSql:fmt.checked,defaultRowLimit:parseInt(limit.value,10)||100,defaultSchema:schema.value.trim(),queryTimeoutMs:parseInt(timeout.value,10)||30000,maxHistoryItems:parseInt(maxh.value,10)||300,autoSaveDelayMs:parseInt(delay.value,10)||500}).then(function(r){S.settings=r.settings;closeModal();logMsg("Settings saved.","ok");}).catch(function(e){logMsg(e.message,"err");});}})])]);openModal(d);}
551
+ function openCellViewer(value){var raw=value==null?"":typeof value==="object"?JSON.stringify(value):String(value);var pretty=raw;try{pretty=JSON.stringify(JSON.parse(raw),null,2);}catch(e){}var ta=el("textarea",{class:"editor",style:"min-height:300px"});ta.value=pretty;openModal(el("div",{class:"dialog",style:"width:700px"},[el("h3",{text:"Cell value"}),ta,el("div",{class:"row right",style:"margin-top:10px"},[el("button",{class:"btn ghost",text:"Copy",onclick:function(){navigator.clipboard.writeText(raw);logMsg("Copied value","ok");}}),el("button",{class:"btn",text:"Close",onclick:closeModal})])]));}
552
+ function crumbs(connName,schema,table){var c=el("div",{class:"crumbs"});c.appendChild(el("a",{text:connName||"Connection",onclick:function(){if(S.activeConnId)activateConnection(S.activeConnId);}}));c.appendChild(el("span",{class:"sep",text:"\\u203a"}));c.appendChild(el("a",{text:schema,onclick:function(){S.activeSchema=schema;updateTopBadges();}}));c.appendChild(el("span",{class:"sep",text:"\\u203a"}));c.appendChild(el("span",{text:table}));return c;}
553
+
554
+ /* ---- grid undo/redo + change review ---- */
555
+ function gridSnapshot(g){return JSON.stringify({edits:g.edits,deletes:g.deletes,inserts:g.inserts});}
556
+ function gridApplySnap(g,s){var o=JSON.parse(s);g.edits=o.edits;g.deletes=o.deletes;g.inserts=o.inserts;}
557
+ function gridPushUndo(tab){var g=tab.state.g;g.undo.push(gridSnapshot(g));if(g.undo.length>60)g.undo.shift();g.redo=[];}
558
+ function gridUndo(tab){var g=tab.state.g;if(!g.undo.length)return logMsg("Nothing to undo.","warn");g.redo.push(gridSnapshot(g));gridApplySnap(g,g.undo.pop());renderGrid(tab);}
559
+ function gridRedo(tab){var g=tab.state.g;if(!g.redo.length)return logMsg("Nothing to redo.","warn");g.undo.push(gridSnapshot(g));gridApplySnap(g,g.redo.pop());renderGrid(tab);}
560
+ function showChanges(tab){var g=tab.state.g;var out=[];Object.keys(g.edits).forEach(function(key){var row=g.rows.filter(function(r){return rowKeyOf(g,r)===key;})[0]||{};Object.keys(g.edits[key]).forEach(function(col){out.push(["update",key,col,String(row[col]==null?"":row[col]),String(g.edits[key][col])]);});});Object.keys(g.deletes).forEach(function(key){out.push(["delete",key,"","",""]);});g.inserts.forEach(function(ins){out.push(["insert","(new)",Object.keys(ins.values).join(","),"",JSON.stringify(ins.values)]);});
561
+ var t=el("table",{class:"grid"});t.appendChild(el("thead",{html:"<tr><th>Type</th><th>Row key</th><th>Column</th><th>Old</th><th>New</th></tr>"}));var tb=el("tbody");if(!out.length)tb.appendChild(el("tr",{html:'<td colspan="5">No pending changes.</td>'}));out.forEach(function(r){tb.appendChild(el("tr",{html:r.map(function(c){return "<td>"+esc(c)+"</td>";}).join("")}));});t.appendChild(tb);
562
+ openModal(el("div",{class:"dialog",style:"width:780px"},[el("h3",{text:"Pending changes"}),el("div",{class:"gridwrap",style:"max-height:52vh"},[t]),el("div",{class:"row right",style:"margin-top:12px"},[el("button",{class:"btn",text:"Close",onclick:closeModal})])]));}
563
+ function editAt(tab,ri,colIdx){var g=tab.state.g;if(ri<0||ri>=g.rows.length||colIdx<0||!g.fields||colIdx>=g.fields.length)return;var tr=g.grid.querySelector('tr[data-ri="'+ri+'"]');if(!tr)return;var tds=tr.querySelectorAll("td");var td=tds[colIdx+1];if(td)startEdit(tab,td,ri,g.fields[colIdx],g.rows[ri]);}
564
+
372
565
  function applyReadOnly(){var b=$("roBadge");b.className="badge ro"+(S.readOnly?" active":"");b.textContent=S.readOnly?"Read-only":"Read/Write";}
373
566
  function toggleReadOnly(){S.readOnly=!S.readOnly;applyReadOnly();logMsg("Read-only mode: "+(S.readOnly?"ON":"OFF"),"ok");}
374
567
 
@@ -382,15 +575,17 @@ window.addEventListener("load",function(){
382
575
  $("btnImport").addEventListener("click",openBtpWizard);
383
576
  $("btnImport2").addEventListener("click",openBtpWizard);
384
577
  $("btnNewConn").addEventListener("click",newConnModal);
385
- $("btnSettings").addEventListener("click",function(){logMsg("Read-only toggle and connection colors are in the top bar / connection menu.","ok");});
578
+ $("btnSettings").addEventListener("click",openSettings);
386
579
  $("btnHome").addEventListener("click",openWelcome);
387
- $("connSearch").addEventListener("input",debounce(renderConnections,200));
388
- $("querySearch").addEventListener("input",debounce(renderSavedQueries,200));
580
+ window.addEventListener("keydown",onGlobalKey);
581
+ $("connSearch").addEventListener("input",debounce(renderConnections,200));wireSearch($("connSearch"),renderConnections);
582
+ $("querySearch").addEventListener("input",debounce(renderSavedQueries,200));wireSearch($("querySearch"),renderSavedQueries);
389
583
  $("topSearch").addEventListener("input",debounce(function(){$("connSearch").value=$("topSearch").value;renderConnections();},200));
390
584
  $("btnNewQuery").addEventListener("click",function(){if(!S.activeConnId)return logMsg("Select a connection first.","warn");openSqlTab();});
585
+ Array.prototype.forEach.call(document.querySelectorAll(".searchbox"),function(box){var inp=box.querySelector("input");if(!inp)return;var x=el("span",{class:"sbclr",html:svgFor("x"),title:"Clear (Esc)"});x.addEventListener("click",function(){inp.value="";inp.dispatchEvent(new Event("input"));inp.focus();});inp.addEventListener("input",function(){x.classList.toggle("show",!!inp.value);});box.appendChild(x);});
391
586
  setConnStatus("Ready","ok");
392
587
  openWelcome();
393
- loadConnections();
588
+ loadSettings().then(function(){return loadConnections();}).then(function(){return restoreWorkspace();});
394
589
  loadSavedQueries();
395
590
  });
396
591
  })();