@hienlh/ppm 0.13.44 → 0.13.45
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.
- package/CHANGELOG.md +5 -0
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/http-api.md +1 -1
- package/dist/web/assets/ai-settings-section-Btix996C.js +2 -1
- package/dist/web/assets/ai-settings-section-Btix996C.js.map +1 -0
- package/dist/web/assets/api-client-DIhJ5qVW.js +2 -1
- package/dist/web/assets/api-client-DIhJ5qVW.js.map +1 -0
- package/dist/web/assets/api-settings-DnHv6JgF.js +2 -1
- package/dist/web/assets/api-settings-DnHv6JgF.js.map +1 -0
- package/dist/web/assets/arrow-up-Rcw6_KKu.js +2 -1
- package/dist/web/assets/arrow-up-Rcw6_KKu.js.map +1 -0
- package/dist/web/assets/audio-preview-B6a1Djtr.js +2 -1
- package/dist/web/assets/audio-preview-B6a1Djtr.js.map +1 -0
- package/dist/web/assets/chat-tab-Z5VNzYCz.js +2 -1
- package/dist/web/assets/chat-tab-Z5VNzYCz.js.map +1 -0
- package/dist/web/assets/chevron-right-DnHIvvcy.js +2 -1
- package/dist/web/assets/chevron-right-DnHIvvcy.js.map +1 -0
- package/dist/web/assets/code-DGBecc50.js +2 -1
- package/dist/web/assets/code-DGBecc50.js.map +1 -0
- package/dist/web/assets/code-editor-Clvs0e0Q.js +2 -1
- package/dist/web/assets/code-editor-Clvs0e0Q.js.map +1 -0
- package/dist/web/assets/conflict-editor-B6nO1gln.js +2 -1
- package/dist/web/assets/conflict-editor-B6nO1gln.js.map +1 -0
- package/dist/web/assets/createLucideIcon-BjHrJDVb.js +2 -1
- package/dist/web/assets/createLucideIcon-BjHrJDVb.js.map +1 -0
- package/dist/web/assets/csv-parser-Dly5nqE1.js +2 -1
- package/dist/web/assets/csv-parser-Dly5nqE1.js.map +1 -0
- package/dist/web/assets/csv-preview-CsqFmPzb.js +2 -1
- package/dist/web/assets/csv-preview-CsqFmPzb.js.map +1 -0
- package/dist/web/assets/data-grid-overlay-editor-aTzJjALy.js +2 -1
- package/dist/web/assets/data-grid-overlay-editor-aTzJjALy.js.map +1 -0
- package/dist/web/assets/data-grid-types-BISkUXAY.js +2 -1
- package/dist/web/assets/data-grid-types-BISkUXAY.js.map +1 -0
- package/dist/web/assets/database-DOWH9-Vv.js +2 -1
- package/dist/web/assets/database-DOWH9-Vv.js.map +1 -0
- package/dist/web/assets/database-viewer-Boup8MJ_.js +2 -1
- package/dist/web/assets/database-viewer-Boup8MJ_.js.map +1 -0
- package/dist/web/assets/diff-viewer-Je2KYGME.js +2 -1
- package/dist/web/assets/diff-viewer-Je2KYGME.js.map +1 -0
- package/dist/web/assets/dist-B1I_4Jtc.js +2 -1
- package/dist/web/assets/dist-B1I_4Jtc.js.map +1 -0
- package/dist/web/assets/dist-CcDNqGjt.js +2 -1
- package/dist/web/assets/dist-CcDNqGjt.js.map +1 -0
- package/dist/web/assets/dist-wf2npcsG.js +2 -1
- package/dist/web/assets/dist-wf2npcsG.js.map +1 -0
- package/dist/web/assets/esm-UZtw2QcY.js +2 -1
- package/dist/web/assets/esm-UZtw2QcY.js.map +1 -0
- package/dist/web/assets/extension-webview-T9TN70nb.js +2 -1
- package/dist/web/assets/extension-webview-T9TN70nb.js.map +1 -0
- package/dist/web/assets/file-exclamation-point-BwzaQ50n.js +2 -1
- package/dist/web/assets/file-exclamation-point-BwzaQ50n.js.map +1 -0
- package/dist/web/assets/file-store-DOxcU_7s.js +2 -1
- package/dist/web/assets/file-store-DOxcU_7s.js.map +1 -0
- package/dist/web/assets/glide-data-grid-CqT8WzTs.js +2 -1
- package/dist/web/assets/glide-data-grid-CqT8WzTs.js.map +1 -0
- package/dist/web/assets/image-preview-DeNUcGI9.js +2 -1
- package/dist/web/assets/image-preview-DeNUcGI9.js.map +1 -0
- package/dist/web/assets/index-DJtqbPFT.js +2 -1
- package/dist/web/assets/index-DJtqbPFT.js.map +1 -0
- package/dist/web/assets/input-_LFQwhzd.js +2 -1
- package/dist/web/assets/input-_LFQwhzd.js.map +1 -0
- package/dist/web/assets/katex-Bqvo_ZG0.js +2 -1
- package/dist/web/assets/katex-Bqvo_ZG0.js.map +1 -0
- package/dist/web/assets/lib-Bu71-TFS.js +2 -1
- package/dist/web/assets/lib-Bu71-TFS.js.map +1 -0
- package/dist/web/assets/markdown-renderer-CXPtICSx.js +2 -1
- package/dist/web/assets/markdown-renderer-CXPtICSx.js.map +1 -0
- package/dist/web/assets/number-overlay-editor-CewUR5pB.js +2 -1
- package/dist/web/assets/number-overlay-editor-CewUR5pB.js.map +1 -0
- package/dist/web/assets/pdf-preview-DI2JU2Lm.js +2 -1
- package/dist/web/assets/pdf-preview-DI2JU2Lm.js.map +1 -0
- package/dist/web/assets/port-forwarding-tab-Cuv_37LW.js +2 -1
- package/dist/web/assets/port-forwarding-tab-Cuv_37LW.js.map +1 -0
- package/dist/web/assets/postgres-viewer-D76ygOZo.js +2 -1
- package/dist/web/assets/postgres-viewer-D76ygOZo.js.map +1 -0
- package/dist/web/assets/react-DMIOAtcX.js +2 -1
- package/dist/web/assets/react-DMIOAtcX.js.map +1 -0
- package/dist/web/assets/refresh-cw-BjrAbUJe.js +2 -1
- package/dist/web/assets/refresh-cw-BjrAbUJe.js.map +1 -0
- package/dist/web/assets/scroll-area-BDi_FNzr.js +2 -1
- package/dist/web/assets/scroll-area-BDi_FNzr.js.map +1 -0
- package/dist/web/assets/search-tM8K5zWU.js +2 -1
- package/dist/web/assets/search-tM8K5zWU.js.map +1 -0
- package/dist/web/assets/settings-store-CVrIYYCB.js +2 -1
- package/dist/web/assets/settings-store-CVrIYYCB.js.map +1 -0
- package/dist/web/assets/sparkles-CulWHe4c.js +2 -1
- package/dist/web/assets/sparkles-CulWHe4c.js.map +1 -0
- package/dist/web/assets/sql-query-editor-lSlKMPlG.js +2 -1
- package/dist/web/assets/sql-query-editor-lSlKMPlG.js.map +1 -0
- package/dist/web/assets/sqlite-viewer-DwTatNwI.js +2 -1
- package/dist/web/assets/sqlite-viewer-DwTatNwI.js.map +1 -0
- package/dist/web/assets/tab-store-S9w6U5gm.js +2 -1
- package/dist/web/assets/tab-store-S9w6U5gm.js.map +1 -0
- package/dist/web/assets/table-BzjWcs87.js +2 -1
- package/dist/web/assets/table-BzjWcs87.js.map +1 -0
- package/dist/web/assets/terminal-tab-XKe1TZiV.js +2 -1
- package/dist/web/assets/terminal-tab-XKe1TZiV.js.map +1 -0
- package/dist/web/assets/text-wrap-DJz9Bgpa.js +2 -1
- package/dist/web/assets/text-wrap-DJz9Bgpa.js.map +1 -0
- package/dist/web/assets/use-blob-url-QX-XajU8.js +2 -1
- package/dist/web/assets/use-blob-url-QX-XajU8.js.map +1 -0
- package/dist/web/assets/use-monaco-theme-BePWbY58.js +2 -1
- package/dist/web/assets/use-monaco-theme-BePWbY58.js.map +1 -0
- package/dist/web/assets/utils-CQux7CsO.js +2 -1
- package/dist/web/assets/utils-CQux7CsO.js.map +1 -0
- package/dist/web/assets/vendor-markdown-0Mxgxy0L.js +2 -1
- package/dist/web/assets/vendor-markdown-0Mxgxy0L.js.map +1 -0
- package/dist/web/assets/vendor-mermaid-Cl50p6TB.js +2 -1
- package/dist/web/assets/vendor-mermaid-Cl50p6TB.js.map +1 -0
- package/dist/web/assets/vendor-ui-UXCWAcmi.js +2 -1
- package/dist/web/assets/vendor-ui-UXCWAcmi.js.map +1 -0
- package/dist/web/assets/vendor-xterm-K3_Xwigj.js +2 -1
- package/dist/web/assets/vendor-xterm-K3_Xwigj.js.map +1 -0
- package/dist/web/assets/video-preview-wHVbAYar.js +2 -1
- package/dist/web/assets/video-preview-wHVbAYar.js.map +1 -0
- package/dist/web/assets/x-BPReZWnP.js +2 -1
- package/dist/web/assets/x-BPReZWnP.js.map +1 -0
- package/dist/web/sw.js +2 -1
- package/dist/web/sw.js.map +1 -0
- package/package.json +1 -1
- package/vite.config.ts +1 -0
|
@@ -1 +1,2 @@
|
|
|
1
|
-
import{o as e}from"./rolldown-runtime-FhOqtrmT.js";import{b as t,x as n}from"./vendor-markdown-0Mxgxy0L.js";import"./vendor-ui-UXCWAcmi.js";import{t as r}from"./database-DOWH9-Vv.js";import{n as i,t as a}from"./glide-data-grid-CqT8WzTs.js";import{t as o}from"./refresh-cw-BjrAbUJe.js";import{t as s}from"./api-client-DIhJ5qVW.js";import"./settings-store-CVrIYYCB.js";import"./vendor-mermaid-Cl50p6TB.js";import"./tab-store-S9w6U5gm.js";import{G as c,K as l}from"./index-DJtqbPFT.js";import"./data-grid-types-BISkUXAY.js";import"./use-monaco-theme-BePWbY58.js";import{t as u}from"./sql-query-editor-lSlKMPlG.js";var d=e(n(),1);function f(e,t,n,r){return`ppm-db-${e}-${n}.${t}-p${r}`}function p(e,t,n,r){try{let i=sessionStorage.getItem(f(e,t,n,r));return i?JSON.parse(i):null}catch{return null}}function m(e,t,n,r,i,a){try{sessionStorage.setItem(f(e,t,n,r),JSON.stringify({data:i,cols:a}))}catch{}}function h(e){let t=`/api/db/connections/${e}`,[n,r]=(0,d.useState)(null),[i,a]=(0,d.useState)(`public`),[o,c]=(0,d.useState)(null),[l,u]=(0,d.useState)([]),[f,h]=(0,d.useState)(!1),[g,_]=(0,d.useState)(null),[v,y]=(0,d.useState)(1),[b,x]=(0,d.useState)(null),[S,C]=(0,d.useState)(null),[w,T]=(0,d.useState)(!1),[E,D]=(0,d.useState)(null),[O,k]=(0,d.useState)(`ASC`),A=(0,d.useCallback)(async(r,a,o,l,d)=>{let f=r??n,p=a??i;if(!f)return;h(!0);let g=l===void 0?E:l,y=d??O;try{let n=g?`&orderBy=${encodeURIComponent(g)}&orderDir=${y}`:``,[r,i]=await Promise.all([s.get(`${t}/data?table=${encodeURIComponent(f)}&schema=${p}&page=${o??v}&limit=100${n}`),s.get(`${t}/schema?table=${encodeURIComponent(f)}&schema=${p}`)]);c(r),u(i),m(e,f,p,o??v,r,i)}catch(e){_(e.message)}finally{h(!1)}},[t,e,n,i,v,E,O]),j=(0,d.useCallback)((t,n=`public`)=>{r(t),a(n),y(1),x(null);let i=p(e,t,n,1);i?(c(i.data),u(i.cols),h(!1),A(t,n,1)):A(t,n,1)},[e,A]),M=(0,d.useCallback)(e=>{y(e),A(void 0,void 0,e)},[A]),N=(0,d.useCallback)(async e=>{T(!0),C(null);try{let r=await s.post(`${t}/query`,{sql:e});x(r),r.changeType===`modify`&&A(n??void 0,i)}catch(e){C(e.message)}finally{T(!1)}},[t,n,i,A]),P=(0,d.useCallback)(async(e,r,a,o)=>{if(!n)return;let c=n,l=i;try{await s.put(`${t}/cell`,{table:c,schema:l,pkColumn:e,pkValue:r,column:a,value:o}),A(c,l)}catch(e){_(e.message)}},[t,n,i,A]),F=(0,d.useCallback)(async(e,r)=>{if(!n)return;let a=n,o=i;try{await s.del(`${t}/row`,{table:a,schema:o,pkColumn:e,pkValue:r}),A(a,o)}catch(e){_(e.message)}},[t,n,i,A]);return{selectedTable:n,selectedSchema:i,selectTable:j,tableData:o,schema:l,loading:f,error:g,page:v,setPage:M,orderBy:E,orderDir:O,toggleSort:(0,d.useCallback)(e=>{let t,n=`ASC`;E===e?O===`ASC`?(t=e,n=`DESC`):(t=null,n=`ASC`):(t=e,n=`ASC`),D(t),k(n),y(1),A(void 0,void 0,1,t,n)},[E,O,A]),clearSort:(0,d.useCallback)(()=>{D(null),k(`ASC`),y(1),A(void 0,void 0,1,null,`ASC`)},[A]),queryResult:b,queryError:S,queryLoading:w,executeQuery:N,updateCell:P,deleteRow:F,bulkDelete:(0,d.useCallback)(async(e,r)=>{if(!n)return;let a=n,o=i;try{await s.post(`${t}/rows/delete`,{table:a,schema:o,pkColumn:e,pkValues:r}),A(a,o)}catch(e){_(e.message)}},[t,n,i,A]),insertRow:(0,d.useCallback)(async e=>{if(!n)return;let r=n,a=i;try{await s.post(`${t}/row`,{table:r,schema:a,values:e}),A(r,a)}catch(e){throw _(e.message),e}},[t,n,i,A]),refreshData:A,queryAsTable:(0,d.useCallback)(async e=>{h(!0);try{let n=await s.post(`${t}/query`,{sql:e});n.changeType===`select`&&c({columns:n.columns,rows:n.rows,total:n.rows.length,page:1,limit:n.rows.length})}catch(e){_(e.message)}finally{h(!1)}},[t])}}var g=t();function _(e){let t={},n=/"(\w+)"\s+ILIKE\s+'%([^']*?)%'/gi,r;for(;(r=n.exec(e))!==null;)t[r[1]]=r[2].replace(/''/g,`'`);return t}function v({metadata:e}){let t=e?.connectionId,n=e?.connectionName,c=e?.tableName,f=e?.schemaName??`public`,p=e?.initialSql,m=h(t),[v,y]=(0,d.useState)([]),[x,S]=(0,d.useState)(180),C=(0,d.useRef)(null),[w,T]=(0,d.useState)({}),E=(0,d.useMemo)(()=>{if(p&&!m.selectedTable)return p;if(m.selectedTable){let e=`SELECT * FROM "${m.selectedTable}"`,t=Object.entries(w).filter(([,e])=>e.trim()).map(([e,t])=>`"${e}" ILIKE '%${t.replace(/'/g,`''`)}%'`);t.length>0&&(e+=` WHERE ${t.join(` AND `)}`),m.orderBy&&(e+=` ORDER BY "${m.orderBy}" ${m.orderDir}`);let n=(m.page-1)*100;return e+=` LIMIT 100`,n>0&&(e+=` OFFSET ${n}`),e}return`SELECT * FROM `},[p,m.selectedTable,m.orderBy,m.orderDir,m.page,w]),D=(0,d.useCallback)(e=>{T(e)},[]),O=(0,d.useRef)(void 0);(0,d.useEffect)(()=>{if(!(!m.selectedTable||Object.keys(w).length===0))return clearTimeout(O.current),O.current=setTimeout(()=>{m.queryAsTable(E)},500),()=>clearTimeout(O.current)},[w]);let k=(0,d.useCallback)(e=>{e.preventDefault();let t=e.clientY,n=x,r=e=>{let r=e.clientY-t;S(Math.max(80,Math.min(n+r,(C.current?.clientHeight??600)-100)))},i=()=>{document.removeEventListener(`mousemove`,r),document.removeEventListener(`mouseup`,i)};document.addEventListener(`mousemove`,r),document.addEventListener(`mouseup`,i)},[x]);(0,d.useEffect)(()=>{s.get(`/api/db/connections/${t}/tables?cached=1`).then(e=>y(e.map(e=>({name:e.name,schema:e.schema})))).catch(()=>{})},[t]);let A=(0,d.useMemo)(()=>{if(v.length!==0)return{tables:v,getColumns:async(e,n)=>await s.get(`/api/db/connections/${t}/schema?table=${encodeURIComponent(e)}${n?`&schema=${encodeURIComponent(n)}`:``}`)}},[t,v]),j=(0,d.useRef)(!1);(0,d.useEffect)(()=>{j.current||(j.current=!0,p?m.executeQuery(p):c&&m.selectTable(c,f))},[c,f,p]);let[M,N]=(0,d.useState)(!!p),P=(0,d.useCallback)(e=>{let t=e.trim();if(m.selectedTable&&RegExp(`^SELECT\\s+\\*\\s+FROM\\s+"?${m.selectedTable.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}"?\\b`,`i`).test(t)){T(_(t)),m.queryAsTable(t);return}N(!0),m.executeQuery(e)},[m.executeQuery,m.queryAsTable,m.selectedTable]),F=(0,d.useCallback)(e=>{N(!1),m.toggleSort(e)},[m.toggleSort]),I=(0,d.useCallback)(()=>{N(!1),m.clearSort()},[m.clearSort]),L=(0,d.useCallback)(e=>{N(!1),m.setPage(e)},[m.setPage]),R=m.queryResult,z=M&&!!(R||m.queryError),B=m.selectedTable&&!z;return(0,g.jsx)(`div`,{ref:C,className:`flex h-full w-full overflow-hidden`,children:(0,g.jsxs)(`div`,{className:`flex-1 flex flex-col overflow-hidden`,children:[(0,g.jsxs)(`div`,{className:`flex items-center gap-2 px-3 py-1.5 border-b border-border bg-background shrink-0`,children:[(0,g.jsx)(r,{className:`size-3.5 text-muted-foreground`}),(0,g.jsx)(`span`,{className:`text-xs text-muted-foreground truncate`,children:n??`Database`}),m.selectedTable&&(0,g.jsxs)(`span`,{className:`text-xs text-muted-foreground`,children:[`/ `,m.selectedTable]}),(0,g.jsxs)(`div`,{className:`ml-auto flex items-center gap-1`,children:[m.tableData&&(0,g.jsx)(i,{columns:m.tableData.columns,rows:m.tableData.rows,filename:n?`${n}-${m.selectedTable??`data`}`:m.selectedTable??`data`,exportAllUrl:m.selectedTable?`/api/db/connections/${t}/export?table=${encodeURIComponent(m.selectedTable)}&schema=${m.selectedSchema}`:void 0}),(0,g.jsx)(`button`,{type:`button`,onClick:()=>m.refreshData(),title:`Reload data`,className:`p-1 rounded text-muted-foreground hover:text-foreground transition-colors`,children:(0,g.jsx)(o,{className:`size-3 ${m.loading?`animate-spin`:``}`})})]})]}),(0,g.jsx)(`div`,{className:`shrink-0 border-b border-border`,style:{height:x},children:(0,g.jsx)(u,{onExecute:P,loading:m.queryLoading,defaultValue:E,schemaInfo:A,cacheKey:t?String(t):void 0})}),(0,g.jsx)(`div`,{onMouseDown:k,className:`shrink-0 h-1.5 cursor-row-resize bg-border/50 hover:bg-primary/30 flex items-center justify-center transition-colors`,children:(0,g.jsx)(l,{className:`size-3 text-muted-foreground/50`})}),(0,g.jsxs)(`div`,{className:`flex-1 overflow-hidden`,children:[B&&m.tableData&&(0,g.jsx)(a,{columns:m.tableData.columns,rows:m.tableData.rows,total:m.tableData.total,limit:m.tableData.limit,schema:m.schema,loading:m.loading,page:m.page,onPageChange:L,onCellUpdate:m.updateCell,onRowDelete:m.deleteRow,orderBy:m.orderBy,orderDir:m.orderDir,onToggleSort:F,onClearSort:I,onBulkDelete:m.bulkDelete,onInsertRow:m.insertRow,connectionId:t,selectedTable:m.selectedTable,selectedSchema:m.selectedSchema,connectionName:n,columnFilters:w,onColumnFilter:D}),z&&(0,g.jsx)(b,{result:R,error:m.queryError,loading:m.queryLoading,schema:m.schema,connectionName:n})]})]})})}var y=()=>{};function b({result:e,error:t,loading:n,schema:r,connectionName:i}){let o=(0,d.useMemo)(()=>e?.changeType===`select`&&e.rows.length>0?{columns:e.columns,rows:e.rows,total:e.rows.length,limit:e.rows.length}:null,[e]),s=(0,d.useMemo)(()=>r?.length?r:(e?.columns??[]).map(e=>({name:e,type:`text`,nullable:!0,pk:!1,defaultValue:null})),[r,e?.columns]);return(0,g.jsxs)(`div`,{className:`flex flex-col h-full overflow-hidden text-xs`,children:[t&&(0,g.jsx)(`div`,{className:`px-3 py-2 text-destructive bg-destructive/5 shrink-0`,children:t}),e?.changeType===`modify`&&(0,g.jsxs)(`div`,{className:`px-3 py-2 text-green-500 shrink-0`,children:[e.rowsAffected,` row(s) affected`,e.executionTimeMs!=null&&(0,g.jsxs)(`span`,{className:`text-muted-foreground ml-2`,children:[e.executionTimeMs,`ms`]})]}),o&&(0,g.jsxs)(`div`,{className:`flex-1 overflow-hidden`,children:[(0,g.jsx)(a,{columns:o.columns,rows:o.rows,total:o.total,limit:o.limit,schema:s,loading:!!n,page:1,onPageChange:y,onCellUpdate:y,orderBy:null,orderDir:`ASC`,onToggleSort:y,connectionName:i}),e?.executionTimeMs!=null&&(0,g.jsxs)(`div`,{className:`px-3 py-0.5 border-t border-border text-[10px] text-muted-foreground shrink-0`,children:[e.rows.length,` rows · `,e.executionTimeMs,`ms`]})]}),e?.changeType===`select`&&e.rows.length===0&&(0,g.jsxs)(`div`,{className:`px-3 py-2 text-muted-foreground shrink-0`,children:[`No results`,e.executionTimeMs!=null&&(0,g.jsxs)(`span`,{className:`ml-2 text-muted-foreground/60`,children:[e.executionTimeMs,`ms`]})]}),!e&&!t&&(0,g.jsx)(`div`,{className:`flex items-center justify-center h-full text-muted-foreground`,children:n?(0,g.jsx)(c,{className:`size-4 animate-spin`}):`Run a query to see results`})]})}export{v as DatabaseViewer};
|
|
1
|
+
import{o as e}from"./rolldown-runtime-FhOqtrmT.js";import{b as t,x as n}from"./vendor-markdown-0Mxgxy0L.js";import"./vendor-ui-UXCWAcmi.js";import{t as r}from"./database-DOWH9-Vv.js";import{n as i,t as a}from"./glide-data-grid-CqT8WzTs.js";import{t as o}from"./refresh-cw-BjrAbUJe.js";import{t as s}from"./api-client-DIhJ5qVW.js";import"./settings-store-CVrIYYCB.js";import"./vendor-mermaid-Cl50p6TB.js";import"./tab-store-S9w6U5gm.js";import{G as c,K as l}from"./index-DJtqbPFT.js";import"./data-grid-types-BISkUXAY.js";import"./use-monaco-theme-BePWbY58.js";import{t as u}from"./sql-query-editor-lSlKMPlG.js";var d=e(n(),1);function f(e,t,n,r){return`ppm-db-${e}-${n}.${t}-p${r}`}function p(e,t,n,r){try{let i=sessionStorage.getItem(f(e,t,n,r));return i?JSON.parse(i):null}catch{return null}}function m(e,t,n,r,i,a){try{sessionStorage.setItem(f(e,t,n,r),JSON.stringify({data:i,cols:a}))}catch{}}function h(e){let t=`/api/db/connections/${e}`,[n,r]=(0,d.useState)(null),[i,a]=(0,d.useState)(`public`),[o,c]=(0,d.useState)(null),[l,u]=(0,d.useState)([]),[f,h]=(0,d.useState)(!1),[g,_]=(0,d.useState)(null),[v,y]=(0,d.useState)(1),[b,x]=(0,d.useState)(null),[S,C]=(0,d.useState)(null),[w,T]=(0,d.useState)(!1),[E,D]=(0,d.useState)(null),[O,k]=(0,d.useState)(`ASC`),A=(0,d.useCallback)(async(r,a,o,l,d)=>{let f=r??n,p=a??i;if(!f)return;h(!0);let g=l===void 0?E:l,y=d??O;try{let n=g?`&orderBy=${encodeURIComponent(g)}&orderDir=${y}`:``,[r,i]=await Promise.all([s.get(`${t}/data?table=${encodeURIComponent(f)}&schema=${p}&page=${o??v}&limit=100${n}`),s.get(`${t}/schema?table=${encodeURIComponent(f)}&schema=${p}`)]);c(r),u(i),m(e,f,p,o??v,r,i)}catch(e){_(e.message)}finally{h(!1)}},[t,e,n,i,v,E,O]),j=(0,d.useCallback)((t,n=`public`)=>{r(t),a(n),y(1),x(null);let i=p(e,t,n,1);i?(c(i.data),u(i.cols),h(!1),A(t,n,1)):A(t,n,1)},[e,A]),M=(0,d.useCallback)(e=>{y(e),A(void 0,void 0,e)},[A]),N=(0,d.useCallback)(async e=>{T(!0),C(null);try{let r=await s.post(`${t}/query`,{sql:e});x(r),r.changeType===`modify`&&A(n??void 0,i)}catch(e){C(e.message)}finally{T(!1)}},[t,n,i,A]),P=(0,d.useCallback)(async(e,r,a,o)=>{if(!n)return;let c=n,l=i;try{await s.put(`${t}/cell`,{table:c,schema:l,pkColumn:e,pkValue:r,column:a,value:o}),A(c,l)}catch(e){_(e.message)}},[t,n,i,A]),F=(0,d.useCallback)(async(e,r)=>{if(!n)return;let a=n,o=i;try{await s.del(`${t}/row`,{table:a,schema:o,pkColumn:e,pkValue:r}),A(a,o)}catch(e){_(e.message)}},[t,n,i,A]);return{selectedTable:n,selectedSchema:i,selectTable:j,tableData:o,schema:l,loading:f,error:g,page:v,setPage:M,orderBy:E,orderDir:O,toggleSort:(0,d.useCallback)(e=>{let t,n=`ASC`;E===e?O===`ASC`?(t=e,n=`DESC`):(t=null,n=`ASC`):(t=e,n=`ASC`),D(t),k(n),y(1),A(void 0,void 0,1,t,n)},[E,O,A]),clearSort:(0,d.useCallback)(()=>{D(null),k(`ASC`),y(1),A(void 0,void 0,1,null,`ASC`)},[A]),queryResult:b,queryError:S,queryLoading:w,executeQuery:N,updateCell:P,deleteRow:F,bulkDelete:(0,d.useCallback)(async(e,r)=>{if(!n)return;let a=n,o=i;try{await s.post(`${t}/rows/delete`,{table:a,schema:o,pkColumn:e,pkValues:r}),A(a,o)}catch(e){_(e.message)}},[t,n,i,A]),insertRow:(0,d.useCallback)(async e=>{if(!n)return;let r=n,a=i;try{await s.post(`${t}/row`,{table:r,schema:a,values:e}),A(r,a)}catch(e){throw _(e.message),e}},[t,n,i,A]),refreshData:A,queryAsTable:(0,d.useCallback)(async e=>{h(!0);try{let n=await s.post(`${t}/query`,{sql:e});n.changeType===`select`&&c({columns:n.columns,rows:n.rows,total:n.rows.length,page:1,limit:n.rows.length})}catch(e){_(e.message)}finally{h(!1)}},[t])}}var g=t();function _(e){let t={},n=/"(\w+)"\s+ILIKE\s+'%([^']*?)%'/gi,r;for(;(r=n.exec(e))!==null;)t[r[1]]=r[2].replace(/''/g,`'`);return t}function v({metadata:e}){let t=e?.connectionId,n=e?.connectionName,c=e?.tableName,f=e?.schemaName??`public`,p=e?.initialSql,m=h(t),[v,y]=(0,d.useState)([]),[x,S]=(0,d.useState)(180),C=(0,d.useRef)(null),[w,T]=(0,d.useState)({}),E=(0,d.useMemo)(()=>{if(p&&!m.selectedTable)return p;if(m.selectedTable){let e=`SELECT * FROM "${m.selectedTable}"`,t=Object.entries(w).filter(([,e])=>e.trim()).map(([e,t])=>`"${e}" ILIKE '%${t.replace(/'/g,`''`)}%'`);t.length>0&&(e+=` WHERE ${t.join(` AND `)}`),m.orderBy&&(e+=` ORDER BY "${m.orderBy}" ${m.orderDir}`);let n=(m.page-1)*100;return e+=` LIMIT 100`,n>0&&(e+=` OFFSET ${n}`),e}return`SELECT * FROM `},[p,m.selectedTable,m.orderBy,m.orderDir,m.page,w]),D=(0,d.useCallback)(e=>{T(e)},[]),O=(0,d.useRef)(void 0);(0,d.useEffect)(()=>{if(!(!m.selectedTable||Object.keys(w).length===0))return clearTimeout(O.current),O.current=setTimeout(()=>{m.queryAsTable(E)},500),()=>clearTimeout(O.current)},[w]);let k=(0,d.useCallback)(e=>{e.preventDefault();let t=e.clientY,n=x,r=e=>{let r=e.clientY-t;S(Math.max(80,Math.min(n+r,(C.current?.clientHeight??600)-100)))},i=()=>{document.removeEventListener(`mousemove`,r),document.removeEventListener(`mouseup`,i)};document.addEventListener(`mousemove`,r),document.addEventListener(`mouseup`,i)},[x]);(0,d.useEffect)(()=>{s.get(`/api/db/connections/${t}/tables?cached=1`).then(e=>y(e.map(e=>({name:e.name,schema:e.schema})))).catch(()=>{})},[t]);let A=(0,d.useMemo)(()=>{if(v.length!==0)return{tables:v,getColumns:async(e,n)=>await s.get(`/api/db/connections/${t}/schema?table=${encodeURIComponent(e)}${n?`&schema=${encodeURIComponent(n)}`:``}`)}},[t,v]),j=(0,d.useRef)(!1);(0,d.useEffect)(()=>{j.current||(j.current=!0,p?m.executeQuery(p):c&&m.selectTable(c,f))},[c,f,p]);let[M,N]=(0,d.useState)(!!p),P=(0,d.useCallback)(e=>{let t=e.trim();if(m.selectedTable&&RegExp(`^SELECT\\s+\\*\\s+FROM\\s+"?${m.selectedTable.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}"?\\b`,`i`).test(t)){T(_(t)),m.queryAsTable(t);return}N(!0),m.executeQuery(e)},[m.executeQuery,m.queryAsTable,m.selectedTable]),F=(0,d.useCallback)(e=>{N(!1),m.toggleSort(e)},[m.toggleSort]),I=(0,d.useCallback)(()=>{N(!1),m.clearSort()},[m.clearSort]),L=(0,d.useCallback)(e=>{N(!1),m.setPage(e)},[m.setPage]),R=m.queryResult,z=M&&!!(R||m.queryError),B=m.selectedTable&&!z;return(0,g.jsx)(`div`,{ref:C,className:`flex h-full w-full overflow-hidden`,children:(0,g.jsxs)(`div`,{className:`flex-1 flex flex-col overflow-hidden`,children:[(0,g.jsxs)(`div`,{className:`flex items-center gap-2 px-3 py-1.5 border-b border-border bg-background shrink-0`,children:[(0,g.jsx)(r,{className:`size-3.5 text-muted-foreground`}),(0,g.jsx)(`span`,{className:`text-xs text-muted-foreground truncate`,children:n??`Database`}),m.selectedTable&&(0,g.jsxs)(`span`,{className:`text-xs text-muted-foreground`,children:[`/ `,m.selectedTable]}),(0,g.jsxs)(`div`,{className:`ml-auto flex items-center gap-1`,children:[m.tableData&&(0,g.jsx)(i,{columns:m.tableData.columns,rows:m.tableData.rows,filename:n?`${n}-${m.selectedTable??`data`}`:m.selectedTable??`data`,exportAllUrl:m.selectedTable?`/api/db/connections/${t}/export?table=${encodeURIComponent(m.selectedTable)}&schema=${m.selectedSchema}`:void 0}),(0,g.jsx)(`button`,{type:`button`,onClick:()=>m.refreshData(),title:`Reload data`,className:`p-1 rounded text-muted-foreground hover:text-foreground transition-colors`,children:(0,g.jsx)(o,{className:`size-3 ${m.loading?`animate-spin`:``}`})})]})]}),(0,g.jsx)(`div`,{className:`shrink-0 border-b border-border`,style:{height:x},children:(0,g.jsx)(u,{onExecute:P,loading:m.queryLoading,defaultValue:E,schemaInfo:A,cacheKey:t?String(t):void 0})}),(0,g.jsx)(`div`,{onMouseDown:k,className:`shrink-0 h-1.5 cursor-row-resize bg-border/50 hover:bg-primary/30 flex items-center justify-center transition-colors`,children:(0,g.jsx)(l,{className:`size-3 text-muted-foreground/50`})}),(0,g.jsxs)(`div`,{className:`flex-1 overflow-hidden`,children:[B&&m.tableData&&(0,g.jsx)(a,{columns:m.tableData.columns,rows:m.tableData.rows,total:m.tableData.total,limit:m.tableData.limit,schema:m.schema,loading:m.loading,page:m.page,onPageChange:L,onCellUpdate:m.updateCell,onRowDelete:m.deleteRow,orderBy:m.orderBy,orderDir:m.orderDir,onToggleSort:F,onClearSort:I,onBulkDelete:m.bulkDelete,onInsertRow:m.insertRow,connectionId:t,selectedTable:m.selectedTable,selectedSchema:m.selectedSchema,connectionName:n,columnFilters:w,onColumnFilter:D}),z&&(0,g.jsx)(b,{result:R,error:m.queryError,loading:m.queryLoading,schema:m.schema,connectionName:n})]})]})})}var y=()=>{};function b({result:e,error:t,loading:n,schema:r,connectionName:i}){let o=(0,d.useMemo)(()=>e?.changeType===`select`&&e.rows.length>0?{columns:e.columns,rows:e.rows,total:e.rows.length,limit:e.rows.length}:null,[e]),s=(0,d.useMemo)(()=>r?.length?r:(e?.columns??[]).map(e=>({name:e,type:`text`,nullable:!0,pk:!1,defaultValue:null})),[r,e?.columns]);return(0,g.jsxs)(`div`,{className:`flex flex-col h-full overflow-hidden text-xs`,children:[t&&(0,g.jsx)(`div`,{className:`px-3 py-2 text-destructive bg-destructive/5 shrink-0`,children:t}),e?.changeType===`modify`&&(0,g.jsxs)(`div`,{className:`px-3 py-2 text-green-500 shrink-0`,children:[e.rowsAffected,` row(s) affected`,e.executionTimeMs!=null&&(0,g.jsxs)(`span`,{className:`text-muted-foreground ml-2`,children:[e.executionTimeMs,`ms`]})]}),o&&(0,g.jsxs)(`div`,{className:`flex-1 overflow-hidden`,children:[(0,g.jsx)(a,{columns:o.columns,rows:o.rows,total:o.total,limit:o.limit,schema:s,loading:!!n,page:1,onPageChange:y,onCellUpdate:y,orderBy:null,orderDir:`ASC`,onToggleSort:y,connectionName:i}),e?.executionTimeMs!=null&&(0,g.jsxs)(`div`,{className:`px-3 py-0.5 border-t border-border text-[10px] text-muted-foreground shrink-0`,children:[e.rows.length,` rows · `,e.executionTimeMs,`ms`]})]}),e?.changeType===`select`&&e.rows.length===0&&(0,g.jsxs)(`div`,{className:`px-3 py-2 text-muted-foreground shrink-0`,children:[`No results`,e.executionTimeMs!=null&&(0,g.jsxs)(`span`,{className:`ml-2 text-muted-foreground/60`,children:[e.executionTimeMs,`ms`]})]}),!e&&!t&&(0,g.jsx)(`div`,{className:`flex items-center justify-center h-full text-muted-foreground`,children:n?(0,g.jsx)(c,{className:`size-4 animate-spin`}):`Run a query to see results`})]})}export{v as DatabaseViewer};
|
|
2
|
+
//# sourceMappingURL=database-viewer-Boup8MJ_.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database-viewer-Boup8MJ_.js","names":[],"sources":["../../../src/web/components/database/use-database.ts","../../../src/web/components/database/database-viewer.tsx"],"sourcesContent":["import { useState, useCallback } from \"react\";\nimport { api } from \"@/lib/api-client\";\n\nexport interface DbTableInfo { name: string; schema: string; rowCount: number }\nexport interface DbColumnInfo { name: string; type: string; nullable: boolean; pk: boolean; defaultValue: string | null; fk?: { table: string; column: string } | null }\nexport interface DbQueryResult { columns: string[]; rows: Record<string, unknown>[]; rowsAffected: number; changeType: \"select\" | \"modify\"; executionTimeMs?: number }\ninterface DbTableData { columns: string[]; rows: Record<string, unknown>[]; total: number; page: number; limit: number }\n\n/** SessionStorage cache key for table data */\nfunction cacheKey(connectionId: number, table: string, schema: string, page: number) {\n return `ppm-db-${connectionId}-${schema}.${table}-p${page}`;\n}\n\nfunction readCache(connectionId: number, table: string, schema: string, page: number): { data: DbTableData; cols: DbColumnInfo[] } | null {\n try {\n const raw = sessionStorage.getItem(cacheKey(connectionId, table, schema, page));\n return raw ? JSON.parse(raw) : null;\n } catch { return null; }\n}\n\nfunction writeCache(connectionId: number, table: string, schema: string, page: number, data: DbTableData, cols: DbColumnInfo[]) {\n try { sessionStorage.setItem(cacheKey(connectionId, table, schema, page), JSON.stringify({ data, cols })); } catch { /* quota */ }\n}\n\n/**\n * Generic database hook for unified API (/api/db/connections/:id/...).\n * Works for any DB type (postgres, sqlite, mysql, etc.) via adapter pattern.\n * No auto-fetch on mount — viewer calls selectTable() to start loading.\n */\nexport function useDatabase(connectionId: number) {\n const base = `/api/db/connections/${connectionId}`;\n const [selectedTable, setSelectedTable] = useState<string | null>(null);\n const [selectedSchema, setSelectedSchema] = useState(\"public\");\n const [tableData, setTableData] = useState<DbTableData | null>(null);\n const [schema, setSchema] = useState<DbColumnInfo[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [page, setPageState] = useState(1);\n const [queryResult, setQueryResult] = useState<DbQueryResult | null>(null);\n const [queryError, setQueryError] = useState<string | null>(null);\n const [queryLoading, setQueryLoading] = useState(false);\n // Sort state\n const [orderBy, setOrderBy] = useState<string | null>(null);\n const [orderDir, setOrderDir] = useState<\"ASC\" | \"DESC\">(\"ASC\");\n\n // Fetch table data + schema for current selection\n const fetchTableData = useCallback(async (table?: string, tableSchema?: string, p?: number, sortCol?: string | null, sortDir?: \"ASC\" | \"DESC\") => {\n const t = table ?? selectedTable;\n const s = tableSchema ?? selectedSchema;\n if (!t) return;\n setLoading(true);\n const ob = sortCol !== undefined ? sortCol : orderBy;\n const od = sortDir ?? orderDir;\n try {\n const orderParams = ob ? `&orderBy=${encodeURIComponent(ob)}&orderDir=${od}` : \"\";\n const [data, cols] = await Promise.all([\n api.get<DbTableData>(`${base}/data?table=${encodeURIComponent(t)}&schema=${s}&page=${p ?? page}&limit=100${orderParams}`),\n api.get<DbColumnInfo[]>(`${base}/schema?table=${encodeURIComponent(t)}&schema=${s}`),\n ]);\n setTableData(data);\n setSchema(cols);\n writeCache(connectionId, t, s, p ?? page, data, cols);\n } catch (e) {\n setError((e as Error).message);\n } finally {\n setLoading(false);\n }\n }, [base, connectionId, selectedTable, selectedSchema, page, orderBy, orderDir]);\n\n const selectTable = useCallback((name: string, tableSchema = \"public\") => {\n setSelectedTable(name);\n setSelectedSchema(tableSchema);\n setPageState(1);\n setQueryResult(null);\n // Show cached data instantly, then refresh in background\n const cached = readCache(connectionId, name, tableSchema, 1);\n if (cached) {\n setTableData(cached.data);\n setSchema(cached.cols);\n setLoading(false);\n // Still fetch fresh data in background\n fetchTableData(name, tableSchema, 1);\n } else {\n fetchTableData(name, tableSchema, 1);\n }\n }, [connectionId, fetchTableData]);\n\n const changePage = useCallback((p: number) => {\n setPageState(p);\n fetchTableData(undefined, undefined, p);\n }, [fetchTableData]);\n\n const executeQuery = useCallback(async (sqlText: string) => {\n setQueryLoading(true);\n setQueryError(null);\n try {\n const result = await api.post<DbQueryResult>(`${base}/query`, { sql: sqlText });\n setQueryResult(result);\n if (result.changeType === \"modify\") fetchTableData(selectedTable ?? undefined, selectedSchema);\n } catch (e) {\n setQueryError((e as Error).message);\n } finally {\n setQueryLoading(false);\n }\n }, [base, selectedTable, selectedSchema, fetchTableData]);\n\n const updateCell = useCallback(async (pkColumn: string, pkValue: unknown, column: string, value: unknown) => {\n if (!selectedTable) return;\n const t = selectedTable;\n const s = selectedSchema;\n try {\n await api.put(`${base}/cell`, { table: t, schema: s, pkColumn, pkValue, column, value });\n // Re-fetch with explicit args to avoid stale closure\n fetchTableData(t, s);\n } catch (e) {\n setError((e as Error).message);\n }\n }, [base, selectedTable, selectedSchema, fetchTableData]);\n\n const deleteRow = useCallback(async (pkColumn: string, pkValue: unknown) => {\n if (!selectedTable) return;\n const t = selectedTable;\n const s = selectedSchema;\n try {\n await api.del(`${base}/row`, { table: t, schema: s, pkColumn, pkValue });\n fetchTableData(t, s);\n } catch (e) {\n setError((e as Error).message);\n }\n }, [base, selectedTable, selectedSchema, fetchTableData]);\n\n /** Toggle sort: none → ASC → DESC → none */\n const toggleSort = useCallback((column: string) => {\n let newCol: string | null;\n let newDir: \"ASC\" | \"DESC\" = \"ASC\";\n if (orderBy !== column) {\n newCol = column; newDir = \"ASC\";\n } else if (orderDir === \"ASC\") {\n newCol = column; newDir = \"DESC\";\n } else {\n newCol = null; newDir = \"ASC\";\n }\n setOrderBy(newCol);\n setOrderDir(newDir);\n setPageState(1);\n fetchTableData(undefined, undefined, 1, newCol, newDir);\n }, [orderBy, orderDir, fetchTableData]);\n\n /** Clear sort entirely */\n const clearSort = useCallback(() => {\n setOrderBy(null);\n setOrderDir(\"ASC\");\n setPageState(1);\n fetchTableData(undefined, undefined, 1, null, \"ASC\");\n }, [fetchTableData]);\n\n /** Bulk delete rows */\n const bulkDelete = useCallback(async (pkColumn: string, pkValues: unknown[]) => {\n if (!selectedTable) return;\n const t = selectedTable;\n const s = selectedSchema;\n try {\n await api.post(`${base}/rows/delete`, { table: t, schema: s, pkColumn, pkValues });\n fetchTableData(t, s);\n } catch (e) {\n setError((e as Error).message);\n }\n }, [base, selectedTable, selectedSchema, fetchTableData]);\n\n /** Insert a new row */\n const insertRow = useCallback(async (values: Record<string, unknown>) => {\n if (!selectedTable) return;\n const t = selectedTable;\n const s = selectedSchema;\n try {\n await api.post(`${base}/row`, { table: t, schema: s, values });\n fetchTableData(t, s);\n } catch (e) {\n setError((e as Error).message);\n throw e;\n }\n }, [base, selectedTable, selectedSchema, fetchTableData]);\n\n /** Execute SQL but put results into tableData (for column filters) */\n const queryAsTable = useCallback(async (sqlText: string) => {\n setLoading(true);\n try {\n const result = await api.post<DbQueryResult>(`${base}/query`, { sql: sqlText });\n if (result.changeType === \"select\") {\n setTableData({ columns: result.columns, rows: result.rows, total: result.rows.length, page: 1, limit: result.rows.length });\n }\n } catch (e) {\n setError((e as Error).message);\n } finally {\n setLoading(false);\n }\n }, [base]);\n\n return {\n selectedTable, selectedSchema, selectTable, tableData, schema,\n loading, error, page, setPage: changePage,\n orderBy, orderDir, toggleSort, clearSort,\n queryResult, queryError, queryLoading, executeQuery,\n updateCell, deleteRow, bulkDelete, insertRow,\n refreshData: fetchTableData, queryAsTable,\n };\n}\n","import { useState, useMemo, useEffect, useRef, useCallback } from \"react\";\nimport { Database, RefreshCw, GripHorizontal, Loader2 } from \"lucide-react\";\nimport { api } from \"@/lib/api-client\";\nimport { useDatabase, type DbColumnInfo } from \"./use-database\";\nimport { SqlQueryEditor } from \"./sql-query-editor\";\nimport { ExportButton } from \"./export-button\";\nimport { GlideDataGrid } from \"./glide-data-grid\";\nimport type { SchemaInfo } from \"./sql-completion-provider\";\n\n/** Parse WHERE \"col\" ILIKE '%val%' clauses from SQL */\nfunction parseSqlFilters(sql: string): Record<string, string> {\n const filters: Record<string, string> = {};\n const re = /\"(\\w+)\"\\s+ILIKE\\s+'%([^']*?)%'/gi;\n let m: RegExpExecArray | null;\n while ((m = re.exec(sql)) !== null) {\n filters[m[1]!] = m[2]!.replace(/''/g, \"'\");\n }\n return filters;\n}\n\ninterface Props { metadata?: Record<string, unknown>; tabId?: string }\n\n/** Generic database viewer — works for any DB type via unified API */\nexport function DatabaseViewer({ metadata }: Props) {\n const connectionId = metadata?.connectionId as number;\n const connectionName = metadata?.connectionName as string | undefined;\n const initialTable = metadata?.tableName as string | undefined;\n const initialSchema = (metadata?.schemaName as string) ?? \"public\";\n const initialSql = metadata?.initialSql as string | undefined;\n\n const db = useDatabase(connectionId);\n const [cachedTableNames, setCachedTableNames] = useState<{ name: string; schema: string }[]>([]);\n const [queryHeight, setQueryHeight] = useState(180);\n const containerRef = useRef<HTMLDivElement>(null);\n\n // Column ILIKE filters from DataGrid header\n const [columnFilters, setColumnFilters] = useState<Record<string, string>>({});\n\n // Build query text reflecting current table state (sort, page, filters)\n const defaultQuery = useMemo(() => {\n if (initialSql && !db.selectedTable) return initialSql;\n if (db.selectedTable) {\n let sql = `SELECT * FROM \"${db.selectedTable}\"`;\n const whereParts = Object.entries(columnFilters)\n .filter(([, v]) => v.trim())\n .map(([col, v]) => `\"${col}\" ILIKE '%${v.replace(/'/g, \"''\")}%'`);\n if (whereParts.length > 0) sql += ` WHERE ${whereParts.join(\" AND \")}`;\n if (db.orderBy) sql += ` ORDER BY \"${db.orderBy}\" ${db.orderDir}`;\n const offset = (db.page - 1) * 100;\n sql += ` LIMIT 100`;\n if (offset > 0) sql += ` OFFSET ${offset}`;\n return sql;\n }\n return \"SELECT * FROM \";\n }, [initialSql, db.selectedTable, db.orderBy, db.orderDir, db.page, columnFilters]);\n\n // When column filters change, auto-execute the query with debounce\n const handleColumnFilter = useCallback((filters: Record<string, string>) => {\n setColumnFilters(filters);\n }, []);\n const filterTimerRef = useRef<ReturnType<typeof setTimeout>>(undefined);\n useEffect(() => {\n if (!db.selectedTable || Object.keys(columnFilters).length === 0) return;\n clearTimeout(filterTimerRef.current);\n filterTimerRef.current = setTimeout(() => {\n // Execute filter query into tableData — stays in table grid mode\n db.queryAsTable(defaultQuery);\n }, 500);\n return () => clearTimeout(filterTimerRef.current);\n }, [columnFilters]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Drag resize handler\n const handleDragStart = useCallback((e: React.MouseEvent) => {\n e.preventDefault();\n const startY = e.clientY;\n const startH = queryHeight;\n const onMove = (ev: MouseEvent) => {\n const delta = ev.clientY - startY;\n const newH = Math.max(80, Math.min(startH + delta, (containerRef.current?.clientHeight ?? 600) - 100));\n setQueryHeight(newH);\n };\n const onUp = () => { document.removeEventListener(\"mousemove\", onMove); document.removeEventListener(\"mouseup\", onUp); };\n document.addEventListener(\"mousemove\", onMove);\n document.addEventListener(\"mouseup\", onUp);\n }, [queryHeight]);\n\n // Fetch table names for autocomplete once on mount\n useEffect(() => {\n api.get<{ name: string; schema: string; rowCount: number }[]>(`/api/db/connections/${connectionId}/tables?cached=1`)\n .then((tables) => setCachedTableNames(tables.map((t) => ({ name: t.name, schema: t.schema }))))\n .catch(() => {});\n }, [connectionId]);\n\n // Build SchemaInfo for autocomplete\n const schemaInfo = useMemo<SchemaInfo | undefined>(() => {\n if (cachedTableNames.length === 0) return undefined;\n return {\n tables: cachedTableNames,\n getColumns: async (table: string, schema?: string) => {\n const cols = await api.get<{ name: string; type: string }[]>(\n `/api/db/connections/${connectionId}/schema?table=${encodeURIComponent(table)}${schema ? `&schema=${encodeURIComponent(schema)}` : \"\"}`,\n );\n return cols;\n },\n };\n }, [connectionId, cachedTableNames]);\n\n // Jump to initial table\n const didInit = useRef(false);\n useEffect(() => {\n if (didInit.current) return;\n didInit.current = true;\n if (initialSql) {\n db.executeQuery(initialSql);\n } else if (initialTable) {\n db.selectTable(initialTable, initialSchema);\n }\n }, [initialTable, initialSchema, initialSql]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Track whether user ran a custom query (show results instead of table grid)\n const [showingQueryResult, setShowingQueryResult] = useState(!!initialSql);\n const handleExecuteQuery = useCallback((sql: string) => {\n const trimmed = sql.trim();\n // Check if query is a simple SELECT on the current table — stay in table grid mode\n if (db.selectedTable) {\n const tablePattern = new RegExp(`^SELECT\\\\s+\\\\*\\\\s+FROM\\\\s+\"?${db.selectedTable.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}\"?\\\\b`, \"i\");\n if (tablePattern.test(trimmed)) {\n // Parse ILIKE filters from SQL and sync to columnFilters\n const parsed = parseSqlFilters(trimmed);\n setColumnFilters(parsed);\n db.queryAsTable(trimmed);\n return;\n }\n }\n setShowingQueryResult(true);\n db.executeQuery(sql);\n }, [db.executeQuery, db.queryAsTable, db.selectedTable]);\n\n // When user interacts with DataGrid (sort/page), switch back to table view\n const handleToggleSort = useCallback((col: string) => {\n setShowingQueryResult(false);\n db.toggleSort(col);\n }, [db.toggleSort]);\n const handleClearSort = useCallback(() => {\n setShowingQueryResult(false);\n db.clearSort();\n }, [db.clearSort]);\n const handlePageChange = useCallback((p: number) => {\n setShowingQueryResult(false);\n db.setPage(p);\n }, [db.setPage]);\n\n const qr = db.queryResult;\n const showQueryResults = showingQueryResult && !!(qr || db.queryError);\n const showTableGrid = db.selectedTable && !showQueryResults;\n\n return (\n <div ref={containerRef} className=\"flex h-full w-full overflow-hidden\">\n <div className=\"flex-1 flex flex-col overflow-hidden\">\n {/* Toolbar */}\n <div className=\"flex items-center gap-2 px-3 py-1.5 border-b border-border bg-background shrink-0\">\n <Database className=\"size-3.5 text-muted-foreground\" />\n <span className=\"text-xs text-muted-foreground truncate\">{connectionName ?? \"Database\"}</span>\n {db.selectedTable && <span className=\"text-xs text-muted-foreground\">/ {db.selectedTable}</span>}\n <div className=\"ml-auto flex items-center gap-1\">\n {db.tableData && (\n <ExportButton\n columns={db.tableData.columns}\n rows={db.tableData.rows}\n filename={connectionName ? `${connectionName}-${db.selectedTable ?? \"data\"}` : db.selectedTable ?? \"data\"}\n exportAllUrl={db.selectedTable ? `/api/db/connections/${connectionId}/export?table=${encodeURIComponent(db.selectedTable)}&schema=${db.selectedSchema}` : undefined}\n />\n )}\n <button type=\"button\" onClick={() => db.refreshData()} title=\"Reload data\"\n className=\"p-1 rounded text-muted-foreground hover:text-foreground transition-colors\">\n <RefreshCw className={`size-3 ${db.loading ? \"animate-spin\" : \"\"}`} />\n </button>\n </div>\n </div>\n\n {/* Always-visible query editor at top */}\n <div className=\"shrink-0 border-b border-border\" style={{ height: queryHeight }}>\n <SqlQueryEditor\n onExecute={handleExecuteQuery} loading={db.queryLoading}\n defaultValue={defaultQuery} schemaInfo={schemaInfo}\n cacheKey={connectionId ? String(connectionId) : undefined} />\n </div>\n\n {/* Resize handle */}\n <div onMouseDown={handleDragStart}\n className=\"shrink-0 h-1.5 cursor-row-resize bg-border/50 hover:bg-primary/30 flex items-center justify-center transition-colors\">\n <GripHorizontal className=\"size-3 text-muted-foreground/50\" />\n </div>\n\n {/* Bottom panel: table data OR query results */}\n <div className=\"flex-1 overflow-hidden\">\n {showTableGrid && db.tableData && (\n <GlideDataGrid\n columns={db.tableData.columns} rows={db.tableData.rows}\n total={db.tableData.total} limit={db.tableData.limit}\n schema={db.schema} loading={db.loading}\n page={db.page} onPageChange={handlePageChange}\n onCellUpdate={db.updateCell} onRowDelete={db.deleteRow}\n orderBy={db.orderBy} orderDir={db.orderDir} onToggleSort={handleToggleSort} onClearSort={handleClearSort}\n onBulkDelete={db.bulkDelete} onInsertRow={db.insertRow}\n connectionId={connectionId} selectedTable={db.selectedTable} selectedSchema={db.selectedSchema}\n connectionName={connectionName} columnFilters={columnFilters} onColumnFilter={handleColumnFilter} />\n )}\n\n {showQueryResults && (\n <QueryResultPanel result={qr} error={db.queryError} loading={db.queryLoading} schema={db.schema} connectionName={connectionName} />\n )}\n </div>\n </div>\n </div>\n );\n}\n\nconst NOOP = () => {};\n\n/** Read-only result panel for ad-hoc query results — uses DataGrid for SELECT to get checkboxes + export */\nfunction QueryResultPanel({ result, error, loading, schema, connectionName }: {\n result: { columns: string[]; rows: Record<string, unknown>[]; rowsAffected: number; changeType: \"select\" | \"modify\"; executionTimeMs?: number } | null;\n error: string | null;\n loading?: boolean;\n schema?: DbColumnInfo[];\n connectionName?: string;\n}) {\n // Build a read-only DataGrid-compatible tableData from query result\n const queryTableData = useMemo(() => (\n result?.changeType === \"select\" && result.rows.length > 0\n ? { columns: result.columns, rows: result.rows, total: result.rows.length, limit: result.rows.length }\n : null\n ), [result]);\n\n // Use schema if available, otherwise build minimal schema from column names\n const querySchema = useMemo(() => (\n schema?.length ? schema : (result?.columns ?? []).map((c) => ({\n name: c, type: \"text\", nullable: true, pk: false, defaultValue: null,\n }))\n ), [schema, result?.columns]);\n\n return (\n <div className=\"flex flex-col h-full overflow-hidden text-xs\">\n {error && <div className=\"px-3 py-2 text-destructive bg-destructive/5 shrink-0\">{error}</div>}\n\n {result?.changeType === \"modify\" && (\n <div className=\"px-3 py-2 text-green-500 shrink-0\">\n {result.rowsAffected} row(s) affected\n {result.executionTimeMs != null && <span className=\"text-muted-foreground ml-2\">{result.executionTimeMs}ms</span>}\n </div>\n )}\n\n {queryTableData && (\n <div className=\"flex-1 overflow-hidden\">\n <GlideDataGrid\n columns={queryTableData.columns} rows={queryTableData.rows}\n total={queryTableData.total} limit={queryTableData.limit}\n schema={querySchema} loading={!!loading}\n page={1} onPageChange={NOOP} onCellUpdate={NOOP}\n orderBy={null} orderDir=\"ASC\" onToggleSort={NOOP}\n connectionName={connectionName}\n />\n {result?.executionTimeMs != null && (\n <div className=\"px-3 py-0.5 border-t border-border text-[10px] text-muted-foreground shrink-0\">\n {result.rows.length} rows · {result.executionTimeMs}ms\n </div>\n )}\n </div>\n )}\n\n {result?.changeType === \"select\" && result.rows.length === 0 && (\n <div className=\"px-3 py-2 text-muted-foreground shrink-0\">\n No results\n {result.executionTimeMs != null && <span className=\"ml-2 text-muted-foreground/60\">{result.executionTimeMs}ms</span>}\n </div>\n )}\n\n {!result && !error && (\n <div className=\"flex items-center justify-center h-full text-muted-foreground\">\n {loading ? <Loader2 className=\"size-4 animate-spin\" /> : \"Run a query to see results\"}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import{o as e}from"./rolldown-runtime-FhOqtrmT.js";import{b as t,x as n}from"./vendor-markdown-0Mxgxy0L.js";import{t as r}from"./text-wrap-DJz9Bgpa.js";import{i,t as a}from"./api-client-DIhJ5qVW.js";import{n as o}from"./settings-store-CVrIYYCB.js";import"./vendor-mermaid-Cl50p6TB.js";import{G as s,Z as c,h as l}from"./index-DJtqbPFT.js";import{r as u,t as d}from"./use-monaco-theme-BePWbY58.js";var f=e(n(),1),p=t();function m(e){return{js:`javascript`,jsx:`javascript`,ts:`typescript`,tsx:`typescript`,py:`python`,html:`html`,css:`css`,scss:`scss`,json:`json`,md:`markdown`,mdx:`markdown`,yaml:`yaml`,yml:`yaml`,sh:`shell`,bash:`shell`}[e.split(`.`).pop()?.toLowerCase()??``]??`plaintext`}function h({metadata:e}){let t=e?.filePath,n=e?.projectName,h=e?.ref1,_=e?.ref2,v=e?.file1,y=e?.file2,b=e?.original,x=e?.modified,S=b!=null||x!=null,C=!!(v&&y),[w,T]=(0,f.useState)(null),[E,D]=(0,f.useState)(null),[O,k]=(0,f.useState)(null),[A,j]=(0,f.useState)(!S),[M,N]=(0,f.useState)(null),{wordWrap:P,toggleWordWrap:F}=o(l(e=>({wordWrap:e.wordWrap,toggleWordWrap:e.toggleWordWrap}))),I=d(),L=(0,f.useRef)(null),R=(0,f.useRef)(null),[z,B]=(0,f.useState)(!1),[V,H]=(0,f.useState)();(0,f.useEffect)(()=>{let e=L.current;if(!e)return;let t=new ResizeObserver(([e])=>{e&&H(Math.floor(e.contentRect.height))});return t.observe(e),()=>t.disconnect()},[A,M]),(0,f.useEffect)(()=>{if(S||!n)return;if(j(!0),N(null),k(null),D(null),T(null),v&&y){let e=new URLSearchParams({file1:v,file2:y});a.get(`${i(n)}/files/compare?${e}`).then(e=>{D(e),j(!1)}).catch(e=>{N(e instanceof Error?e.message:`Failed to compare files`),j(!1)});return}if(t){let e=new URLSearchParams({file:t});h&&e.set(`ref`,h),a.get(`${i(n)}/git/file-full-diff?${e}`).then(e=>{k(e),j(!1)}).catch(e=>{N(e instanceof Error?e.message:`Failed to load diff`),j(!1)});return}let e;if(h||_){let t=new URLSearchParams;h&&t.set(`ref1`,h),_&&t.set(`ref2`,_),e=`${i(n)}/git/diff?${t}`}else e=`${i(n)}/git/diff`;a.get(e).then(e=>{T(e.diff),j(!1)}).catch(e=>{N(e instanceof Error?e.message:`Failed to load diff`),j(!1)})},[t,n,h,_,v,y,S]);let{original:U,modified:W}=(0,f.useMemo)(()=>S?{original:b??``,modified:x??``}:C&&E?E:O||(w?g(w):{original:``,modified:``}),[w,S,b,x,C,E,O]),G=(0,f.useMemo)(()=>{let e=t??y??v;return e?m(e):`plaintext`},[t,v,y]),K=typeof window<`u`&&window.innerWidth<768,q=!K;return(0,f.useEffect)(()=>{let e=R.current;if(!e)return;let t=K||P?`on`:`off`;e.updateOptions({diffWordWrap:t}),e.getOriginalEditor().updateOptions({wordWrapOverride2:t}),e.getModifiedEditor().updateOptions({wordWrapOverride2:t})},[P,K,z]),!n&&!S?(0,p.jsx)(`div`,{className:`flex items-center justify-center h-full text-muted-foreground text-sm`,children:`No project selected.`}):A?(0,p.jsxs)(`div`,{className:`flex items-center justify-center h-full gap-2 text-muted-foreground`,children:[(0,p.jsx)(s,{className:`size-5 animate-spin`}),(0,p.jsx)(`span`,{className:`text-sm`,children:`Loading diff...`})]}):M?(0,p.jsx)(`div`,{className:`flex items-center justify-center h-full text-destructive text-sm`,children:M}):!S&&!C&&!O&&!U&&!W?(0,p.jsxs)(`div`,{className:`flex flex-col items-center justify-center h-full gap-2 text-muted-foreground`,children:[(0,p.jsx)(c,{className:`size-8`}),(0,p.jsx)(`p`,{className:`text-sm`,children:`No content changes`}),t&&(0,p.jsx)(`p`,{className:`text-xs font-mono`,children:t})]}):(0,p.jsxs)(`div`,{className:`flex flex-col h-full`,children:[!K&&(0,p.jsx)(`div`,{className:`flex items-center justify-end gap-0.5 px-2 py-0.5 border-b border-border shrink-0`,children:(0,p.jsx)(`button`,{type:`button`,onClick:F,title:`Toggle word wrap`,className:`p-1 rounded hover:bg-muted transition-colors ${P?`bg-muted text-foreground`:``}`,children:(0,p.jsx)(r,{className:`size-3.5`})})}),(0,p.jsx)(`div`,{ref:L,className:`flex-1 overflow-hidden`,children:V&&V>0?(0,p.jsx)(u,{height:V,language:G,original:U,modified:W,theme:I,onMount:e=>{R.current=e,B(!0)},options:{fontSize:K?11:13,fontFamily:`Menlo, Monaco, Consolas, monospace`,diffWordWrap:K||P?`on`:`off`,renderSideBySide:q,useInlineViewWhenSpaceIsLimited:!1,readOnly:!0,automaticLayout:!0,scrollBeyondLastLine:!1},loading:(0,p.jsx)(s,{className:`size-5 animate-spin text-muted-foreground`})}):(0,p.jsx)(`div`,{className:`flex items-center justify-center h-full`,children:(0,p.jsx)(s,{className:`size-5 animate-spin text-muted-foreground`})})})]})}function g(e){let t=e.split(`
|
|
2
2
|
`),n=[],r=[],i=!1;for(let e of t)if(!(e.startsWith(`diff --git`)||e.startsWith(`diff --no-index`)||e.startsWith(`index `)||e.startsWith(`new file`)||e.startsWith(`deleted file`)||e.startsWith(`old mode`)||e.startsWith(`new mode`)||e.startsWith(`---`)||e.startsWith(`+++`)||e.startsWith(`Binary files`)||e.startsWith(`\\ No newline`))){if(e.startsWith(`@@`)){i=!0;continue}if(i)if(e.startsWith(`-`))n.push(e.slice(1));else if(e.startsWith(`+`))r.push(e.slice(1));else{let t=e.startsWith(` `)?e.slice(1):e;n.push(t),r.push(t)}}return{original:n.join(`
|
|
3
3
|
`),modified:r.join(`
|
|
4
|
-
`)}}export{h as DiffViewer};
|
|
4
|
+
`)}}export{h as DiffViewer};
|
|
5
|
+
//# sourceMappingURL=diff-viewer-Je2KYGME.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff-viewer-Je2KYGME.js","names":[],"sources":["../../../src/web/components/editor/diff-viewer.tsx"],"sourcesContent":["import { useEffect, useState, useMemo, useRef } from \"react\";\nimport { DiffEditor } from \"@monaco-editor/react\";\nimport { api, projectUrl } from \"@/lib/api-client\";\nimport { useShallow } from \"zustand/react/shallow\";\nimport { useSettingsStore } from \"@/stores/settings-store\";\nimport { useMonacoTheme } from \"@/lib/use-monaco-theme\";\nimport { Loader2, FileCode, WrapText } from \"lucide-react\";\n\nfunction getMonacoLanguage(filename: string): string {\n const ext = filename.split(\".\").pop()?.toLowerCase() ?? \"\";\n const map: Record<string, string> = {\n js: \"javascript\", jsx: \"javascript\",\n ts: \"typescript\", tsx: \"typescript\",\n py: \"python\", html: \"html\",\n css: \"css\", scss: \"scss\",\n json: \"json\", md: \"markdown\", mdx: \"markdown\",\n yaml: \"yaml\", yml: \"yaml\",\n sh: \"shell\", bash: \"shell\",\n };\n return map[ext] ?? \"plaintext\";\n}\n\ninterface DiffViewerProps {\n metadata?: Record<string, unknown>;\n}\n\nexport function DiffViewer({ metadata }: DiffViewerProps) {\n const filePath = metadata?.filePath as string | undefined;\n const projectName = metadata?.projectName as string | undefined;\n const ref1 = metadata?.ref1 as string | undefined;\n const ref2 = metadata?.ref2 as string | undefined;\n const file1 = metadata?.file1 as string | undefined;\n const file2 = metadata?.file2 as string | undefined;\n const inlineOriginal = metadata?.original as string | undefined;\n const inlineModified = metadata?.modified as string | undefined;\n const isInline = inlineOriginal != null || inlineModified != null;\n const isFileCompare = Boolean(file1 && file2);\n\n const [diffText, setDiffText] = useState<string | null>(null);\n const [fileContents, setFileContents] = useState<{ original: string; modified: string } | null>(null);\n const [fullFileDiff, setFullFileDiff] = useState<{ original: string; modified: string } | null>(null);\n const [loading, setLoading] = useState(!isInline);\n const [error, setError] = useState<string | null>(null);\n const { wordWrap, toggleWordWrap } = useSettingsStore(useShallow((s) => ({ wordWrap: s.wordWrap, toggleWordWrap: s.toggleWordWrap })));\n const monacoTheme = useMonacoTheme();\n\n // Measure container height — Monaco needs explicit pixel height on mobile\n const containerRef = useRef<HTMLDivElement>(null);\n const diffEditorRef = useRef<import(\"monaco-editor\").editor.IStandaloneDiffEditor | null>(null);\n const [editorReady, setEditorReady] = useState(false);\n const [containerHeight, setContainerHeight] = useState<number | undefined>();\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n const ro = new ResizeObserver(([entry]) => {\n if (entry) setContainerHeight(Math.floor(entry.contentRect.height));\n });\n ro.observe(el);\n return () => ro.disconnect();\n }, [loading, error]);\n\n useEffect(() => {\n if (isInline) return;\n if (!projectName) return;\n setLoading(true);\n setError(null);\n setFullFileDiff(null);\n setFileContents(null);\n setDiffText(null);\n\n if (file1 && file2) {\n const params = new URLSearchParams({ file1, file2 });\n api\n .get<{ original: string; modified: string }>(\n `${projectUrl(projectName)}/files/compare?${params}`,\n )\n .then((data) => { setFileContents(data); setLoading(false); })\n .catch((err) => { setError(err instanceof Error ? err.message : \"Failed to compare files\"); setLoading(false); });\n return;\n }\n\n // Single-file diff → fetch FULL file contents on both sides (VSCode-style).\n // Monaco DiffEditor computes the diff itself, giving full-file view instead\n // of just the changed hunks + 3 lines of context that `git diff` returns.\n if (filePath) {\n const params = new URLSearchParams({ file: filePath });\n if (ref1) params.set(\"ref\", ref1);\n api\n .get<{ original: string; modified: string }>(\n `${projectUrl(projectName)}/git/file-full-diff?${params}`,\n )\n .then((data) => { setFullFileDiff(data); setLoading(false); })\n .catch((err) => { setError(err instanceof Error ? err.message : \"Failed to load diff\"); setLoading(false); });\n return;\n }\n\n let url: string;\n if (ref1 || ref2) {\n const params = new URLSearchParams();\n if (ref1) params.set(\"ref1\", ref1);\n if (ref2) params.set(\"ref2\", ref2);\n url = `${projectUrl(projectName)}/git/diff?${params}`;\n } else {\n url = `${projectUrl(projectName)}/git/diff`;\n }\n\n api\n .get<{ diff: string }>(url)\n .then((data) => { setDiffText(data.diff); setLoading(false); })\n .catch((err) => { setError(err instanceof Error ? err.message : \"Failed to load diff\"); setLoading(false); });\n }, [filePath, projectName, ref1, ref2, file1, file2, isInline]);\n\n const { original, modified } = useMemo(() => {\n if (isInline) return { original: inlineOriginal ?? \"\", modified: inlineModified ?? \"\" };\n if (isFileCompare && fileContents) return fileContents;\n if (fullFileDiff) return fullFileDiff;\n if (!diffText) return { original: \"\", modified: \"\" };\n return parseDiff(diffText);\n }, [diffText, isInline, inlineOriginal, inlineModified, isFileCompare, fileContents, fullFileDiff]);\n\n const language = useMemo(() => {\n const langFile = filePath ?? file2 ?? file1;\n return langFile ? getMonacoLanguage(langFile) : \"plaintext\";\n }, [filePath, file1, file2]);\n\n // Force inline on mobile (<768px) since side-by-side is too narrow\n const isMobile = typeof window !== \"undefined\" && window.innerWidth < 768;\n const renderSideBySide = !isMobile;\n\n // Sync word wrap on both sub-editors.\n // Monaco DiffEditor has a bug: during init when container width is 0,\n // useInlineViewWhenSpaceIsLimited briefly triggers inline mode which sets\n // wordWrapOverride2='off' on the original editor. When side-by-side resumes,\n // wordWrapOverride2 is never cleared, permanently blocking word wrap on the\n // left side. We disable that option and also force wordWrapOverride2 to clear it.\n useEffect(() => {\n const editor = diffEditorRef.current;\n if (!editor) return;\n const val: \"on\" | \"off\" = isMobile ? \"on\" : wordWrap ? \"on\" : \"off\";\n editor.updateOptions({ diffWordWrap: val });\n editor.getOriginalEditor().updateOptions({ wordWrapOverride2: val } as any);\n editor.getModifiedEditor().updateOptions({ wordWrapOverride2: val } as any);\n }, [wordWrap, isMobile, editorReady]);\n\n if (!projectName && !isInline) {\n return (\n <div className=\"flex items-center justify-center h-full text-muted-foreground text-sm\">\n No project selected.\n </div>\n );\n }\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center h-full gap-2 text-muted-foreground\">\n <Loader2 className=\"size-5 animate-spin\" />\n <span className=\"text-sm\">Loading diff...</span>\n </div>\n );\n }\n\n if (error) {\n return (\n <div className=\"flex items-center justify-center h-full text-destructive text-sm\">{error}</div>\n );\n }\n\n // Catch diffs with metadata-only changes (mode, rename) where parseDiff returns empty\n if (!isInline && !isFileCompare && !fullFileDiff && !original && !modified) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full gap-2 text-muted-foreground\">\n <FileCode className=\"size-8\" />\n <p className=\"text-sm\">No content changes</p>\n {filePath && <p className=\"text-xs font-mono\">{filePath}</p>}\n </div>\n );\n }\n\n return (\n <div className=\"flex flex-col h-full\">\n {/* Toolbar */}\n {!isMobile && (\n <div className=\"flex items-center justify-end gap-0.5 px-2 py-0.5 border-b border-border shrink-0\">\n <button type=\"button\" onClick={toggleWordWrap} title=\"Toggle word wrap\"\n className={`p-1 rounded hover:bg-muted transition-colors ${wordWrap ? \"bg-muted text-foreground\" : \"\"}`}\n >\n <WrapText className=\"size-3.5\" />\n </button>\n </div>\n )}\n {/* Monaco DiffEditor */}\n <div ref={containerRef} className=\"flex-1 overflow-hidden\">\n {containerHeight && containerHeight > 0 ? (\n <DiffEditor\n height={containerHeight}\n language={language}\n original={original}\n modified={modified}\n theme={monacoTheme}\n onMount={(editor) => {\n diffEditorRef.current = editor;\n setEditorReady(true);\n }}\n options={{\n fontSize: isMobile ? 11 : 13,\n fontFamily: \"Menlo, Monaco, Consolas, monospace\",\n diffWordWrap: isMobile ? \"on\" : wordWrap ? \"on\" : \"off\",\n renderSideBySide,\n useInlineViewWhenSpaceIsLimited: false,\n readOnly: true,\n automaticLayout: true,\n scrollBeyondLastLine: false,\n }}\n loading={<Loader2 className=\"size-5 animate-spin text-muted-foreground\" />}\n />\n ) : (\n <div className=\"flex items-center justify-center h-full\">\n <Loader2 className=\"size-5 animate-spin text-muted-foreground\" />\n </div>\n )}\n </div>\n </div>\n );\n}\n\nfunction parseDiff(diff: string): { original: string; modified: string } {\n const lines = diff.split(\"\\n\");\n const originalLines: string[] = [];\n const modifiedLines: string[] = [];\n let inHunk = false;\n\n for (const line of lines) {\n if (\n line.startsWith(\"diff --git\") || line.startsWith(\"diff --no-index\") ||\n line.startsWith(\"index \") || line.startsWith(\"new file\") ||\n line.startsWith(\"deleted file\") || line.startsWith(\"old mode\") ||\n line.startsWith(\"new mode\") || line.startsWith(\"---\") ||\n line.startsWith(\"+++\") || line.startsWith(\"Binary files\") ||\n line.startsWith(\"\\\\ No newline\")\n ) continue;\n\n if (line.startsWith(\"@@\")) { inHunk = true; continue; }\n if (!inHunk) continue;\n\n if (line.startsWith(\"-\")) {\n originalLines.push(line.slice(1));\n } else if (line.startsWith(\"+\")) {\n modifiedLines.push(line.slice(1));\n } else {\n const content = line.startsWith(\" \") ? line.slice(1) : line;\n originalLines.push(content);\n modifiedLines.push(content);\n }\n }\n\n return { original: originalLines.join(\"\\n\"), modified: modifiedLines.join(\"\\n\") };\n}\n"],"mappings":"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"}
|