@hienlh/ppm 0.13.49 → 0.13.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/http-api.md +1 -1
- package/dist/web/assets/{audio-preview-CQ-l5P8I.js → audio-preview-CILFIsuu.js} +2 -2
- package/dist/web/assets/{audio-preview-CQ-l5P8I.js.map → audio-preview-CILFIsuu.js.map} +1 -1
- package/dist/web/assets/{chat-tab-DbuBr2ax.js → chat-tab-DBYwH_Aa.js} +4 -4
- package/dist/web/assets/{chat-tab-DbuBr2ax.js.map → chat-tab-DBYwH_Aa.js.map} +1 -1
- package/dist/web/assets/{code-editor-DEa0t62y.js → code-editor-MXnkYRLp.js} +3 -3
- package/dist/web/assets/{code-editor-DEa0t62y.js.map → code-editor-MXnkYRLp.js.map} +1 -1
- package/dist/web/assets/{conflict-editor-D5H9urYy.js → conflict-editor-C6wH5wV6.js} +2 -2
- package/dist/web/assets/{conflict-editor-D5H9urYy.js.map → conflict-editor-C6wH5wV6.js.map} +1 -1
- package/dist/web/assets/{database-viewer-CW60ytCl.js → database-viewer-BjUruZLv.js} +2 -2
- package/dist/web/assets/{database-viewer-CW60ytCl.js.map → database-viewer-BjUruZLv.js.map} +1 -1
- package/dist/web/assets/{diff-viewer-BfatMgWw.js → diff-viewer-B_nU7bQi.js} +2 -2
- package/dist/web/assets/{diff-viewer-BfatMgWw.js.map → diff-viewer-B_nU7bQi.js.map} +1 -1
- package/dist/web/assets/{extension-webview-DKSDoW_g.js → extension-webview-B56ZfvoD.js} +2 -2
- package/dist/web/assets/{extension-webview-DKSDoW_g.js.map → extension-webview-B56ZfvoD.js.map} +1 -1
- package/dist/web/assets/{glide-data-grid-Bx48618B.js → glide-data-grid-D-qQqqp7.js} +2 -2
- package/dist/web/assets/{glide-data-grid-Bx48618B.js.map → glide-data-grid-D-qQqqp7.js.map} +1 -1
- package/dist/web/assets/{image-preview-ClY2xl1B.js → image-preview-Dc6AiqYX.js} +2 -2
- package/dist/web/assets/{image-preview-ClY2xl1B.js.map → image-preview-Dc6AiqYX.js.map} +1 -1
- package/dist/web/assets/{index-DkQ6jVSH.js → index-8_rE2Q1-.js} +5 -5
- package/dist/web/assets/{index-DkQ6jVSH.js.map → index-8_rE2Q1-.js.map} +1 -1
- package/dist/web/assets/keybindings-store-COJD5O6M.js +1 -0
- package/dist/web/assets/{markdown-renderer-BmMmo0F-.js → markdown-renderer-CNQ8I0Dk.js} +2 -2
- package/dist/web/assets/{markdown-renderer-BmMmo0F-.js.map → markdown-renderer-CNQ8I0Dk.js.map} +1 -1
- package/dist/web/assets/notification-store-BiZaLXop.js +1 -0
- package/dist/web/assets/{panel-store-Dy8-7E_g.js → panel-store-C8wwxBpn.js} +2 -2
- package/dist/web/assets/{panel-store-Dy8-7E_g.js.map → panel-store-C8wwxBpn.js.map} +1 -1
- package/dist/web/assets/{pdf-preview-YylEP_Su.js → pdf-preview-zs9QdgDp.js} +2 -2
- package/dist/web/assets/{pdf-preview-YylEP_Su.js.map → pdf-preview-zs9QdgDp.js.map} +1 -1
- package/dist/web/assets/{port-forwarding-tab-COdo70kU.js → port-forwarding-tab-sArYx1nt.js} +2 -2
- package/dist/web/assets/{port-forwarding-tab-COdo70kU.js.map → port-forwarding-tab-sArYx1nt.js.map} +1 -1
- package/dist/web/assets/{postgres-viewer-3y9VshZZ.js → postgres-viewer-khk7N7cd.js} +2 -2
- package/dist/web/assets/{postgres-viewer-3y9VshZZ.js.map → postgres-viewer-khk7N7cd.js.map} +1 -1
- package/dist/web/assets/{settings-tab-sYavdJk-.js → settings-tab-CGWhVzQm.js} +1 -1
- package/dist/web/assets/{sql-query-editor-DlBYx1Ye.js → sql-query-editor-B5Ndypxp.js} +2 -2
- package/dist/web/assets/{sql-query-editor-DlBYx1Ye.js.map → sql-query-editor-B5Ndypxp.js.map} +1 -1
- package/dist/web/assets/{sqlite-viewer-Bj8oPYho.js → sqlite-viewer-BkpONSGa.js} +2 -2
- package/dist/web/assets/{sqlite-viewer-Bj8oPYho.js.map → sqlite-viewer-BkpONSGa.js.map} +1 -1
- package/dist/web/assets/{tab-store-Dtg1_qL0.js → tab-store-CNas5Ny8.js} +2 -2
- package/dist/web/assets/{tab-store-Dtg1_qL0.js.map → tab-store-CNas5Ny8.js.map} +1 -1
- package/dist/web/assets/{terminal-tab-B7ECmf95.js → terminal-tab-BgMCsdeN.js} +2 -2
- package/dist/web/assets/{terminal-tab-B7ECmf95.js.map → terminal-tab-BgMCsdeN.js.map} +1 -1
- package/dist/web/assets/{video-preview-CT78iZBo.js → video-preview-w8ZAy8av.js} +2 -2
- package/dist/web/assets/{video-preview-CT78iZBo.js.map → video-preview-w8ZAy8av.js.map} +1 -1
- package/dist/web/index.html +3 -3
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
- package/src/web/stores/panel-store.ts +5 -0
- package/dist/web/assets/keybindings-store-DrAeg6Gw.js +0 -1
- package/dist/web/assets/notification-store-Bukl8bKo.js +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/markdown-renderer-
|
|
2
|
-
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"./createLucideIcon-BjHrJDVb.js";import"./scroll-area-BDi_FNzr.js";import{t as i}from"./database-DOWH9-Vv.js";import{t as a}from"./chevron-right-DnHIvvcy.js";import{a as o,l as s,n as c,o as l,r as u,s as d,t as f}from"./input-_LFQwhzd.js";import{t as p}from"./code-DGBecc50.js";import{a as m,i as h,t as g}from"./x-BPReZWnP.js";import{t as _}from"./file-exclamation-point-BwzaQ50n.js";import{i as ee,r as te,t as v}from"./glide-data-grid-Bx48618B.js";import{t as y}from"./table-BzjWcs87.js";import{t as b}from"./text-wrap-DJz9Bgpa.js";import{i as ne,t as x}from"./api-client-DIhJ5qVW.js";import{n as re}from"./settings-store-8FpQDjEA.js";import{K as S}from"./vendor-mermaid-D2KKkqNs.js";import{t as C}from"./utils-CQux7CsO.js";import{t as ie}from"./panel-store-Dy8-7E_g.js";import{i as w,n as T}from"./file-store-DOxcU_7s.js";import{t as ae}from"./tab-store-Dtg1_qL0.js";import{$ as E,J as oe,L as D,Q as O,W as se,X as ce,Y as k,Z as A,a as le,c as j,d as M,et as N,f as ue,g as P,h as de,l as fe,m as pe,o as me,p as F,q as I,rt as he,tt as ge,u as L}from"./index-DkQ6jVSH.js";import"./data-grid-types-D2cHE8hx.js";import{n as _e,t as ve}from"./use-monaco-theme-DEI-tJAh.js";var ye=r(`redo-2`,[[`path`,{d:`m15 14 5-5-5-5`,key:`12vg1m`}],[`path`,{d:`M20 9H9.5A5.5 5.5 0 0 0 4 14.5A5.5 5.5 0 0 0 9.5 20H13`,key:`6uklza`}]]),R=e(n(),1),z=t(),B={ts:E,tsx:E,js:E,jsx:E,py:E,rs:E,go:E,html:E,css:E,scss:E,json:N,md:O,txt:O,yaml:A,yml:A};function be(e,t){return t?k:B[e.split(`.`).pop()?.toLowerCase()??``]??ce}function xe(e,t){let n=[],r=e,i=``;for(let e=0;e<t.length;e++){let a=t[e],o=t.slice(0,e+1).join(`/`),s=r.find(e=>e.name===a);if(n.push({name:a,fullPath:o,node:s??null,siblings:r,parentPath:i}),s?.children)i=s.path,r=s.children;else{for(let r=e+1;r<t.length;r++)n.push({name:t[r],fullPath:t.slice(0,r+1).join(`/`),node:null,siblings:[],parentPath:t.slice(0,r).join(`/`)});break}}return n}function V(e){return[...e].sort((e,t)=>e.type===t.type?e.name.localeCompare(t.name):e.type===`directory`?-1:1)}function Se({filePath:e,projectName:t,tabId:n,className:r}){let i=T(e=>e.tree),{updateTab:o,openTab:s}=ae(de(e=>({updateTab:e.updateTab,openTab:e.openTab}))),c=w(e=>e.projects.find(e=>e.name===t)?.path??``),l=(0,R.useRef)(null),{prefixParts:u,relativePath:d}=(0,R.useMemo)(()=>{let t=e.startsWith(`/`)?e.slice(1):e,n=c.startsWith(`/`)?c.slice(1):c;if(n&&t.startsWith(n+`/`)){let e=t.slice(n.length+1);return{prefixParts:n.split(`/`),relativePath:e}}return{prefixParts:[],relativePath:t}},[e,c]),f=(0,R.useMemo)(()=>xe(i,d.split(`/`).filter(Boolean)),[i,d]);(0,R.useEffect)(()=>{l.current&&(l.current.scrollLeft=l.current.scrollWidth)},[f]);function p(e,r){let i=C(e);r.metaKey||r.ctrlKey?s({type:`editor`,title:i,metadata:{filePath:e,projectName:t},projectId:t,closable:!0}):o(n,{title:i,metadata:{filePath:e,projectName:t}})}return(0,z.jsxs)(`div`,{ref:l,className:r,children:[u.map((e,t)=>(0,z.jsxs)(`div`,{className:`flex items-center shrink-0`,children:[t>0&&(0,z.jsx)(a,{className:`size-3 text-muted-foreground shrink-0 mx-0.5`}),(0,z.jsx)(`span`,{className:`text-xs text-muted-foreground px-1 py-0.5`,children:e})]},`prefix-${t}`)),f.map((e,n)=>(0,z.jsxs)(`div`,{className:`flex items-center shrink-0`,children:[(n>0||u.length>0)&&(0,z.jsx)(a,{className:`size-3 text-muted-foreground shrink-0 mx-0.5`}),(0,z.jsx)(Ce,{segment:e,isLast:n===f.length-1,projectName:t,onFileClick:p})]},e.fullPath))]})}function Ce({segment:e,isLast:t,projectName:n,onFileClick:r}){let i=T(e=>e.loadChildren),a=T(e=>e.loadedPaths),o=(0,R.useMemo)(()=>V(e.siblings),[e.siblings]),s=a.has(e.parentPath);function c(t){t&&!s&&i(n,e.parentPath)}return(0,z.jsxs)(j,{onOpenChange:c,children:[(0,z.jsx)(pe,{asChild:!0,children:(0,z.jsx)(`button`,{type:`button`,className:`text-xs px-1 py-0.5 rounded hover:bg-muted transition-colors truncate max-w-[120px] ${t?`text-foreground font-medium`:`text-muted-foreground`}`,children:e.name})}),(0,z.jsx)(fe,{align:`start`,className:`max-h-[300px] p-1`,children:o.length===0?(0,z.jsx)(L,{disabled:!0,className:`text-xs text-muted-foreground`,children:`Loading…`}):o.map(t=>(0,z.jsx)(H,{node:t,projectName:n,activePath:e.fullPath,onFileClick:r},t.path))})]})}function H({node:e,projectName:t,activePath:n,onFileClick:r}){let i=be(e.name,e.type===`directory`),a=e.path===n,o=T(e=>e.loadChildren),s=T(e=>e.loadedPaths);if(e.type===`directory`){let c=e.children??[],l=s.has(e.path);function u(n){n&&!l&&o(t,e.path)}return(0,z.jsxs)(M,{onOpenChange:u,children:[(0,z.jsxs)(F,{className:`text-xs gap-1.5 ${a?`bg-muted`:``}`,children:[(0,z.jsx)(i,{className:`size-3.5 shrink-0 text-muted-foreground`}),(0,z.jsx)(`span`,{className:`truncate`,children:e.name})]}),(0,z.jsx)(ue,{className:`max-h-[300px] overflow-y-auto p-1`,children:c.length===0?(0,z.jsx)(L,{disabled:!0,className:`text-xs text-muted-foreground`,children:`Loading…`}):V(c).map(e=>(0,z.jsx)(H,{node:e,projectName:t,activePath:n,onFileClick:r},e.path))})]})}return(0,z.jsxs)(L,{className:`text-xs gap-1.5 cursor-pointer ${a?`bg-muted`:``}`,onSelect:e=>{},onClick:t=>{r(e.path,t)},children:[(0,z.jsx)(i,{className:`size-3.5 shrink-0 text-muted-foreground`}),(0,z.jsx)(`span`,{className:`truncate`,children:e.name})]})}function U({active:e,onClick:t,icon:n,label:r}){return(0,z.jsxs)(`button`,{type:`button`,onClick:t,className:`flex items-center gap-1 px-2 py-1 rounded text-xs transition-colors ${e?`bg-muted text-foreground`:`text-muted-foreground hover:text-foreground`}`,children:[(0,z.jsx)(n,{className:`size-3`}),(0,z.jsx)(`span`,{className:`hidden sm:inline`,children:r})]})}function we({ext:e,mdMode:t,onMdModeChange:n,csvMode:r,onCsvModeChange:i,wordWrap:a,onToggleWordWrap:o,filePath:s,projectName:c,className:l}){return(0,z.jsxs)(`div`,{className:l,children:[(e===`md`||e===`mdx`)&&n&&(0,z.jsxs)(z.Fragment,{children:[(0,z.jsx)(U,{active:t===`edit`,onClick:()=>n(`edit`),icon:p,label:`Edit`}),(0,z.jsx)(U,{active:t===`preview`,onClick:()=>n(`preview`),icon:h,label:`Preview`})]}),e===`csv`&&i&&(0,z.jsxs)(z.Fragment,{children:[(0,z.jsx)(U,{active:r===`table`,onClick:()=>i(`table`),icon:y,label:`Table`}),(0,z.jsx)(U,{active:r===`raw`,onClick:()=>i(`raw`),icon:p,label:`Raw`})]}),(0,z.jsx)(U,{active:a,onClick:o,icon:b,label:`Wrap`}),s&&c&&(0,z.jsx)(U,{active:!1,onClick:()=>P(c,s),icon:m,label:`Download`})]})}function Te({open:e,defaultName:t,content:n,onSave:r,onCancel:i}){let[a,p]=(0,R.useState)(t),[m,h]=(0,R.useState)(!1),[g,_]=(0,R.useState)(``),ee=w(e=>e.activeProject),te=(0,R.useCallback)(()=>{let e=a.trim();if(!e){_(`Filename cannot be empty`);return}if(/[/\\]/.test(e)){_(`Filename cannot contain / or \\`);return}_(``),h(!0)},[a]),v=(0,R.useCallback)(e=>{let t=e.includes(`\\`)?`\\`:`/`;r(e.endsWith(t)?`${e}${a.trim()}`:`${e}${t}${a.trim()}`,n)},[a,n,r]);return m?(0,z.jsx)(me,{open:!0,mode:`folder`,root:ee?.path,title:`Save "${a.trim()}" to...`,onSelect:v,onCancel:()=>h(!1)}):(0,z.jsx)(c,{open:e,onOpenChange:e=>{e||i()},children:(0,z.jsxs)(u,{className:`sm:max-w-md`,children:[(0,z.jsx)(l,{children:(0,z.jsx)(d,{children:`Save As`})}),(0,z.jsxs)(`div`,{className:`flex flex-col gap-2 py-2`,children:[(0,z.jsx)(`label`,{className:`text-sm text-muted-foreground`,children:`Filename`}),(0,z.jsx)(f,{value:a,onChange:e=>{p(e.target.value),_(``)},onKeyDown:e=>{e.key===`Enter`&&te()},placeholder:`e.g. my-file.ts`,autoFocus:!0}),g&&(0,z.jsx)(`p`,{className:`text-xs text-destructive`,children:g})]}),(0,z.jsxs)(o,{children:[(0,z.jsx)(s,{variant:`outline`,onClick:i,children:`Cancel`}),(0,z.jsx)(s,{onClick:te,children:`Choose Folder...`})]})]})})}var W=typeof window<`u`&&window.isSecureContext,Ee=[`(`,`)`,`{`,`}`,`[`,`]`,`<`,`>`,`;`,`:`,`=`,`"`,`'`,"`",`/`,`\\`,`_`,`#`],G=`px-2 py-1.5 rounded text-xs min-w-[36px] min-h-[32px] bg-surface-elevated text-text-primary active:bg-primary active:text-primary-foreground transition-colors select-none`,K=`px-3 py-1.5 rounded text-xs font-mono min-w-[36px] min-h-[32px] bg-surface-elevated text-text-primary active:bg-primary active:text-primary-foreground transition-colors select-none`,De=`w-px h-5 bg-border mx-0.5 shrink-0`;function Oe({editorRef:e,readOnly:t}){let n=(0,R.useCallback)(()=>e.current,[e]),r=(0,R.useCallback)(e=>{let t=n();if(!t)return;t.focus();let r=t.getSelection();r&&t.executeEdits(`mobile-toolbar`,[{range:r,text:e}])},[n]),[i,a]=(0,R.useState)(!1),o=(0,R.useRef)(null),s=(0,R.useCallback)(async()=>{try{let e=await navigator.clipboard.readText();e&&r(e)}catch{}},[r]),c=(0,R.useCallback)(()=>{a(!0),requestAnimationFrame(()=>o.current?.focus())},[]),l=(0,R.useCallback)(e=>{e.preventDefault();let t=e.clipboardData.getData(`text/plain`);t&&(a(!1),r(t))},[r]),u=(0,R.useCallback)(()=>{let e=n();e&&(e.focus(),e.trigger(`mobile-toolbar`,`undo`,null))},[n]),d=(0,R.useCallback)(()=>{let e=n();e&&(e.focus(),e.trigger(`mobile-toolbar`,`redo`,null))},[n]),f=(0,R.useCallback)(()=>{let e=n();e&&(e.focus(),e.trigger(`mobile-toolbar`,`tab`,null))},[n]);return t?null:(0,z.jsxs)(`div`,{className:`shrink-0 border-t border-border bg-surface`,children:[!W&&i&&(0,z.jsxs)(`div`,{className:`flex items-center gap-2 px-2 py-1.5 border-b border-border bg-muted/50`,children:[(0,z.jsx)(`textarea`,{ref:o,onPaste:l,placeholder:`Long-press here → Paste`,className:`flex-1 h-8 rounded border border-border bg-background text-foreground text-xs px-2 py-1.5 resize-none focus:outline-none focus:ring-1 focus:ring-primary`}),(0,z.jsx)(`button`,{type:`button`,onClick:()=>a(!1),className:`p-1.5 rounded text-muted-foreground active:bg-muted transition-colors`,children:(0,z.jsx)(g,{size:14})})]}),(0,z.jsxs)(`div`,{className:`flex items-center gap-1 px-2 py-1.5 overflow-x-auto`,children:[(0,z.jsx)(`button`,{type:`button`,onClick:W?s:c,className:G,title:`Paste`,children:(0,z.jsx)(he,{size:14})}),(0,z.jsx)(`button`,{type:`button`,onClick:u,className:G,title:`Undo`,children:(0,z.jsx)(D,{size:14})}),(0,z.jsx)(`button`,{type:`button`,onClick:d,className:G,title:`Redo`,children:(0,z.jsx)(ye,{size:14})}),(0,z.jsx)(`div`,{className:De}),(0,z.jsx)(`button`,{type:`button`,onClick:f,className:K,children:`Tab`}),(0,z.jsx)(`div`,{className:De}),Ee.map(e=>(0,z.jsx)(`button`,{type:`button`,onClick:()=>r(e),className:K,children:e},e))]})]})}var ke=(0,R.lazy)(()=>S(()=>import(`./markdown-renderer-BmMmo0F-.js`).then(e=>({default:e.MarkdownRenderer})),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24]))),Ae=(0,R.lazy)(()=>S(()=>import(`./csv-preview-B3Dyhgho.js`).then(e=>({default:e.CsvPreview})),__vite__mapDeps([25,1,4,5,26,27,8,28]))),je=(0,R.lazy)(()=>S(()=>import(`./image-preview-ClY2xl1B.js`).then(e=>({default:e.ImagePreview})),__vite__mapDeps([29,2,1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,30,31]))),Me=(0,R.lazy)(()=>S(()=>import(`./pdf-preview-YylEP_Su.js`).then(e=>({default:e.PdfPreview})),__vite__mapDeps([32,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,30,31]))),Ne=(0,R.lazy)(()=>S(()=>import(`./video-preview-CT78iZBo.js`).then(e=>({default:e.VideoPreview})),__vite__mapDeps([33,2,1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,30,31]))),Pe=(0,R.lazy)(()=>S(()=>import(`./audio-preview-CQ-l5P8I.js`).then(e=>({default:e.AudioPreview})),__vite__mapDeps([34,2,1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,30,31]))),Fe=new Set([`png`,`jpg`,`jpeg`,`gif`,`webp`,`svg`,`ico`]),Ie=new Set([`mp4`,`webm`,`mov`,`ogg`,`avi`,`mkv`]),Le=new Set([`mp3`,`wav`,`flac`,`aac`,`m4a`,`wma`]),Re=new Set([`db`,`sqlite`,`sqlite3`]);function ze(e){return e.split(`.`).pop()?.toLowerCase()??``}function Be(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`,sql:`sql`}[ze(e)]??`plaintext`}var q=(0,R.memo)(function({metadata:e,tabId:t}){let n=e?.filePath,r=e?.projectName,a=e?.inlineContent,o=e?.inlineLanguage,[s,c]=(0,R.useState)(a??null),[l,u]=(0,R.useState)(`utf-8`),[d,f]=(0,R.useState)(!0),[p,m]=(0,R.useState)(null),[h,g]=(0,R.useState)(!1),v=(0,R.useRef)(null),y=(0,R.useRef)(``),b=(0,R.useRef)(null),{tabs:S,updateTab:w}=ae(de(e=>({tabs:e.tabs,updateTab:e.updateTab}))),{wordWrap:T,toggleWordWrap:E}=re(de(e=>({wordWrap:e.wordWrap,toggleWordWrap:e.toggleWordWrap}))),oe=ve(),D=e?.isUntitled===!0,O=e?.unsavedContent,[ce,k]=(0,R.useState)(!1),A=S.find(e=>e.id===t),j=n?ze(n):``,M=Fe.has(j),N=j===`pdf`,ue=Ie.has(j),P=Le.has(j),fe=Re.has(j),pe=j===`md`||j===`mdx`,me=j===`csv`,F=j===`sql`,[he,ge]=(0,R.useState)(`preview`),[L,ye]=(0,R.useState)(`table`),{connections:B,cachedTables:be,refreshTables:xe}=le(),[V,Ce]=(0,R.useState)(()=>{if(!F||!n)return null;let e=localStorage.getItem(`ppm:sql-conn:${n}`);return e?Number(e):null}),H=(0,R.useRef)(null),U=(0,R.useRef)(null),W=(0,R.useMemo)(()=>B.find(e=>e.id===V)??null,[B,V]),Ee=a!=null&&(o===`json`||o===`xml`),[G,K]=(0,R.useState)(!1),De=(0,R.useCallback)(()=>{if(a)if(G)c(a),K(!1);else{let e=a.trimStart();if(o===`json`)try{c(JSON.stringify(JSON.parse(e),null,2)),K(!0)}catch{}else if(o===`xml`){let t=0;c(e.replace(/(>)(<)(\/*)/g,`$1
|
|
1
|
+
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/markdown-renderer-CNQ8I0Dk.js","assets/rolldown-runtime-FhOqtrmT.js","assets/index-8_rE2Q1-.js","assets/vendor-mermaid-D2KKkqNs.js","assets/vendor-ui-UXCWAcmi.js","assets/vendor-markdown-0Mxgxy0L.js","assets/input-_LFQwhzd.js","assets/utils-CQux7CsO.js","assets/createLucideIcon-BjHrJDVb.js","assets/x-BPReZWnP.js","assets/settings-store-8FpQDjEA.js","assets/react-DMIOAtcX.js","assets/api-client-DIhJ5qVW.js","assets/scroll-area-BDi_FNzr.js","assets/ai-settings-section-AuV6Lzz2.js","assets/globe-B4Ilypbs.js","assets/refresh-cw-BjrAbUJe.js","assets/api-settings-C3T95dWg.js","assets/database-DOWH9-Vv.js","assets/chevron-right-DnHIvvcy.js","assets/search-tM8K5zWU.js","assets/file-store-DOxcU_7s.js","assets/panel-store-C8wwxBpn.js","assets/tab-store-CNas5Ny8.js","assets/index-CKKoR3gY.css","assets/csv-preview-B3Dyhgho.js","assets/lib-C2D8j3K3.js","assets/csv-parser-Dly5nqE1.js","assets/arrow-up-Rcw6_KKu.js","assets/image-preview-Dc6AiqYX.js","assets/file-exclamation-point-BwzaQ50n.js","assets/use-blob-url-DB4nNruT.js","assets/pdf-preview-zs9QdgDp.js","assets/video-preview-w8ZAy8av.js","assets/audio-preview-CILFIsuu.js"])))=>i.map(i=>d[i]);
|
|
2
|
+
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"./createLucideIcon-BjHrJDVb.js";import"./scroll-area-BDi_FNzr.js";import{t as i}from"./database-DOWH9-Vv.js";import{t as a}from"./chevron-right-DnHIvvcy.js";import{a as o,l as s,n as c,o as l,r as u,s as d,t as f}from"./input-_LFQwhzd.js";import{t as p}from"./code-DGBecc50.js";import{a as m,i as h,t as g}from"./x-BPReZWnP.js";import{t as _}from"./file-exclamation-point-BwzaQ50n.js";import{i as ee,r as te,t as v}from"./glide-data-grid-D-qQqqp7.js";import{t as y}from"./table-BzjWcs87.js";import{t as b}from"./text-wrap-DJz9Bgpa.js";import{i as ne,t as x}from"./api-client-DIhJ5qVW.js";import{n as re}from"./settings-store-8FpQDjEA.js";import{K as S}from"./vendor-mermaid-D2KKkqNs.js";import{t as C}from"./utils-CQux7CsO.js";import{t as ie}from"./panel-store-C8wwxBpn.js";import{i as w,n as T}from"./file-store-DOxcU_7s.js";import{t as ae}from"./tab-store-CNas5Ny8.js";import{$ as E,J as oe,L as D,Q as O,W as se,X as ce,Y as k,Z as A,a as le,c as j,d as M,et as N,f as ue,g as P,h as de,l as fe,m as pe,o as me,p as F,q as I,rt as he,tt as ge,u as L}from"./index-8_rE2Q1-.js";import"./data-grid-types-D2cHE8hx.js";import{n as _e,t as ve}from"./use-monaco-theme-DEI-tJAh.js";var ye=r(`redo-2`,[[`path`,{d:`m15 14 5-5-5-5`,key:`12vg1m`}],[`path`,{d:`M20 9H9.5A5.5 5.5 0 0 0 4 14.5A5.5 5.5 0 0 0 9.5 20H13`,key:`6uklza`}]]),R=e(n(),1),z=t(),B={ts:E,tsx:E,js:E,jsx:E,py:E,rs:E,go:E,html:E,css:E,scss:E,json:N,md:O,txt:O,yaml:A,yml:A};function be(e,t){return t?k:B[e.split(`.`).pop()?.toLowerCase()??``]??ce}function xe(e,t){let n=[],r=e,i=``;for(let e=0;e<t.length;e++){let a=t[e],o=t.slice(0,e+1).join(`/`),s=r.find(e=>e.name===a);if(n.push({name:a,fullPath:o,node:s??null,siblings:r,parentPath:i}),s?.children)i=s.path,r=s.children;else{for(let r=e+1;r<t.length;r++)n.push({name:t[r],fullPath:t.slice(0,r+1).join(`/`),node:null,siblings:[],parentPath:t.slice(0,r).join(`/`)});break}}return n}function V(e){return[...e].sort((e,t)=>e.type===t.type?e.name.localeCompare(t.name):e.type===`directory`?-1:1)}function Se({filePath:e,projectName:t,tabId:n,className:r}){let i=T(e=>e.tree),{updateTab:o,openTab:s}=ae(de(e=>({updateTab:e.updateTab,openTab:e.openTab}))),c=w(e=>e.projects.find(e=>e.name===t)?.path??``),l=(0,R.useRef)(null),{prefixParts:u,relativePath:d}=(0,R.useMemo)(()=>{let t=e.startsWith(`/`)?e.slice(1):e,n=c.startsWith(`/`)?c.slice(1):c;if(n&&t.startsWith(n+`/`)){let e=t.slice(n.length+1);return{prefixParts:n.split(`/`),relativePath:e}}return{prefixParts:[],relativePath:t}},[e,c]),f=(0,R.useMemo)(()=>xe(i,d.split(`/`).filter(Boolean)),[i,d]);(0,R.useEffect)(()=>{l.current&&(l.current.scrollLeft=l.current.scrollWidth)},[f]);function p(e,r){let i=C(e);r.metaKey||r.ctrlKey?s({type:`editor`,title:i,metadata:{filePath:e,projectName:t},projectId:t,closable:!0}):o(n,{title:i,metadata:{filePath:e,projectName:t}})}return(0,z.jsxs)(`div`,{ref:l,className:r,children:[u.map((e,t)=>(0,z.jsxs)(`div`,{className:`flex items-center shrink-0`,children:[t>0&&(0,z.jsx)(a,{className:`size-3 text-muted-foreground shrink-0 mx-0.5`}),(0,z.jsx)(`span`,{className:`text-xs text-muted-foreground px-1 py-0.5`,children:e})]},`prefix-${t}`)),f.map((e,n)=>(0,z.jsxs)(`div`,{className:`flex items-center shrink-0`,children:[(n>0||u.length>0)&&(0,z.jsx)(a,{className:`size-3 text-muted-foreground shrink-0 mx-0.5`}),(0,z.jsx)(Ce,{segment:e,isLast:n===f.length-1,projectName:t,onFileClick:p})]},e.fullPath))]})}function Ce({segment:e,isLast:t,projectName:n,onFileClick:r}){let i=T(e=>e.loadChildren),a=T(e=>e.loadedPaths),o=(0,R.useMemo)(()=>V(e.siblings),[e.siblings]),s=a.has(e.parentPath);function c(t){t&&!s&&i(n,e.parentPath)}return(0,z.jsxs)(j,{onOpenChange:c,children:[(0,z.jsx)(pe,{asChild:!0,children:(0,z.jsx)(`button`,{type:`button`,className:`text-xs px-1 py-0.5 rounded hover:bg-muted transition-colors truncate max-w-[120px] ${t?`text-foreground font-medium`:`text-muted-foreground`}`,children:e.name})}),(0,z.jsx)(fe,{align:`start`,className:`max-h-[300px] p-1`,children:o.length===0?(0,z.jsx)(L,{disabled:!0,className:`text-xs text-muted-foreground`,children:`Loading…`}):o.map(t=>(0,z.jsx)(H,{node:t,projectName:n,activePath:e.fullPath,onFileClick:r},t.path))})]})}function H({node:e,projectName:t,activePath:n,onFileClick:r}){let i=be(e.name,e.type===`directory`),a=e.path===n,o=T(e=>e.loadChildren),s=T(e=>e.loadedPaths);if(e.type===`directory`){let c=e.children??[],l=s.has(e.path);function u(n){n&&!l&&o(t,e.path)}return(0,z.jsxs)(M,{onOpenChange:u,children:[(0,z.jsxs)(F,{className:`text-xs gap-1.5 ${a?`bg-muted`:``}`,children:[(0,z.jsx)(i,{className:`size-3.5 shrink-0 text-muted-foreground`}),(0,z.jsx)(`span`,{className:`truncate`,children:e.name})]}),(0,z.jsx)(ue,{className:`max-h-[300px] overflow-y-auto p-1`,children:c.length===0?(0,z.jsx)(L,{disabled:!0,className:`text-xs text-muted-foreground`,children:`Loading…`}):V(c).map(e=>(0,z.jsx)(H,{node:e,projectName:t,activePath:n,onFileClick:r},e.path))})]})}return(0,z.jsxs)(L,{className:`text-xs gap-1.5 cursor-pointer ${a?`bg-muted`:``}`,onSelect:e=>{},onClick:t=>{r(e.path,t)},children:[(0,z.jsx)(i,{className:`size-3.5 shrink-0 text-muted-foreground`}),(0,z.jsx)(`span`,{className:`truncate`,children:e.name})]})}function U({active:e,onClick:t,icon:n,label:r}){return(0,z.jsxs)(`button`,{type:`button`,onClick:t,className:`flex items-center gap-1 px-2 py-1 rounded text-xs transition-colors ${e?`bg-muted text-foreground`:`text-muted-foreground hover:text-foreground`}`,children:[(0,z.jsx)(n,{className:`size-3`}),(0,z.jsx)(`span`,{className:`hidden sm:inline`,children:r})]})}function we({ext:e,mdMode:t,onMdModeChange:n,csvMode:r,onCsvModeChange:i,wordWrap:a,onToggleWordWrap:o,filePath:s,projectName:c,className:l}){return(0,z.jsxs)(`div`,{className:l,children:[(e===`md`||e===`mdx`)&&n&&(0,z.jsxs)(z.Fragment,{children:[(0,z.jsx)(U,{active:t===`edit`,onClick:()=>n(`edit`),icon:p,label:`Edit`}),(0,z.jsx)(U,{active:t===`preview`,onClick:()=>n(`preview`),icon:h,label:`Preview`})]}),e===`csv`&&i&&(0,z.jsxs)(z.Fragment,{children:[(0,z.jsx)(U,{active:r===`table`,onClick:()=>i(`table`),icon:y,label:`Table`}),(0,z.jsx)(U,{active:r===`raw`,onClick:()=>i(`raw`),icon:p,label:`Raw`})]}),(0,z.jsx)(U,{active:a,onClick:o,icon:b,label:`Wrap`}),s&&c&&(0,z.jsx)(U,{active:!1,onClick:()=>P(c,s),icon:m,label:`Download`})]})}function Te({open:e,defaultName:t,content:n,onSave:r,onCancel:i}){let[a,p]=(0,R.useState)(t),[m,h]=(0,R.useState)(!1),[g,_]=(0,R.useState)(``),ee=w(e=>e.activeProject),te=(0,R.useCallback)(()=>{let e=a.trim();if(!e){_(`Filename cannot be empty`);return}if(/[/\\]/.test(e)){_(`Filename cannot contain / or \\`);return}_(``),h(!0)},[a]),v=(0,R.useCallback)(e=>{let t=e.includes(`\\`)?`\\`:`/`;r(e.endsWith(t)?`${e}${a.trim()}`:`${e}${t}${a.trim()}`,n)},[a,n,r]);return m?(0,z.jsx)(me,{open:!0,mode:`folder`,root:ee?.path,title:`Save "${a.trim()}" to...`,onSelect:v,onCancel:()=>h(!1)}):(0,z.jsx)(c,{open:e,onOpenChange:e=>{e||i()},children:(0,z.jsxs)(u,{className:`sm:max-w-md`,children:[(0,z.jsx)(l,{children:(0,z.jsx)(d,{children:`Save As`})}),(0,z.jsxs)(`div`,{className:`flex flex-col gap-2 py-2`,children:[(0,z.jsx)(`label`,{className:`text-sm text-muted-foreground`,children:`Filename`}),(0,z.jsx)(f,{value:a,onChange:e=>{p(e.target.value),_(``)},onKeyDown:e=>{e.key===`Enter`&&te()},placeholder:`e.g. my-file.ts`,autoFocus:!0}),g&&(0,z.jsx)(`p`,{className:`text-xs text-destructive`,children:g})]}),(0,z.jsxs)(o,{children:[(0,z.jsx)(s,{variant:`outline`,onClick:i,children:`Cancel`}),(0,z.jsx)(s,{onClick:te,children:`Choose Folder...`})]})]})})}var W=typeof window<`u`&&window.isSecureContext,Ee=[`(`,`)`,`{`,`}`,`[`,`]`,`<`,`>`,`;`,`:`,`=`,`"`,`'`,"`",`/`,`\\`,`_`,`#`],G=`px-2 py-1.5 rounded text-xs min-w-[36px] min-h-[32px] bg-surface-elevated text-text-primary active:bg-primary active:text-primary-foreground transition-colors select-none`,K=`px-3 py-1.5 rounded text-xs font-mono min-w-[36px] min-h-[32px] bg-surface-elevated text-text-primary active:bg-primary active:text-primary-foreground transition-colors select-none`,De=`w-px h-5 bg-border mx-0.5 shrink-0`;function Oe({editorRef:e,readOnly:t}){let n=(0,R.useCallback)(()=>e.current,[e]),r=(0,R.useCallback)(e=>{let t=n();if(!t)return;t.focus();let r=t.getSelection();r&&t.executeEdits(`mobile-toolbar`,[{range:r,text:e}])},[n]),[i,a]=(0,R.useState)(!1),o=(0,R.useRef)(null),s=(0,R.useCallback)(async()=>{try{let e=await navigator.clipboard.readText();e&&r(e)}catch{}},[r]),c=(0,R.useCallback)(()=>{a(!0),requestAnimationFrame(()=>o.current?.focus())},[]),l=(0,R.useCallback)(e=>{e.preventDefault();let t=e.clipboardData.getData(`text/plain`);t&&(a(!1),r(t))},[r]),u=(0,R.useCallback)(()=>{let e=n();e&&(e.focus(),e.trigger(`mobile-toolbar`,`undo`,null))},[n]),d=(0,R.useCallback)(()=>{let e=n();e&&(e.focus(),e.trigger(`mobile-toolbar`,`redo`,null))},[n]),f=(0,R.useCallback)(()=>{let e=n();e&&(e.focus(),e.trigger(`mobile-toolbar`,`tab`,null))},[n]);return t?null:(0,z.jsxs)(`div`,{className:`shrink-0 border-t border-border bg-surface`,children:[!W&&i&&(0,z.jsxs)(`div`,{className:`flex items-center gap-2 px-2 py-1.5 border-b border-border bg-muted/50`,children:[(0,z.jsx)(`textarea`,{ref:o,onPaste:l,placeholder:`Long-press here → Paste`,className:`flex-1 h-8 rounded border border-border bg-background text-foreground text-xs px-2 py-1.5 resize-none focus:outline-none focus:ring-1 focus:ring-primary`}),(0,z.jsx)(`button`,{type:`button`,onClick:()=>a(!1),className:`p-1.5 rounded text-muted-foreground active:bg-muted transition-colors`,children:(0,z.jsx)(g,{size:14})})]}),(0,z.jsxs)(`div`,{className:`flex items-center gap-1 px-2 py-1.5 overflow-x-auto`,children:[(0,z.jsx)(`button`,{type:`button`,onClick:W?s:c,className:G,title:`Paste`,children:(0,z.jsx)(he,{size:14})}),(0,z.jsx)(`button`,{type:`button`,onClick:u,className:G,title:`Undo`,children:(0,z.jsx)(D,{size:14})}),(0,z.jsx)(`button`,{type:`button`,onClick:d,className:G,title:`Redo`,children:(0,z.jsx)(ye,{size:14})}),(0,z.jsx)(`div`,{className:De}),(0,z.jsx)(`button`,{type:`button`,onClick:f,className:K,children:`Tab`}),(0,z.jsx)(`div`,{className:De}),Ee.map(e=>(0,z.jsx)(`button`,{type:`button`,onClick:()=>r(e),className:K,children:e},e))]})]})}var ke=(0,R.lazy)(()=>S(()=>import(`./markdown-renderer-CNQ8I0Dk.js`).then(e=>({default:e.MarkdownRenderer})),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24]))),Ae=(0,R.lazy)(()=>S(()=>import(`./csv-preview-B3Dyhgho.js`).then(e=>({default:e.CsvPreview})),__vite__mapDeps([25,1,4,5,26,27,8,28]))),je=(0,R.lazy)(()=>S(()=>import(`./image-preview-Dc6AiqYX.js`).then(e=>({default:e.ImagePreview})),__vite__mapDeps([29,2,1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,30,31]))),Me=(0,R.lazy)(()=>S(()=>import(`./pdf-preview-zs9QdgDp.js`).then(e=>({default:e.PdfPreview})),__vite__mapDeps([32,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,30,31]))),Ne=(0,R.lazy)(()=>S(()=>import(`./video-preview-w8ZAy8av.js`).then(e=>({default:e.VideoPreview})),__vite__mapDeps([33,2,1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,30,31]))),Pe=(0,R.lazy)(()=>S(()=>import(`./audio-preview-CILFIsuu.js`).then(e=>({default:e.AudioPreview})),__vite__mapDeps([34,2,1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,30,31]))),Fe=new Set([`png`,`jpg`,`jpeg`,`gif`,`webp`,`svg`,`ico`]),Ie=new Set([`mp4`,`webm`,`mov`,`ogg`,`avi`,`mkv`]),Le=new Set([`mp3`,`wav`,`flac`,`aac`,`m4a`,`wma`]),Re=new Set([`db`,`sqlite`,`sqlite3`]);function ze(e){return e.split(`.`).pop()?.toLowerCase()??``}function Be(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`,sql:`sql`}[ze(e)]??`plaintext`}var q=(0,R.memo)(function({metadata:e,tabId:t}){let n=e?.filePath,r=e?.projectName,a=e?.inlineContent,o=e?.inlineLanguage,[s,c]=(0,R.useState)(a??null),[l,u]=(0,R.useState)(`utf-8`),[d,f]=(0,R.useState)(!0),[p,m]=(0,R.useState)(null),[h,g]=(0,R.useState)(!1),v=(0,R.useRef)(null),y=(0,R.useRef)(``),b=(0,R.useRef)(null),{tabs:S,updateTab:w}=ae(de(e=>({tabs:e.tabs,updateTab:e.updateTab}))),{wordWrap:T,toggleWordWrap:E}=re(de(e=>({wordWrap:e.wordWrap,toggleWordWrap:e.toggleWordWrap}))),oe=ve(),D=e?.isUntitled===!0,O=e?.unsavedContent,[ce,k]=(0,R.useState)(!1),A=S.find(e=>e.id===t),j=n?ze(n):``,M=Fe.has(j),N=j===`pdf`,ue=Ie.has(j),P=Le.has(j),fe=Re.has(j),pe=j===`md`||j===`mdx`,me=j===`csv`,F=j===`sql`,[he,ge]=(0,R.useState)(`preview`),[L,ye]=(0,R.useState)(`table`),{connections:B,cachedTables:be,refreshTables:xe}=le(),[V,Ce]=(0,R.useState)(()=>{if(!F||!n)return null;let e=localStorage.getItem(`ppm:sql-conn:${n}`);return e?Number(e):null}),H=(0,R.useRef)(null),U=(0,R.useRef)(null),W=(0,R.useMemo)(()=>B.find(e=>e.id===V)??null,[B,V]),Ee=a!=null&&(o===`json`||o===`xml`),[G,K]=(0,R.useState)(!1),De=(0,R.useCallback)(()=>{if(a)if(G)c(a),K(!1);else{let e=a.trimStart();if(o===`json`)try{c(JSON.stringify(JSON.parse(e),null,2)),K(!0)}catch{}else if(o===`xml`){let t=0;c(e.replace(/(>)(<)(\/*)/g,`$1
|
|
3
3
|
$2$3`).split(`
|
|
4
4
|
`).map(e=>{let n=e.trim();n.startsWith(`</`)&&(t=Math.max(0,t-1));let r=` `.repeat(t)+n;return n.startsWith(`<`)&&!n.startsWith(`</`)&&!n.endsWith(`/>`)&&!n.includes(`</`)&&t++,r}).join(`
|
|
5
5
|
`)),K(!0)}}},[a,o,G]),ke=(0,R.useCallback)(e=>{Ce(e),n&&localStorage.setItem(`ppm:sql-conn:${n}`,String(e)),xe(e).catch(()=>{})},[n,xe]),q=(0,R.useMemo)(()=>{if(!F||!V)return;let e=(be.get(V)??[]).map(e=>({name:e.tableName,schema:e.schemaName}));if(e.length!==0)return{tables:e,getColumns:async(e,t)=>x.get(`/api/db/connections/${V}/schema?table=${encodeURIComponent(e)}${t?`&schema=${encodeURIComponent(t)}`:``}`)}},[F,V,be]);(0,R.useEffect)(()=>{if(!(!H.current||!q))return U.current?.dispose(),te(),U.current=H.current.languages.registerCompletionItemProvider(`sql`,ee(H.current,q)),()=>{U.current?.dispose()}},[q]);let J=ae(e=>e.openTab),[Ue,We]=(0,R.useState)(null),[Ge,Ke]=(0,R.useState)(null),[qe,Je]=(0,R.useState)(!1),[Ye,Xe]=(0,R.useState)(``),X=(0,R.useCallback)(async e=>{if(W){Je(!0),Ke(null),Xe(e);try{We(await x.post(`/api/db/connections/${W.id}/query`,{sql:e}))}catch(e){Ke(e.message),We(null)}finally{Je(!1)}}},[W]),Ze=(0,R.useCallback)(()=>{!W||!Ye||J({type:`database`,title:`${W.name} · Query`,projectId:null,closable:!0,metadata:{connectionId:W.id,connectionName:W.name,dbType:W.type,initialSql:Ye}})},[W,J,Ye]),Qe=(0,R.useCallback)(()=>{if(!b.current||!W)return;let e=b.current,t=e.getSelection();X(t&&!t.isEmpty()?e.getModel()?.getValueInRange(t)??e.getValue():e.getValue())},[W,X]),$e=typeof window<`u`&&`ontouchstart`in window,et=(0,R.useRef)(null),[tt,nt]=(0,R.useState)(null);(0,R.useEffect)(()=>{if(!$e)return;let e=window.visualViewport;if(!e)return;let t=()=>{let t=et.current;if(!t)return;let n=t.getBoundingClientRect().top;nt(e.height-Math.max(0,n))};return e.addEventListener(`resize`,t),e.addEventListener(`scroll`,t),()=>{e.removeEventListener(`resize`,t),e.removeEventListener(`scroll`,t)}},[$e]);let Z=(0,R.useRef)([]),rt=(0,R.useRef)(X);rt.current=X,(0,R.useEffect)(()=>()=>{Z.current.forEach(e=>e.dispose()),Z.current=[]},[]),(0,R.useEffect)(()=>{fe&&t&&w(t,{type:`sqlite`})},[fe,t,w]);let Q=n?/^(\/|[A-Za-z]:[/\\])/.test(n):!1;(0,R.useEffect)(()=>{if(a!=null){f(!1);return}if(D){c(O??``),y.current=O??``,f(!1),O&&g(!0);return}if(!n||!Q&&!r)return;if(M||N||ue||P){f(!1);return}f(!0),m(null);let e=Q?`/api/fs/read?path=${encodeURIComponent(n)}`:`${ne(r)}/files/read?path=${encodeURIComponent(n)}`;return x.get(e).then(e=>{c(e.content),e.encoding&&u(e.encoding),y.current=e.content,f(!1)}).catch(e=>{m(e instanceof Error?e.message:`Failed to load file`),f(!1)}),()=>{v.current&&clearTimeout(v.current)}},[n,r,M,N,Q,D]);let it=(0,R.useRef)(h);it.current=h,(0,R.useEffect)(()=>{if(!n||!r||a!=null||D)return;let e=e=>{let t=e.detail;if(t.projectName!==r||t.path!==n||it.current)return;let i=Q?`/api/fs/read?path=${encodeURIComponent(n)}`:`${ne(r)}/files/read?path=${encodeURIComponent(n)}`;x.get(i).then(e=>{e.content!==y.current&&(c(e.content),y.current=e.content,e.encoding&&u(e.encoding))}).catch(()=>{})};return window.addEventListener(`file:changed`,e),()=>window.removeEventListener(`file:changed`,e)},[n,r,Q,a,D]),(0,R.useEffect)(()=>{if(!A||a!=null)return;let t=D?`Untitled-${e?.untitledNumber??1}`:n?C(n):`Untitled`,r=h?`${t} \u25CF`:t;A.title!==r&&w(A.id,{title:r})},[h]);let at=(0,R.useCallback)(async e=>{if(n&&!(!Q&&!r))try{Q?await x.put(`/api/fs/write`,{path:n,content:e}):await x.put(`${ne(r)}/files/write`,{path:n,content:e}),g(!1)}catch{}},[n,r,Q]);function ot(n){let r=n??``;c(r),y.current=r,g(!0),v.current&&clearTimeout(v.current),D?v.current=setTimeout(()=>{t&&w(t,{metadata:{...e,unsavedContent:y.current}})},2e3):v.current=setTimeout(()=>at(y.current),1e3)}let st=(0,R.useCallback)(async(e,n)=>{try{if(v.current&&clearTimeout(v.current),await x.put(`/api/fs/write`,{path:e,content:n}),t){let{closeTab:n,openTab:r}=ie.getState();n(t),r({type:`editor`,title:C(e),projectId:null,metadata:{filePath:e},closable:!0})}g(!1),k(!1)}catch{}},[t]),$=e?.lineNumber,ct=(0,R.useCallback)((e,t)=>{if(b.current=e,H.current=t,$&&$>0&&setTimeout(()=>{e.revealLineInCenter($),e.setPosition({lineNumber:$,column:1}),e.focus()},100),D&&e.addCommand(t.KeyMod.CtrlCmd|t.KeyCode.KeyS,()=>k(!0)),e.addCommand(t.KeyMod.Alt|t.KeyCode.KeyZ,()=>re.getState().toggleWordWrap()),t.languages.typescript.typescriptDefaults.setDiagnosticsOptions({noSemanticValidation:!0,noSyntaxValidation:!0,noSuggestionDiagnostics:!0}),t.languages.typescript.javascriptDefaults.setDiagnosticsOptions({noSemanticValidation:!0,noSyntaxValidation:!0,noSuggestionDiagnostics:!0}),q&&(U.current?.dispose(),U.current=t.languages.registerCompletionItemProvider(`sql`,ee(t,q))),F){Z.current.forEach(e=>e.dispose()),Z.current=[];let n=e.getModel(),r=e.addCommand(0,(e,t)=>{t&&rt.current(t)});if(r&&n){let e=t.languages.registerCodeLensProvider(`sql`,{provideCodeLenses:e=>{if(e!==n)return{lenses:[],dispose:()=>{}};let t=[],i=e.getValue().split(`
|
|
6
6
|
`),a=-1,o=[],s=!1,c=(e,n)=>{let i=n.trim();!i||i.startsWith(`--`)||t.push({range:{startLineNumber:e,startColumn:1,endLineNumber:e,endColumn:1},command:{id:r,title:`▷ Run`,arguments:[i]}})};for(let e=0;e<i.length;e++){let t=i[e].trim();if(a===-1){if(!t||t.startsWith(`--`))continue;a=e+1,o=[]}o.push(i[e]),(t.match(/\$\$/g)||[]).length%2==1&&(s=!s),!s&&t.endsWith(`;`)&&(c(a,o.join(`
|
|
7
7
|
`)),a=-1,o=[])}return a>0&&o.join(``).trim()&&c(a,o.join(`
|
|
8
8
|
`)),{lenses:t,dispose:()=>{}}}});Z.current.push(e)}}},[q]);if(!a&&!D&&(!n||!Q&&!r))return(0,z.jsx)(`div`,{className:`flex items-center justify-center h-full text-text-secondary text-sm`,children:`No file selected.`});if(d)return(0,z.jsxs)(`div`,{className:`flex items-center justify-center h-full gap-2 text-text-secondary`,children:[(0,z.jsx)(I,{className:`size-5 animate-spin`}),(0,z.jsx)(`span`,{className:`text-sm`,children:`Loading file...`})]});if(p)return(0,z.jsx)(`div`,{className:`flex items-center justify-center h-full text-error text-sm`,children:p});if(M)return(0,z.jsx)(R.Suspense,{fallback:(0,z.jsx)(Y,{}),children:(0,z.jsx)(je,{filePath:n,projectName:r})});if(N)return(0,z.jsx)(R.Suspense,{fallback:(0,z.jsx)(Y,{}),children:(0,z.jsx)(Me,{filePath:n,projectName:r})});if(ue)return(0,z.jsx)(R.Suspense,{fallback:(0,z.jsx)(Y,{}),children:(0,z.jsx)(Ne,{filePath:n,projectName:r})});if(P)return(0,z.jsx)(R.Suspense,{fallback:(0,z.jsx)(Y,{}),children:(0,z.jsx)(Pe,{filePath:n,projectName:r})});if(l===`base64`)return(0,z.jsxs)(`div`,{className:`flex flex-col items-center justify-center h-full gap-3 text-text-secondary`,children:[(0,z.jsx)(_,{className:`size-10 text-text-subtle`}),(0,z.jsx)(`p`,{className:`text-sm`,children:`This file is a binary format and cannot be displayed.`}),(0,z.jsx)(`p`,{className:`text-xs text-text-subtle`,children:n})]});let lt=F?(0,z.jsxs)(`div`,{className:`shrink-0 flex items-center gap-1 px-2 border-l border-border`,children:[(0,z.jsx)(i,{className:`size-3 text-muted-foreground`}),(0,z.jsxs)(`select`,{value:V??``,onChange:e=>{let t=Number(e.target.value);t&&ke(t)},className:`h-5 text-[10px] bg-transparent border border-border rounded px-1 text-foreground outline-none max-w-[140px]`,title:`Select connection for autocomplete`,children:[(0,z.jsx)(`option`,{value:``,children:`Connection…`}),B.map(e=>(0,z.jsx)(`option`,{value:e.id,children:e.name},e.id))]}),(0,z.jsx)(`button`,{type:`button`,onClick:Qe,disabled:!W,className:`p-0.5 rounded text-muted-foreground hover:text-primary disabled:opacity-30 transition-colors`,title:`Run SQL`,children:(0,z.jsx)(se,{className:`size-3.5`})})]}):null;return(0,z.jsxs)(`div`,{ref:et,className:`flex flex-col h-full w-full overflow-hidden`,style:tt?{height:`${tt}px`,maxHeight:`${tt}px`}:void 0,children:[a!=null&&Ee&&(0,z.jsx)(`div`,{className:`flex items-center h-7 border-b border-border bg-background shrink-0 px-2 gap-2`,children:(0,z.jsx)(`button`,{type:`button`,onClick:De,className:`text-[10px] px-2 py-0.5 rounded border border-border hover:bg-muted transition-colors text-foreground`,children:G?`Raw`:`Beautify`})}),n&&r&&t&&(0,z.jsxs)(`div`,{className:`hidden md:flex items-center h-7 border-b border-border bg-background shrink-0`,children:[(0,z.jsx)(Se,{filePath:n,projectName:r,tabId:t,className:`flex items-center flex-1 min-w-0 overflow-x-auto scrollbar-none px-2 gap-0.5`}),lt,(0,z.jsx)(we,{ext:j,mdMode:he,onMdModeChange:ge,csvMode:L,onCsvModeChange:ye,wordWrap:T,onToggleWordWrap:E,filePath:n,projectName:r,className:`shrink-0 flex items-center gap-1 px-2`})]}),F&&(!r||!t)&&(0,z.jsxs)(`div`,{className:`hidden md:flex items-center h-7 border-b border-border bg-background shrink-0 px-2`,children:[(0,z.jsx)(`span`,{className:`text-xs text-muted-foreground truncate flex-1`,children:n?C(n):`SQL`}),lt]}),me&&L===`table`?(0,z.jsx)(R.Suspense,{fallback:(0,z.jsx)(`div`,{className:`flex items-center justify-center h-full`,children:(0,z.jsx)(I,{className:`size-5 animate-spin text-text-subtle`})}),children:(0,z.jsx)(Ae,{content:s??``,onContentChange:ot,wordWrap:T})}):pe&&he===`preview`?(0,z.jsx)(He,{content:s??``}):(0,z.jsx)(`div`,{className:`flex-1 overflow-hidden min-h-0`,children:(0,z.jsx)(_e,{height:`100%`,language:o??Be(n??``),value:s??``,onChange:a==null?ot:void 0,onMount:ct,theme:oe,options:{fontSize:13,fontFamily:`Menlo, Monaco, Consolas, monospace`,wordWrap:T?`on`:`off`,minimap:{enabled:!1},scrollBeyondLastLine:!1,automaticLayout:!0,lineNumbers:`on`,folding:!0,bracketPairColorization:{enabled:!0},readOnly:a!=null},loading:(0,z.jsx)(I,{className:`size-5 animate-spin text-text-subtle`})})}),F&&(Ue||Ge||qe)&&(0,z.jsx)(Ve,{result:Ue,error:Ge,loading:qe,connName:W?.name,onClose:()=>{We(null),Ke(null),Je(!1)},onOpenInTab:Ze}),$e&&(0,z.jsx)(Oe,{editorRef:b,readOnly:a!=null}),ce&&(0,z.jsx)(Te,{open:ce,defaultName:`Untitled-${e?.untitledNumber??1}`,content:y.current,onSave:st,onCancel:()=>k(!1)})]})}),J=()=>{};function Ve({result:e,error:t,loading:n,connName:r,onClose:a,onOpenInTab:o}){let s=(0,R.useMemo)(()=>e?.changeType===`select`&&e.rows.length>0?{columns:e.columns,rows:e.rows,total:e.rows.length,limit:e.rows.length}:null,[e]),c=(0,R.useMemo)(()=>(e?.columns??[]).map(e=>({name:e,type:`text`,nullable:!0,pk:!1,defaultValue:null})),[e?.columns]),[l,u]=(0,R.useState)(250),d=(0,R.useCallback)(e=>{e.preventDefault();let t=e.clientY,n=l,r=e=>u(Math.max(80,n+(t-e.clientY))),i=()=>{document.removeEventListener(`mousemove`,r),document.removeEventListener(`mouseup`,i)};document.addEventListener(`mousemove`,r),document.addEventListener(`mouseup`,i)},[l]);return(0,z.jsxs)(`div`,{className:`shrink-0 border-t border-border flex flex-col`,style:{height:l},children:[(0,z.jsx)(`div`,{onMouseDown:d,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,z.jsx)(oe,{className:`size-3 text-muted-foreground/50`})}),(0,z.jsxs)(`div`,{className:`flex items-center gap-2 px-2 py-1 bg-muted/50 border-b border-border shrink-0`,children:[(0,z.jsx)(i,{className:`size-3 text-muted-foreground`}),(0,z.jsxs)(`span`,{className:`text-xs font-medium text-foreground truncate flex-1`,children:[r?`${r} · Results`:`Query Results`,e?.executionTimeMs!=null&&(0,z.jsxs)(`span`,{className:`text-muted-foreground ml-1.5 font-normal`,children:[e.executionTimeMs,`ms`]})]}),(0,z.jsxs)(`button`,{type:`button`,onClick:o,title:`Open in DB Viewer tab`,className:`flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] text-muted-foreground hover:text-foreground hover:bg-muted transition-colors`,children:[(0,z.jsx)(ge,{className:`size-3`}),(0,z.jsx)(`span`,{className:`hidden sm:inline`,children:`Open in Tab`})]}),(0,z.jsx)(`button`,{type:`button`,onClick:a,title:`Close results`,className:`p-0.5 rounded text-muted-foreground hover:text-foreground transition-colors`,children:(0,z.jsx)(g,{className:`size-3`})})]}),(0,z.jsxs)(`div`,{className:`flex-1 overflow-hidden min-h-0`,children:[n&&(0,z.jsx)(`div`,{className:`flex items-center justify-center h-full`,children:(0,z.jsx)(I,{className:`size-4 animate-spin text-muted-foreground`})}),t&&(0,z.jsx)(`div`,{className:`px-3 py-2 text-xs text-destructive bg-destructive/5`,children:t}),e?.changeType===`modify`&&(0,z.jsxs)(`div`,{className:`px-3 py-2 text-xs text-green-500`,children:[e.rowsAffected,` row(s) affected`]}),s&&(0,z.jsx)(v,{columns:s.columns,rows:s.rows,total:s.total,limit:s.limit,schema:c,loading:!1,page:1,onPageChange:J,onCellUpdate:J,orderBy:null,orderDir:`ASC`,onToggleSort:J,connectionName:r}),e?.changeType===`select`&&e.rows.length===0&&(0,z.jsx)(`div`,{className:`px-3 py-2 text-xs text-muted-foreground`,children:`No results`})]})]})}function Y(){return(0,z.jsx)(`div`,{className:`flex items-center justify-center h-full`,children:(0,z.jsx)(I,{className:`size-5 animate-spin text-text-subtle`})})}function He({content:e}){return(0,z.jsx)(R.Suspense,{fallback:(0,z.jsx)(`div`,{className:`animate-pulse h-4 bg-muted rounded m-4`}),children:(0,z.jsx)(ke,{content:e,className:`flex-1 overflow-auto p-4`})})}export{q as CodeEditor};
|
|
9
|
-
//# sourceMappingURL=code-editor-
|
|
9
|
+
//# sourceMappingURL=code-editor-MXnkYRLp.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"mappings":"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","names":[],"ignoreList":[0],"sources":["../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/redo-2.js","../../../src/web/components/editor/editor-breadcrumb.tsx","../../../src/web/components/editor/editor-toolbar.tsx","../../../src/web/components/editor/save-as-dialog.tsx","../../../src/web/components/editor/editor-mobile-toolbar.tsx","../../../src/web/components/editor/code-editor.tsx"],"sourcesContent":["/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"m15 14 5-5-5-5\", key: \"12vg1m\" }],\n [\"path\", { d: \"M20 9H9.5A5.5 5.5 0 0 0 4 14.5A5.5 5.5 0 0 0 9.5 20H13\", key: \"6uklza\" }]\n];\nconst Redo2 = createLucideIcon(\"redo-2\", __iconNode);\n\nexport { __iconNode, Redo2 as default };\n//# sourceMappingURL=redo-2.js.map\n","import { useMemo, useRef, useEffect } from \"react\";\nimport { ChevronRight, Folder, File, FileCode, FileJson, FileText, FileType } from \"lucide-react\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n DropdownMenuSub,\n DropdownMenuSubTrigger,\n DropdownMenuSubContent,\n} from \"@/components/ui/dropdown-menu\";\nimport { useFileStore, type FileNode } from \"@/stores/file-store\";\nimport { useShallow } from \"zustand/react/shallow\";\nimport { useTabStore } from \"@/stores/tab-store\";\nimport { useProjectStore } from \"@/stores/project-store\";\nimport { basename } from \"@/lib/utils\";\n\nconst ICON_MAP: Record<string, React.ComponentType<{ className?: string }>> = {\n ts: FileCode, tsx: FileCode, js: FileCode, jsx: FileCode,\n py: FileCode, rs: FileCode, go: FileCode, html: FileCode,\n css: FileCode, scss: FileCode,\n json: FileJson,\n md: FileText, txt: FileText,\n yaml: FileType, yml: FileType,\n};\n\nfunction getIcon(name: string, isDir: boolean) {\n if (isDir) return Folder;\n const ext = name.split(\".\").pop()?.toLowerCase() ?? \"\";\n return ICON_MAP[ext] ?? File;\n}\n\ninterface BreadcrumbSegment {\n name: string;\n fullPath: string;\n node: FileNode | null;\n siblings: FileNode[];\n /** Folder path whose children are the siblings (empty string = root) */\n parentPath: string;\n}\n\nfunction walkTree(tree: FileNode[], segments: string[]): BreadcrumbSegment[] {\n const result: BreadcrumbSegment[] = [];\n let current: FileNode[] = tree;\n let parentPath = \"\";\n\n for (let i = 0; i < segments.length; i++) {\n const seg = segments[i]!;\n const fullPath = segments.slice(0, i + 1).join(\"/\");\n const match = current.find((n) => n.name === seg);\n result.push({\n name: seg,\n fullPath,\n node: match ?? null,\n siblings: current,\n parentPath,\n });\n if (match?.children) {\n parentPath = match.path;\n current = match.children;\n } else {\n // Remaining segments — parent children not loaded yet\n for (let j = i + 1; j < segments.length; j++) {\n result.push({\n name: segments[j]!,\n fullPath: segments.slice(0, j + 1).join(\"/\"),\n node: null,\n siblings: [],\n parentPath: segments.slice(0, j).join(\"/\"),\n });\n }\n break;\n }\n }\n return result;\n}\n\nfunction sortNodes(nodes: FileNode[]): FileNode[] {\n return [...nodes].sort((a, b) => {\n if (a.type !== b.type) return a.type === \"directory\" ? -1 : 1;\n return a.name.localeCompare(b.name);\n });\n}\n\ninterface EditorBreadcrumbProps {\n filePath: string;\n projectName: string;\n tabId: string;\n className?: string;\n}\n\nexport function EditorBreadcrumb({ filePath, projectName, tabId, className }: EditorBreadcrumbProps) {\n const tree = useFileStore((s) => s.tree);\n const { updateTab, openTab } = useTabStore(useShallow((s) => ({ updateTab: s.updateTab, openTab: s.openTab })));\n const projectPath = useProjectStore((s) => s.projects.find((p) => p.name === projectName)?.path ?? \"\");\n const scrollRef = useRef<HTMLDivElement>(null);\n\n // Strip project root prefix so segments align with the relative-path file tree\n const { prefixParts, relativePath } = useMemo(() => {\n const norm = filePath.startsWith(\"/\") ? filePath.slice(1) : filePath;\n const normRoot = projectPath.startsWith(\"/\") ? projectPath.slice(1) : projectPath;\n if (normRoot && norm.startsWith(normRoot + \"/\")) {\n const rel = norm.slice(normRoot.length + 1);\n return { prefixParts: normRoot.split(\"/\"), relativePath: rel };\n }\n return { prefixParts: [] as string[], relativePath: norm };\n }, [filePath, projectPath]);\n\n const segments = useMemo(\n () => walkTree(tree, relativePath.split(\"/\").filter(Boolean)),\n [tree, relativePath],\n );\n\n // Auto-scroll to rightmost segment\n useEffect(() => {\n if (scrollRef.current) {\n scrollRef.current.scrollLeft = scrollRef.current.scrollWidth;\n }\n }, [segments]);\n\n function handleFileClick(path: string, e: React.MouseEvent) {\n const name = basename(path);\n if (e.metaKey || e.ctrlKey) {\n openTab({ type: \"editor\", title: name, metadata: { filePath: path, projectName }, projectId: projectName, closable: true });\n } else {\n updateTab(tabId, { title: name, metadata: { filePath: path, projectName } });\n }\n }\n\n return (\n <div ref={scrollRef} className={className}>\n {prefixParts.map((part, i) => (\n <div key={`prefix-${i}`} className=\"flex items-center shrink-0\">\n {i > 0 && <ChevronRight className=\"size-3 text-muted-foreground shrink-0 mx-0.5\" />}\n <span className=\"text-xs text-muted-foreground px-1 py-0.5\">{part}</span>\n </div>\n ))}\n {segments.map((seg, i) => (\n <div key={seg.fullPath} className=\"flex items-center shrink-0\">\n {(i > 0 || prefixParts.length > 0) && <ChevronRight className=\"size-3 text-muted-foreground shrink-0 mx-0.5\" />}\n <SegmentDropdown\n segment={seg}\n isLast={i === segments.length - 1}\n projectName={projectName}\n onFileClick={handleFileClick}\n />\n </div>\n ))}\n </div>\n );\n}\n\ninterface SegmentDropdownProps {\n segment: BreadcrumbSegment;\n isLast: boolean;\n projectName: string;\n onFileClick: (path: string, e: React.MouseEvent) => void;\n}\n\nfunction SegmentDropdown({ segment, isLast, projectName, onFileClick }: SegmentDropdownProps) {\n const loadChildren = useFileStore((s) => s.loadChildren);\n const loadedPaths = useFileStore((s) => s.loadedPaths);\n const sorted = useMemo(() => sortNodes(segment.siblings), [segment.siblings]);\n const isLoaded = loadedPaths.has(segment.parentPath);\n\n function handleOpenChange(open: boolean) {\n if (open && !isLoaded) {\n loadChildren(projectName, segment.parentPath);\n }\n }\n\n return (\n <DropdownMenu onOpenChange={handleOpenChange}>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n className={`text-xs px-1 py-0.5 rounded hover:bg-muted transition-colors truncate max-w-[120px] ${\n isLast ? \"text-foreground font-medium\" : \"text-muted-foreground\"\n }`}\n >\n {segment.name}\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"max-h-[300px] p-1\">\n {sorted.length === 0 ? (\n <DropdownMenuItem disabled className=\"text-xs text-muted-foreground\">\n Loading…\n </DropdownMenuItem>\n ) : (\n sorted.map((node) => (\n <NodeMenuItem\n key={node.path}\n node={node}\n projectName={projectName}\n activePath={segment.fullPath}\n onFileClick={onFileClick}\n />\n ))\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\ninterface NodeMenuItemProps {\n node: FileNode;\n projectName: string;\n activePath: string;\n onFileClick: (path: string, e: React.MouseEvent) => void;\n}\n\nfunction NodeMenuItem({ node, projectName, activePath, onFileClick }: NodeMenuItemProps) {\n const Icon = getIcon(node.name, node.type === \"directory\");\n const isActive = node.path === activePath;\n const loadChildren = useFileStore((s) => s.loadChildren);\n const loadedPaths = useFileStore((s) => s.loadedPaths);\n\n if (node.type === \"directory\") {\n const children = node.children ?? [];\n const isLoaded = loadedPaths.has(node.path);\n\n function handleSubOpen(open: boolean) {\n if (open && !isLoaded) {\n loadChildren(projectName, node.path);\n }\n }\n\n return (\n <DropdownMenuSub onOpenChange={handleSubOpen}>\n <DropdownMenuSubTrigger className={`text-xs gap-1.5 ${isActive ? \"bg-muted\" : \"\"}`}>\n <Icon className=\"size-3.5 shrink-0 text-muted-foreground\" />\n <span className=\"truncate\">{node.name}</span>\n </DropdownMenuSubTrigger>\n <DropdownMenuSubContent className=\"max-h-[300px] overflow-y-auto p-1\">\n {children.length === 0 ? (\n <DropdownMenuItem disabled className=\"text-xs text-muted-foreground\">\n Loading…\n </DropdownMenuItem>\n ) : (\n sortNodes(children).map((child) => (\n <NodeMenuItem\n key={child.path}\n node={child}\n projectName={projectName}\n activePath={activePath}\n onFileClick={onFileClick}\n />\n ))\n )}\n </DropdownMenuSubContent>\n </DropdownMenuSub>\n );\n }\n\n return (\n <DropdownMenuItem\n className={`text-xs gap-1.5 cursor-pointer ${isActive ? \"bg-muted\" : \"\"}`}\n onSelect={(e) => {\n // onSelect doesn't give MouseEvent, use click handler for Ctrl detection\n }}\n onClick={(e) => {\n onFileClick(node.path, e);\n }}\n >\n <Icon className=\"size-3.5 shrink-0 text-muted-foreground\" />\n <span className=\"truncate\">{node.name}</span>\n </DropdownMenuItem>\n );\n}\n","import { Code, Eye, WrapText, Table, Download } from \"lucide-react\";\nimport { downloadFile } from \"@/lib/file-download\";\n\ninterface EditorToolbarProps {\n ext: string;\n mdMode?: \"edit\" | \"preview\";\n onMdModeChange?: (mode: \"edit\" | \"preview\") => void;\n csvMode?: \"table\" | \"raw\";\n onCsvModeChange?: (mode: \"table\" | \"raw\") => void;\n wordWrap: boolean;\n onToggleWordWrap: () => void;\n filePath?: string;\n projectName?: string;\n className?: string;\n}\n\nfunction ToolbarButton({\n active,\n onClick,\n icon: Icon,\n label,\n}: {\n active: boolean;\n onClick: () => void;\n icon: React.ComponentType<{ className?: string }>;\n label: string;\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n className={`flex items-center gap-1 px-2 py-1 rounded text-xs transition-colors ${\n active ? \"bg-muted text-foreground\" : \"text-muted-foreground hover:text-foreground\"\n }`}\n >\n <Icon className=\"size-3\" />\n <span className=\"hidden sm:inline\">{label}</span>\n </button>\n );\n}\n\nexport function EditorToolbar({\n ext,\n mdMode,\n onMdModeChange,\n csvMode,\n onCsvModeChange,\n wordWrap,\n onToggleWordWrap,\n filePath,\n projectName,\n className,\n}: EditorToolbarProps) {\n const isMarkdown = ext === \"md\" || ext === \"mdx\";\n const isCsv = ext === \"csv\";\n\n return (\n <div className={className}>\n {isMarkdown && onMdModeChange && (\n <>\n <ToolbarButton active={mdMode === \"edit\"} onClick={() => onMdModeChange(\"edit\")} icon={Code} label=\"Edit\" />\n <ToolbarButton active={mdMode === \"preview\"} onClick={() => onMdModeChange(\"preview\")} icon={Eye} label=\"Preview\" />\n </>\n )}\n {isCsv && onCsvModeChange && (\n <>\n <ToolbarButton active={csvMode === \"table\"} onClick={() => onCsvModeChange(\"table\")} icon={Table} label=\"Table\" />\n <ToolbarButton active={csvMode === \"raw\"} onClick={() => onCsvModeChange(\"raw\")} icon={Code} label=\"Raw\" />\n </>\n )}\n <ToolbarButton\n active={wordWrap}\n onClick={onToggleWordWrap}\n icon={WrapText}\n label=\"Wrap\"\n />\n {filePath && projectName && (\n <ToolbarButton\n active={false}\n onClick={() => downloadFile(projectName, filePath)}\n icon={Download}\n label=\"Download\"\n />\n )}\n </div>\n );\n}\n","import { useState, useCallback } from \"react\";\nimport {\n Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter,\n} from \"@/components/ui/dialog\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { FileBrowserPicker } from \"@/components/ui/file-browser-picker\";\nimport { useProjectStore } from \"@/stores/project-store\";\n\ninterface SaveAsDialogProps {\n open: boolean;\n defaultName: string;\n content: string;\n onSave: (fullPath: string, content: string) => void;\n onCancel: () => void;\n}\n\nexport function SaveAsDialog({ open, defaultName, content, onSave, onCancel }: SaveAsDialogProps) {\n const [filename, setFilename] = useState(defaultName);\n const [showPicker, setShowPicker] = useState(false);\n const [error, setError] = useState(\"\");\n const activeProject = useProjectStore((s) => s.activeProject);\n\n const validateAndProceed = useCallback(() => {\n const trimmed = filename.trim();\n if (!trimmed) { setError(\"Filename cannot be empty\"); return; }\n if (/[/\\\\]/.test(trimmed)) { setError(\"Filename cannot contain / or \\\\\"); return; }\n setError(\"\");\n setShowPicker(true);\n }, [filename]);\n\n const handleFolderSelect = useCallback((dirPath: string) => {\n const sep = dirPath.includes(\"\\\\\") ? \"\\\\\" : \"/\";\n const fullPath = dirPath.endsWith(sep) ? `${dirPath}${filename.trim()}` : `${dirPath}${sep}${filename.trim()}`;\n onSave(fullPath, content);\n }, [filename, content, onSave]);\n\n if (showPicker) {\n return (\n <FileBrowserPicker\n open\n mode=\"folder\"\n root={activeProject?.path}\n title={`Save \"${filename.trim()}\" to...`}\n onSelect={handleFolderSelect}\n onCancel={() => setShowPicker(false)}\n />\n );\n }\n\n return (\n <Dialog open={open} onOpenChange={(v) => { if (!v) onCancel(); }}>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>Save As</DialogTitle>\n </DialogHeader>\n <div className=\"flex flex-col gap-2 py-2\">\n <label className=\"text-sm text-muted-foreground\">Filename</label>\n <Input\n value={filename}\n onChange={(e) => { setFilename(e.target.value); setError(\"\"); }}\n onKeyDown={(e) => { if (e.key === \"Enter\") validateAndProceed(); }}\n placeholder=\"e.g. my-file.ts\"\n autoFocus\n />\n {error && <p className=\"text-xs text-destructive\">{error}</p>}\n </div>\n <DialogFooter>\n <Button variant=\"outline\" onClick={onCancel}>Cancel</Button>\n <Button onClick={validateAndProceed}>Choose Folder...</Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n","import { useCallback, useRef, useState } from \"react\";\nimport { ClipboardPaste, Undo2, Redo2, X } from \"lucide-react\";\nimport type * as MonacoType from \"monaco-editor\";\n\n/** Clipboard API requires secure context (HTTPS / localhost) */\nconst isSecureContext = typeof window !== \"undefined\" && window.isSecureContext;\n\n/** Symbols commonly needed when coding on mobile — ordered by frequency */\nconst SYMBOL_KEYS = [\n \"(\", \")\", \"{\", \"}\", \"[\", \"]\",\n \"<\", \">\", \";\", \":\", \"=\",\n '\"', \"'\", \"`\", \"/\", \"\\\\\", \"_\", \"#\",\n];\n\nconst btnBase =\n \"px-2 py-1.5 rounded text-xs min-w-[36px] min-h-[32px] bg-surface-elevated text-text-primary active:bg-primary active:text-primary-foreground transition-colors select-none\";\nconst btnSymbol =\n \"px-3 py-1.5 rounded text-xs font-mono min-w-[36px] min-h-[32px] bg-surface-elevated text-text-primary active:bg-primary active:text-primary-foreground transition-colors select-none\";\nconst divider = \"w-px h-5 bg-border mx-0.5 shrink-0\";\n\ninterface EditorMobileToolbarProps {\n editorRef: React.RefObject<MonacoType.editor.IStandaloneCodeEditor | null>;\n readOnly?: boolean;\n}\n\nexport function EditorMobileToolbar({ editorRef, readOnly }: EditorMobileToolbarProps) {\n const getEditor = useCallback(() => editorRef.current, [editorRef]);\n\n /** Insert text at cursor position in Monaco */\n const insertText = useCallback((text: string) => {\n const editor = getEditor();\n if (!editor) return;\n editor.focus();\n const selection = editor.getSelection();\n if (selection) {\n editor.executeEdits(\"mobile-toolbar\", [{ range: selection, text }]);\n }\n }, [getEditor]);\n\n // --- Paste: two strategies based on secure context ---\n const [pasteMode, setPasteMode] = useState(false);\n const pasteRef = useRef<HTMLTextAreaElement | null>(null);\n\n // HTTPS: use Clipboard API directly (single tap)\n const handleClipboardPaste = useCallback(async () => {\n try {\n const text = await navigator.clipboard.readText();\n if (text) insertText(text);\n } catch { /* permission denied */ }\n }, [insertText]);\n\n // HTTP fallback: show textarea for native long-press paste\n const openPasteMode = useCallback(() => {\n setPasteMode(true);\n requestAnimationFrame(() => pasteRef.current?.focus());\n }, []);\n\n const handleNativePaste = useCallback((e: React.ClipboardEvent<HTMLTextAreaElement>) => {\n e.preventDefault();\n const text = e.clipboardData.getData(\"text/plain\");\n if (!text) return;\n setPasteMode(false);\n insertText(text);\n }, [insertText]);\n\n const handleUndo = useCallback(() => {\n const editor = getEditor();\n if (!editor) return;\n editor.focus();\n editor.trigger(\"mobile-toolbar\", \"undo\", null);\n }, [getEditor]);\n\n const handleRedo = useCallback(() => {\n const editor = getEditor();\n if (!editor) return;\n editor.focus();\n editor.trigger(\"mobile-toolbar\", \"redo\", null);\n }, [getEditor]);\n\n const handleTab = useCallback(() => {\n const editor = getEditor();\n if (!editor) return;\n editor.focus();\n editor.trigger(\"mobile-toolbar\", \"tab\", null);\n }, [getEditor]);\n\n if (readOnly) return null;\n\n return (\n <div className=\"shrink-0 border-t border-border bg-surface\">\n {/* HTTP-only: textarea for native paste via long-press */}\n {!isSecureContext && pasteMode && (\n <div className=\"flex items-center gap-2 px-2 py-1.5 border-b border-border bg-muted/50\">\n <textarea\n ref={pasteRef}\n onPaste={handleNativePaste}\n placeholder=\"Long-press here → Paste\"\n className=\"flex-1 h-8 rounded border border-border bg-background text-foreground text-xs px-2 py-1.5 resize-none focus:outline-none focus:ring-1 focus:ring-primary\"\n />\n <button\n type=\"button\"\n onClick={() => setPasteMode(false)}\n className=\"p-1.5 rounded text-muted-foreground active:bg-muted transition-colors\"\n >\n <X size={14} />\n </button>\n </div>\n )}\n\n {/* Toolbar buttons */}\n <div className=\"flex items-center gap-1 px-2 py-1.5 overflow-x-auto\">\n {/* Paste: Clipboard API on HTTPS, textarea fallback on HTTP */}\n <button\n type=\"button\"\n onClick={isSecureContext ? handleClipboardPaste : openPasteMode}\n className={btnBase}\n title=\"Paste\"\n >\n <ClipboardPaste size={14} />\n </button>\n <button type=\"button\" onClick={handleUndo} className={btnBase} title=\"Undo\">\n <Undo2 size={14} />\n </button>\n <button type=\"button\" onClick={handleRedo} className={btnBase} title=\"Redo\">\n <Redo2 size={14} />\n </button>\n\n <div className={divider} />\n\n <button type=\"button\" onClick={handleTab} className={btnSymbol}>\n Tab\n </button>\n\n <div className={divider} />\n\n {SYMBOL_KEYS.map((key) => (\n <button key={key} type=\"button\" onClick={() => insertText(key)} className={btnSymbol}>\n {key}\n </button>\n ))}\n </div>\n </div>\n );\n}\n","import { useEffect, useState, useCallback, useRef, useMemo, memo, lazy, Suspense } from \"react\";\nimport Editor, { type OnMount } from \"@monaco-editor/react\";\nimport type * as MonacoType from \"monaco-editor\";\nimport { api, projectUrl } from \"@/lib/api-client\";\nimport { useShallow } from \"zustand/react/shallow\";\nimport { useTabStore } from \"@/stores/tab-store\";\nimport { usePanelStore } from \"@/stores/panel-store\";\nimport { useSettingsStore } from \"@/stores/settings-store\";\nimport { basename } from \"@/lib/utils\";\nimport { useMonacoTheme } from \"@/lib/use-monaco-theme\";\nimport { Loader2, FileWarning, Play, Database, ExternalLink, X, GripHorizontal } from \"lucide-react\";\nimport { EditorBreadcrumb } from \"./editor-breadcrumb\";\nimport { EditorToolbar } from \"./editor-toolbar\";\nimport { SaveAsDialog } from \"./save-as-dialog\";\nimport { EditorMobileToolbar } from \"./editor-mobile-toolbar\";\nimport { createSqlCompletionProvider, clearCompletionCache, type SchemaInfo } from \"../database/sql-completion-provider\";\nimport { useConnections, type Connection } from \"../database/use-connections\";\nimport { GlideDataGrid } from \"../database/glide-data-grid\";\nimport type { GridColumnSchema } from \"../database/glide-grid-types\";\nimport type { DbQueryResult } from \"../database/use-database\";\n\nconst MarkdownRenderer = lazy(() =>\n import(\"@/components/shared/markdown-renderer\").then((m) => ({ default: m.MarkdownRenderer }))\n);\nconst CsvPreview = lazy(() => import(\"./csv-preview\").then((m) => ({ default: m.CsvPreview })));\nconst ImagePreview = lazy(() => import(\"./image-preview\").then((m) => ({ default: m.ImagePreview })));\nconst PdfPreview = lazy(() => import(\"./pdf-preview\").then((m) => ({ default: m.PdfPreview })));\nconst VideoPreview = lazy(() => import(\"./video-preview\").then((m) => ({ default: m.VideoPreview })));\nconst AudioPreview = lazy(() => import(\"./audio-preview\").then((m) => ({ default: m.AudioPreview })));\n\n/** Image extensions renderable inline */\nconst IMAGE_EXTS = new Set([\"png\", \"jpg\", \"jpeg\", \"gif\", \"webp\", \"svg\", \"ico\"]);\n/** Video extensions playable inline */\nconst VIDEO_EXTS = new Set([\"mp4\", \"webm\", \"mov\", \"ogg\", \"avi\", \"mkv\"]);\n/** Audio extensions playable inline */\nconst AUDIO_EXTS = new Set([\"mp3\", \"wav\", \"flac\", \"aac\", \"m4a\", \"wma\"]);\n/** SQLite extensions — redirect to sqlite viewer */\nconst SQLITE_EXTS = new Set([\"db\", \"sqlite\", \"sqlite3\"]);\n\nfunction getFileExt(filename: string): string {\n return filename.split(\".\").pop()?.toLowerCase() ?? \"\";\n}\n\nfunction getMonacoLanguage(filename: string): string {\n const ext = getFileExt(filename);\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 sql: \"sql\",\n };\n return map[ext] ?? \"plaintext\";\n}\n\ninterface CodeEditorProps {\n metadata?: Record<string, unknown>;\n tabId?: string;\n}\n\nexport const CodeEditor = memo(function CodeEditor({ metadata, tabId }: CodeEditorProps) {\n const filePath = metadata?.filePath as string | undefined;\n const projectName = metadata?.projectName as string | undefined;\n // Inline content mode: read-only Monaco with pre-loaded content (e.g. cell viewer)\n const inlineContent = metadata?.inlineContent as string | undefined;\n const inlineLanguage = metadata?.inlineLanguage as string | undefined;\n const [content, setContent] = useState<string | null>(inlineContent ?? null);\n const [encoding, setEncoding] = useState<string>(\"utf-8\");\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [unsaved, setUnsaved] = useState(false);\n const saveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const latestContentRef = useRef<string>(\"\");\n const editorRef = useRef<MonacoType.editor.IStandaloneCodeEditor | null>(null);\n const { tabs, updateTab } = useTabStore(useShallow((s) => ({ tabs: s.tabs, updateTab: s.updateTab })));\n const { wordWrap, toggleWordWrap } = useSettingsStore(useShallow((s) => ({ wordWrap: s.wordWrap, toggleWordWrap: s.toggleWordWrap })));\n const monacoTheme = useMonacoTheme();\n\n const isUntitled = metadata?.isUntitled === true;\n const savedContent = metadata?.unsavedContent as string | undefined;\n const [showSaveAs, setShowSaveAs] = useState(false);\n\n const ownTab = tabs.find((t) => t.id === tabId);\n const ext = filePath ? getFileExt(filePath) : \"\";\n const isImage = IMAGE_EXTS.has(ext);\n const isPdf = ext === \"pdf\";\n const isVideo = VIDEO_EXTS.has(ext);\n const isAudio = AUDIO_EXTS.has(ext);\n const isSqlite = SQLITE_EXTS.has(ext);\n const isMarkdown = ext === \"md\" || ext === \"mdx\";\n const isCsv = ext === \"csv\";\n const isSql = ext === \"sql\";\n const [mdMode, setMdMode] = useState<\"edit\" | \"preview\">(\"preview\");\n const [csvMode, setCsvMode] = useState<\"table\" | \"raw\">(\"table\");\n\n // SQL file: connection picker + autocomplete + run in DB viewer\n const { connections, cachedTables, refreshTables } = useConnections();\n const [sqlConnId, setSqlConnId] = useState<number | null>(() => {\n if (!isSql || !filePath) return null;\n const stored = localStorage.getItem(`ppm:sql-conn:${filePath}`);\n return stored ? Number(stored) : null;\n });\n const monacoInstanceRef = useRef<typeof MonacoType | null>(null);\n const completionDisposable = useRef<MonacoType.IDisposable | null>(null);\n\n const selectedSqlConn = useMemo(() => connections.find((c) => c.id === sqlConnId) ?? null, [connections, sqlConnId]);\n\n // Beautify for inline content (must be before early returns to maintain hook order)\n const canBeautifyInline = inlineContent != null && (inlineLanguage === \"json\" || inlineLanguage === \"xml\");\n const [isBeautified, setIsBeautified] = useState(false);\n const handleBeautifyInline = useCallback(() => {\n if (!inlineContent) return;\n if (isBeautified) {\n setContent(inlineContent);\n setIsBeautified(false);\n } else {\n const trimmed = inlineContent.trimStart();\n if (inlineLanguage === \"json\") {\n try { setContent(JSON.stringify(JSON.parse(trimmed), null, 2)); setIsBeautified(true); } catch { /* not valid */ }\n } else if (inlineLanguage === \"xml\") {\n let indent = 0;\n const formatted = trimmed.replace(/(>)(<)(\\/*)/g, \"$1\\n$2$3\")\n .split(\"\\n\")\n .map((line) => {\n const l = line.trim();\n if (l.startsWith(\"</\")) indent = Math.max(0, indent - 1);\n const padded = \" \".repeat(indent) + l;\n if (l.startsWith(\"<\") && !l.startsWith(\"</\") && !l.endsWith(\"/>\") && !l.includes(\"</\")) indent++;\n return padded;\n })\n .join(\"\\n\");\n setContent(formatted);\n setIsBeautified(true);\n }\n }\n }, [inlineContent, inlineLanguage, isBeautified]);\n\n // Persist selected connection per file\n const handleSqlConnChange = useCallback((connId: number) => {\n setSqlConnId(connId);\n if (filePath) localStorage.setItem(`ppm:sql-conn:${filePath}`, String(connId));\n // Refresh tables for autocomplete\n refreshTables(connId).catch(() => {});\n }, [filePath, refreshTables]);\n\n // Build SchemaInfo for .sql file autocomplete\n const sqlSchemaInfo = useMemo<SchemaInfo | undefined>(() => {\n if (!isSql || !sqlConnId) return undefined;\n const tables = (cachedTables.get(sqlConnId) ?? []).map((t) => ({ name: t.tableName, schema: t.schemaName }));\n if (tables.length === 0) return undefined;\n return {\n tables,\n getColumns: async (table: string, schema?: string) => {\n return api.get<{ name: string; type: string }[]>(\n `/api/db/connections/${sqlConnId}/schema?table=${encodeURIComponent(table)}${schema ? `&schema=${encodeURIComponent(schema)}` : \"\"}`,\n );\n },\n };\n }, [isSql, sqlConnId, cachedTables]);\n\n // Register/dispose completion provider when connection changes\n useEffect(() => {\n if (!monacoInstanceRef.current || !sqlSchemaInfo) return;\n completionDisposable.current?.dispose();\n clearCompletionCache();\n completionDisposable.current = monacoInstanceRef.current.languages.registerCompletionItemProvider(\n \"sql\",\n createSqlCompletionProvider(monacoInstanceRef.current, sqlSchemaInfo),\n );\n return () => { completionDisposable.current?.dispose(); };\n }, [sqlSchemaInfo]);\n\n // Run SQL inline — execute query and show results in bottom panel\n const openTab = useTabStore((s) => s.openTab);\n const [sqlResult, setSqlResult] = useState<DbQueryResult | null>(null);\n const [sqlError, setSqlError] = useState<string | null>(null);\n const [sqlLoading, setSqlLoading] = useState(false);\n const [sqlResultSql, setSqlResultSql] = useState<string>(\"\");\n const runSqlInViewer = useCallback(async (sqlText: string) => {\n if (!selectedSqlConn) return;\n setSqlLoading(true);\n setSqlError(null);\n setSqlResultSql(sqlText);\n try {\n const result = await api.post<DbQueryResult>(`/api/db/connections/${selectedSqlConn.id}/query`, { sql: sqlText });\n setSqlResult(result);\n } catch (e) {\n setSqlError((e as Error).message);\n setSqlResult(null);\n } finally {\n setSqlLoading(false);\n }\n }, [selectedSqlConn]);\n const openSqlResultInTab = useCallback(() => {\n if (!selectedSqlConn || !sqlResultSql) return;\n openTab({\n type: \"database\",\n title: `${selectedSqlConn.name} · Query`,\n projectId: null,\n closable: true,\n metadata: { connectionId: selectedSqlConn.id, connectionName: selectedSqlConn.name, dbType: selectedSqlConn.type, initialSql: sqlResultSql },\n });\n }, [selectedSqlConn, openTab, sqlResultSql]);\n\n const handleRunInDbViewer = useCallback(() => {\n if (!editorRef.current || !selectedSqlConn) return;\n const editor = editorRef.current;\n const selection = editor.getSelection();\n const sqlText = selection && !selection.isEmpty()\n ? editor.getModel()?.getValueInRange(selection) ?? editor.getValue()\n : editor.getValue();\n runSqlInViewer(sqlText);\n }, [selectedSqlConn, runSqlInViewer]);\n\n // Touch device detection for mobile toolbar\n const isMobile = typeof window !== \"undefined\" && \"ontouchstart\" in window;\n\n // Track visual viewport so toolbar stays above mobile keyboard\n const containerRef = useRef<HTMLDivElement | null>(null);\n const [mobileHeight, setMobileHeight] = useState<number | null>(null);\n useEffect(() => {\n if (!isMobile) return;\n const vv = window.visualViewport;\n if (!vv) return;\n const handle = () => {\n const el = containerRef.current;\n if (!el) return;\n // Calculate available height = viewport height - element's top offset from viewport\n const top = el.getBoundingClientRect().top;\n setMobileHeight(vv.height - Math.max(0, top));\n };\n vv.addEventListener(\"resize\", handle);\n vv.addEventListener(\"scroll\", handle);\n return () => {\n vv.removeEventListener(\"resize\", handle);\n vv.removeEventListener(\"scroll\", handle);\n };\n }, [isMobile]);\n\n // CodeLens: inline Run buttons between SQL statements\n const codeLensDisposable = useRef<MonacoType.IDisposable[]>([]);\n const runSqlRef = useRef(runSqlInViewer);\n runSqlRef.current = runSqlInViewer;\n\n // Cleanup CodeLens providers on unmount to prevent duplicate \"Run\" buttons\n useEffect(() => {\n return () => {\n codeLensDisposable.current.forEach((d) => d.dispose());\n codeLensDisposable.current = [];\n };\n }, []);\n\n // Redirect .db files to sqlite viewer by changing tab type\n useEffect(() => {\n if (isSqlite && tabId) updateTab(tabId, { type: \"sqlite\" });\n }, [isSqlite, tabId, updateTab]);\n\n // Detect external (absolute) file path — not relative to project\n const isExternalFile = filePath ? /^(\\/|[A-Za-z]:[/\\\\])/.test(filePath) : false;\n\n // Load file content\n useEffect(() => {\n if (inlineContent != null) { setLoading(false); return; }\n if (isUntitled) {\n setContent(savedContent ?? \"\");\n latestContentRef.current = savedContent ?? \"\";\n setLoading(false);\n if (savedContent) setUnsaved(true);\n return;\n }\n if (!filePath) return;\n if (!isExternalFile && !projectName) return;\n if (isImage || isPdf || isVideo || isAudio) { setLoading(false); return; }\n\n setLoading(true);\n setError(null);\n\n const readUrl = isExternalFile\n ? `/api/fs/read?path=${encodeURIComponent(filePath)}`\n : `${projectUrl(projectName!)}/files/read?path=${encodeURIComponent(filePath)}`;\n\n api\n .get<{ content: string; encoding?: string }>(readUrl)\n .then((data) => {\n setContent(data.content);\n if (data.encoding) setEncoding(data.encoding);\n latestContentRef.current = data.content;\n setLoading(false);\n })\n .catch((err) => {\n setError(err instanceof Error ? err.message : \"Failed to load file\");\n setLoading(false);\n });\n\n return () => { if (saveTimerRef.current) clearTimeout(saveTimerRef.current); };\n }, [filePath, projectName, isImage, isPdf, isExternalFile, isUntitled]);\n\n // Real-time reload: listen for file:changed WS events, re-fetch if editor is clean\n const unsavedRef = useRef(unsaved);\n unsavedRef.current = unsaved;\n useEffect(() => {\n if (!filePath || !projectName || inlineContent != null || isUntitled) return;\n const handler = (e: Event) => {\n const detail = (e as CustomEvent).detail;\n if (detail.projectName !== projectName || detail.path !== filePath) return;\n if (unsavedRef.current) return; // don't overwrite unsaved changes\n const readUrl = isExternalFile\n ? `/api/fs/read?path=${encodeURIComponent(filePath)}`\n : `${projectUrl(projectName)}/files/read?path=${encodeURIComponent(filePath)}`;\n api.get<{ content: string; encoding?: string }>(readUrl).then((data) => {\n if (data.content === latestContentRef.current) return; // skip if unchanged (e.g. self-save)\n setContent(data.content);\n latestContentRef.current = data.content;\n if (data.encoding) setEncoding(data.encoding);\n }).catch(() => {});\n };\n window.addEventListener(\"file:changed\", handler);\n return () => window.removeEventListener(\"file:changed\", handler);\n }, [filePath, projectName, isExternalFile, inlineContent, isUntitled]);\n\n // Update tab title unsaved indicator (skip for inline content — title set by caller)\n useEffect(() => {\n if (!ownTab || inlineContent != null) return;\n const baseName = isUntitled\n ? `Untitled-${metadata?.untitledNumber ?? 1}`\n : (filePath ? basename(filePath) : \"Untitled\");\n const newTitle = unsaved ? `${baseName} \\u25CF` : baseName;\n if (ownTab.title !== newTitle) updateTab(ownTab.id, { title: newTitle });\n }, [unsaved]); // eslint-disable-line react-hooks/exhaustive-deps\n\n const saveFile = useCallback(\n async (text: string) => {\n if (!filePath) return;\n if (!isExternalFile && !projectName) return;\n try {\n if (isExternalFile) {\n await api.put(\"/api/fs/write\", { path: filePath, content: text });\n } else {\n await api.put(`${projectUrl(projectName!)}/files/write`, { path: filePath, content: text });\n }\n setUnsaved(false);\n } catch { /* Silent — unsaved indicator persists */ }\n },\n [filePath, projectName, isExternalFile],\n );\n\n function handleChange(value: string | undefined) {\n const val = value ?? \"\";\n setContent(val);\n latestContentRef.current = val;\n setUnsaved(true);\n if (saveTimerRef.current) clearTimeout(saveTimerRef.current);\n if (isUntitled) {\n // Persist to metadata for localStorage survival\n saveTimerRef.current = setTimeout(() => {\n if (tabId) updateTab(tabId, { metadata: { ...metadata, unsavedContent: latestContentRef.current } });\n }, 2000);\n } else {\n saveTimerRef.current = setTimeout(() => saveFile(latestContentRef.current), 1000);\n }\n }\n\n // Save As completion — transitions untitled → saved file\n const handleSaveAs = useCallback(async (targetPath: string, savedText: string) => {\n try {\n // Clear any pending metadata persistence timer to prevent race condition\n if (saveTimerRef.current) clearTimeout(saveTimerRef.current);\n await api.put(\"/api/fs/write\", { path: targetPath, content: savedText });\n if (tabId) {\n // Close old untitled tab and open as proper file tab\n const { closeTab, openTab } = usePanelStore.getState();\n closeTab(tabId);\n openTab({\n type: \"editor\",\n title: basename(targetPath),\n projectId: null,\n metadata: { filePath: targetPath },\n closable: true,\n });\n }\n setUnsaved(false);\n setShowSaveAs(false);\n } catch { /* silent — user can retry */ }\n }, [tabId]);\n\n // Jump to line when metadata.lineNumber is set (e.g. from search panel)\n const lineNumber = metadata?.lineNumber as number | undefined;\n const handleEditorMount: OnMount = useCallback((editor, monaco) => {\n editorRef.current = editor;\n monacoInstanceRef.current = monaco;\n if (lineNumber && lineNumber > 0) {\n setTimeout(() => {\n editor.revealLineInCenter(lineNumber);\n editor.setPosition({ lineNumber, column: 1 });\n editor.focus();\n }, 100);\n }\n // Ctrl+S → Save As for untitled tabs\n if (isUntitled) {\n editor.addCommand(\n monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS,\n () => setShowSaveAs(true),\n );\n }\n editor.addCommand(\n monaco.KeyMod.Alt | monaco.KeyCode.KeyZ,\n () => useSettingsStore.getState().toggleWordWrap(),\n );\n monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({\n noSemanticValidation: true, noSyntaxValidation: true, noSuggestionDiagnostics: true,\n });\n monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({\n noSemanticValidation: true, noSyntaxValidation: true, noSuggestionDiagnostics: true,\n });\n // Register SQL completion if schema available\n if (sqlSchemaInfo) {\n completionDisposable.current?.dispose();\n completionDisposable.current = monaco.languages.registerCompletionItemProvider(\n \"sql\", createSqlCompletionProvider(monaco, sqlSchemaInfo),\n );\n }\n\n // Register CodeLens for inline Run buttons on .sql files (scoped to this editor's model)\n if (isSql) {\n codeLensDisposable.current.forEach((d) => d.dispose());\n codeLensDisposable.current = [];\n\n const thisModel = editor.getModel();\n const cmdId = editor.addCommand(0, (_accessor: unknown, sql: string) => {\n if (sql) runSqlRef.current(sql);\n });\n\n if (cmdId && thisModel) {\n const provider = monaco.languages.registerCodeLensProvider(\"sql\", {\n provideCodeLenses: (model: MonacoType.editor.ITextModel) => {\n // Only provide lenses for THIS editor's model, not all SQL models\n if (model !== thisModel) return { lenses: [], dispose: () => {} };\n\n const lenses: MonacoType.languages.CodeLens[] = [];\n const text = model.getValue();\n const lines = text.split(\"\\n\");\n let stmtStartLine = -1;\n let stmtLines: string[] = [];\n let dollarBlock = false; // Track DO $$ ... $$ blocks\n\n const addLens = (line: number, stmt: string) => {\n const trimmed = stmt.trim();\n if (!trimmed || trimmed.startsWith(\"--\")) return;\n lenses.push({\n range: { startLineNumber: line, startColumn: 1, endLineNumber: line, endColumn: 1 },\n command: { id: cmdId, title: \"\\u25B7 Run\", arguments: [trimmed] },\n });\n };\n\n for (let i = 0; i < lines.length; i++) {\n const trimmed = lines[i]!.trim();\n if (stmtStartLine === -1) {\n if (!trimmed || trimmed.startsWith(\"--\")) continue;\n stmtStartLine = i + 1;\n stmtLines = [];\n }\n stmtLines.push(lines[i]!);\n\n // Detect $$ dollar-quoted block start/end\n const dollarMatches = (trimmed.match(/\\$\\$/g) || []).length;\n if (dollarMatches % 2 === 1) dollarBlock = !dollarBlock;\n\n // Only split on ; when NOT inside a $$ block\n if (!dollarBlock && trimmed.endsWith(\";\")) {\n addLens(stmtStartLine, stmtLines.join(\"\\n\"));\n stmtStartLine = -1;\n stmtLines = [];\n }\n }\n if (stmtStartLine > 0 && stmtLines.join(\"\").trim()) {\n addLens(stmtStartLine, stmtLines.join(\"\\n\"));\n }\n return { lenses, dispose: () => {} };\n },\n });\n codeLensDisposable.current.push(provider);\n }\n }\n }, [sqlSchemaInfo]); // eslint-disable-line react-hooks/exhaustive-deps\n\n if (!inlineContent && !isUntitled && (!filePath || (!isExternalFile && !projectName))) {\n return (\n <div className=\"flex items-center justify-center h-full text-text-secondary text-sm\">\n No file selected.\n </div>\n );\n }\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center h-full gap-2 text-text-secondary\">\n <Loader2 className=\"size-5 animate-spin\" />\n <span className=\"text-sm\">Loading file...</span>\n </div>\n );\n }\n\n if (error) {\n return (\n <div className=\"flex items-center justify-center h-full text-error text-sm\">{error}</div>\n );\n }\n\n if (isImage) return <Suspense fallback={<LoadingSpinner />}><ImagePreview filePath={filePath!} projectName={projectName!} /></Suspense>;\n if (isPdf) return <Suspense fallback={<LoadingSpinner />}><PdfPreview filePath={filePath!} projectName={projectName!} /></Suspense>;\n if (isVideo) return <Suspense fallback={<LoadingSpinner />}><VideoPreview filePath={filePath!} projectName={projectName!} /></Suspense>;\n if (isAudio) return <Suspense fallback={<LoadingSpinner />}><AudioPreview filePath={filePath!} projectName={projectName!} /></Suspense>;\n\n if (encoding === \"base64\") {\n return (\n <div className=\"flex flex-col items-center justify-center h-full gap-3 text-text-secondary\">\n <FileWarning className=\"size-10 text-text-subtle\" />\n <p className=\"text-sm\">This file is a binary format and cannot be displayed.</p>\n <p className=\"text-xs text-text-subtle\">{filePath}</p>\n </div>\n );\n }\n\n /** SQL connection picker bar (shared between breadcrumb and standalone) */\n const sqlPickerBar = isSql ? (\n <div className=\"shrink-0 flex items-center gap-1 px-2 border-l border-border\">\n <Database className=\"size-3 text-muted-foreground\" />\n <select\n value={sqlConnId ?? \"\"}\n onChange={(e) => { const v = Number(e.target.value); if (v) handleSqlConnChange(v); }}\n className=\"h-5 text-[10px] bg-transparent border border-border rounded px-1 text-foreground outline-none max-w-[140px]\"\n title=\"Select connection for autocomplete\"\n >\n <option value=\"\">Connection…</option>\n {connections.map((c) => <option key={c.id} value={c.id}>{c.name}</option>)}\n </select>\n <button\n type=\"button\"\n onClick={handleRunInDbViewer}\n disabled={!selectedSqlConn}\n className=\"p-0.5 rounded text-muted-foreground hover:text-primary disabled:opacity-30 transition-colors\"\n title=\"Run SQL\"\n >\n <Play className=\"size-3.5\" />\n </button>\n </div>\n ) : null;\n\n return (\n <div\n ref={containerRef}\n className=\"flex flex-col h-full w-full overflow-hidden\"\n style={mobileHeight ? { height: `${mobileHeight}px`, maxHeight: `${mobileHeight}px` } : undefined}\n >\n {/* Inline content toolbar (cell viewer mode) */}\n {inlineContent != null && canBeautifyInline && (\n <div className=\"flex items-center h-7 border-b border-border bg-background shrink-0 px-2 gap-2\">\n <button type=\"button\" onClick={handleBeautifyInline}\n className=\"text-[10px] px-2 py-0.5 rounded border border-border hover:bg-muted transition-colors text-foreground\">\n {isBeautified ? \"Raw\" : \"Beautify\"}\n </button>\n </div>\n )}\n {/* Breadcrumb + Toolbar bar — desktop only */}\n {filePath && projectName && tabId && (\n <div className=\"hidden md:flex items-center h-7 border-b border-border bg-background shrink-0\">\n <EditorBreadcrumb\n filePath={filePath}\n projectName={projectName}\n tabId={tabId}\n className=\"flex items-center flex-1 min-w-0 overflow-x-auto scrollbar-none px-2 gap-0.5\"\n />\n {sqlPickerBar}\n <EditorToolbar\n ext={ext}\n mdMode={mdMode}\n onMdModeChange={setMdMode}\n csvMode={csvMode}\n onCsvModeChange={setCsvMode}\n wordWrap={wordWrap}\n onToggleWordWrap={toggleWordWrap}\n filePath={filePath}\n projectName={projectName}\n className=\"shrink-0 flex items-center gap-1 px-2\"\n />\n </div>\n )}\n\n {/* Standalone SQL toolbar for external files (no breadcrumb available) */}\n {isSql && (!projectName || !tabId) && (\n <div className=\"hidden md:flex items-center h-7 border-b border-border bg-background shrink-0 px-2\">\n <span className=\"text-xs text-muted-foreground truncate flex-1\">{filePath ? basename(filePath) : \"SQL\"}</span>\n {sqlPickerBar}\n </div>\n )}\n\n {/* Content area */}\n {isCsv && csvMode === \"table\" ? (\n <Suspense fallback={<div className=\"flex items-center justify-center h-full\"><Loader2 className=\"size-5 animate-spin text-text-subtle\" /></div>}>\n <CsvPreview content={content ?? \"\"} onContentChange={handleChange} wordWrap={wordWrap} />\n </Suspense>\n ) : isMarkdown && mdMode === \"preview\" ? (\n <MarkdownPreview content={content ?? \"\"} />\n ) : (\n <div className=\"flex-1 overflow-hidden min-h-0\">\n <Editor\n height=\"100%\"\n language={inlineLanguage ?? getMonacoLanguage(filePath ?? \"\")}\n value={content ?? \"\"}\n onChange={inlineContent != null ? undefined : handleChange}\n onMount={handleEditorMount}\n theme={monacoTheme}\n options={{\n fontSize: 13,\n fontFamily: \"Menlo, Monaco, Consolas, monospace\",\n wordWrap: wordWrap ? \"on\" : \"off\",\n minimap: { enabled: false },\n scrollBeyondLastLine: false,\n automaticLayout: true,\n lineNumbers: \"on\",\n folding: true,\n bracketPairColorization: { enabled: true },\n readOnly: inlineContent != null,\n }}\n loading={<Loader2 className=\"size-5 animate-spin text-text-subtle\" />}\n />\n </div>\n )}\n\n {/* Inline SQL result panel */}\n {isSql && (sqlResult || sqlError || sqlLoading) && (\n <SqlResultPanel\n result={sqlResult} error={sqlError} loading={sqlLoading}\n connName={selectedSqlConn?.name}\n onClose={() => { setSqlResult(null); setSqlError(null); setSqlLoading(false); }}\n onOpenInTab={openSqlResultInTab}\n />\n )}\n\n {/* Mobile toolbar — bottom, like terminal */}\n {isMobile && <EditorMobileToolbar editorRef={editorRef} readOnly={inlineContent != null} />}\n\n {/* Save As dialog for untitled tabs */}\n {showSaveAs && (\n <SaveAsDialog\n open={showSaveAs}\n defaultName={`Untitled-${metadata?.untitledNumber ?? 1}`}\n content={latestContentRef.current}\n onSave={handleSaveAs}\n onCancel={() => setShowSaveAs(false)}\n />\n )}\n </div>\n );\n});\n\nconst NOOP = () => {};\n\n/** Inline SQL result panel — shows query results below the editor */\nfunction SqlResultPanel({ result, error, loading, connName, onClose, onOpenInTab }: {\n result: DbQueryResult | null;\n error: string | null;\n loading: boolean;\n connName?: string;\n onClose: () => void;\n onOpenInTab: () => void;\n}) {\n const tableData = 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 const querySchema = useMemo<GridColumnSchema[]>(() => (\n (result?.columns ?? []).map((c) => ({ name: c, type: \"text\", nullable: true, pk: false, defaultValue: null }))\n ), [result?.columns]);\n\n const [panelHeight, setPanelHeight] = useState(250);\n const handleDrag = useCallback((e: React.MouseEvent) => {\n e.preventDefault();\n const startY = e.clientY;\n const startH = panelHeight;\n const onMove = (ev: MouseEvent) => setPanelHeight(Math.max(80, startH + (startY - ev.clientY)));\n const onUp = () => { document.removeEventListener(\"mousemove\", onMove); document.removeEventListener(\"mouseup\", onUp); };\n document.addEventListener(\"mousemove\", onMove);\n document.addEventListener(\"mouseup\", onUp);\n }, [panelHeight]);\n\n return (\n <div className=\"shrink-0 border-t border-border flex flex-col\" style={{ height: panelHeight }}>\n {/* Resize handle */}\n <div onMouseDown={handleDrag}\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 {/* Title bar */}\n <div className=\"flex items-center gap-2 px-2 py-1 bg-muted/50 border-b border-border shrink-0\">\n <Database className=\"size-3 text-muted-foreground\" />\n <span className=\"text-xs font-medium text-foreground truncate flex-1\">\n {connName ? `${connName} · Results` : \"Query Results\"}\n {result?.executionTimeMs != null && <span className=\"text-muted-foreground ml-1.5 font-normal\">{result.executionTimeMs}ms</span>}\n </span>\n <button type=\"button\" onClick={onOpenInTab} title=\"Open in DB Viewer tab\"\n className=\"flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] text-muted-foreground hover:text-foreground hover:bg-muted transition-colors\">\n <ExternalLink className=\"size-3\" />\n <span className=\"hidden sm:inline\">Open in Tab</span>\n </button>\n <button type=\"button\" onClick={onClose} title=\"Close results\"\n className=\"p-0.5 rounded text-muted-foreground hover:text-foreground transition-colors\">\n <X className=\"size-3\" />\n </button>\n </div>\n\n {/* Content */}\n <div className=\"flex-1 overflow-hidden min-h-0\">\n {loading && (\n <div className=\"flex items-center justify-center h-full\">\n <Loader2 className=\"size-4 animate-spin text-muted-foreground\" />\n </div>\n )}\n {error && <div className=\"px-3 py-2 text-xs text-destructive bg-destructive/5\">{error}</div>}\n {result?.changeType === \"modify\" && (\n <div className=\"px-3 py-2 text-xs text-green-500\">\n {result.rowsAffected} row(s) affected\n </div>\n )}\n {tableData && (\n <GlideDataGrid\n columns={tableData.columns} rows={tableData.rows} total={tableData.total} limit={tableData.limit}\n schema={querySchema} loading={false}\n page={1} onPageChange={NOOP} onCellUpdate={NOOP}\n orderBy={null} orderDir=\"ASC\" onToggleSort={NOOP}\n connectionName={connName}\n />\n )}\n {result?.changeType === \"select\" && result.rows.length === 0 && (\n <div className=\"px-3 py-2 text-xs text-muted-foreground\">No results</div>\n )}\n </div>\n </div>\n );\n}\n\nfunction LoadingSpinner() {\n return <div className=\"flex items-center justify-center h-full\"><Loader2 className=\"size-5 animate-spin text-text-subtle\" /></div>;\n}\n\nfunction MarkdownPreview({ content }: { content: string }) {\n return (\n <Suspense fallback={<div className=\"animate-pulse h-4 bg-muted rounded m-4\" />}>\n <MarkdownRenderer content={content} className=\"flex-1 overflow-auto p-4\" />\n </Suspense>\n );\n}\n\n"],"file":"code-editor-DEa0t62y.js"}
|
|
1
|
+
{"version":3,"mappings":"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","names":[],"ignoreList":[0],"sources":["../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/redo-2.js","../../../src/web/components/editor/editor-breadcrumb.tsx","../../../src/web/components/editor/editor-toolbar.tsx","../../../src/web/components/editor/save-as-dialog.tsx","../../../src/web/components/editor/editor-mobile-toolbar.tsx","../../../src/web/components/editor/code-editor.tsx"],"sourcesContent":["/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"m15 14 5-5-5-5\", key: \"12vg1m\" }],\n [\"path\", { d: \"M20 9H9.5A5.5 5.5 0 0 0 4 14.5A5.5 5.5 0 0 0 9.5 20H13\", key: \"6uklza\" }]\n];\nconst Redo2 = createLucideIcon(\"redo-2\", __iconNode);\n\nexport { __iconNode, Redo2 as default };\n//# sourceMappingURL=redo-2.js.map\n","import { useMemo, useRef, useEffect } from \"react\";\nimport { ChevronRight, Folder, File, FileCode, FileJson, FileText, FileType } from \"lucide-react\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n DropdownMenuSub,\n DropdownMenuSubTrigger,\n DropdownMenuSubContent,\n} from \"@/components/ui/dropdown-menu\";\nimport { useFileStore, type FileNode } from \"@/stores/file-store\";\nimport { useShallow } from \"zustand/react/shallow\";\nimport { useTabStore } from \"@/stores/tab-store\";\nimport { useProjectStore } from \"@/stores/project-store\";\nimport { basename } from \"@/lib/utils\";\n\nconst ICON_MAP: Record<string, React.ComponentType<{ className?: string }>> = {\n ts: FileCode, tsx: FileCode, js: FileCode, jsx: FileCode,\n py: FileCode, rs: FileCode, go: FileCode, html: FileCode,\n css: FileCode, scss: FileCode,\n json: FileJson,\n md: FileText, txt: FileText,\n yaml: FileType, yml: FileType,\n};\n\nfunction getIcon(name: string, isDir: boolean) {\n if (isDir) return Folder;\n const ext = name.split(\".\").pop()?.toLowerCase() ?? \"\";\n return ICON_MAP[ext] ?? File;\n}\n\ninterface BreadcrumbSegment {\n name: string;\n fullPath: string;\n node: FileNode | null;\n siblings: FileNode[];\n /** Folder path whose children are the siblings (empty string = root) */\n parentPath: string;\n}\n\nfunction walkTree(tree: FileNode[], segments: string[]): BreadcrumbSegment[] {\n const result: BreadcrumbSegment[] = [];\n let current: FileNode[] = tree;\n let parentPath = \"\";\n\n for (let i = 0; i < segments.length; i++) {\n const seg = segments[i]!;\n const fullPath = segments.slice(0, i + 1).join(\"/\");\n const match = current.find((n) => n.name === seg);\n result.push({\n name: seg,\n fullPath,\n node: match ?? null,\n siblings: current,\n parentPath,\n });\n if (match?.children) {\n parentPath = match.path;\n current = match.children;\n } else {\n // Remaining segments — parent children not loaded yet\n for (let j = i + 1; j < segments.length; j++) {\n result.push({\n name: segments[j]!,\n fullPath: segments.slice(0, j + 1).join(\"/\"),\n node: null,\n siblings: [],\n parentPath: segments.slice(0, j).join(\"/\"),\n });\n }\n break;\n }\n }\n return result;\n}\n\nfunction sortNodes(nodes: FileNode[]): FileNode[] {\n return [...nodes].sort((a, b) => {\n if (a.type !== b.type) return a.type === \"directory\" ? -1 : 1;\n return a.name.localeCompare(b.name);\n });\n}\n\ninterface EditorBreadcrumbProps {\n filePath: string;\n projectName: string;\n tabId: string;\n className?: string;\n}\n\nexport function EditorBreadcrumb({ filePath, projectName, tabId, className }: EditorBreadcrumbProps) {\n const tree = useFileStore((s) => s.tree);\n const { updateTab, openTab } = useTabStore(useShallow((s) => ({ updateTab: s.updateTab, openTab: s.openTab })));\n const projectPath = useProjectStore((s) => s.projects.find((p) => p.name === projectName)?.path ?? \"\");\n const scrollRef = useRef<HTMLDivElement>(null);\n\n // Strip project root prefix so segments align with the relative-path file tree\n const { prefixParts, relativePath } = useMemo(() => {\n const norm = filePath.startsWith(\"/\") ? filePath.slice(1) : filePath;\n const normRoot = projectPath.startsWith(\"/\") ? projectPath.slice(1) : projectPath;\n if (normRoot && norm.startsWith(normRoot + \"/\")) {\n const rel = norm.slice(normRoot.length + 1);\n return { prefixParts: normRoot.split(\"/\"), relativePath: rel };\n }\n return { prefixParts: [] as string[], relativePath: norm };\n }, [filePath, projectPath]);\n\n const segments = useMemo(\n () => walkTree(tree, relativePath.split(\"/\").filter(Boolean)),\n [tree, relativePath],\n );\n\n // Auto-scroll to rightmost segment\n useEffect(() => {\n if (scrollRef.current) {\n scrollRef.current.scrollLeft = scrollRef.current.scrollWidth;\n }\n }, [segments]);\n\n function handleFileClick(path: string, e: React.MouseEvent) {\n const name = basename(path);\n if (e.metaKey || e.ctrlKey) {\n openTab({ type: \"editor\", title: name, metadata: { filePath: path, projectName }, projectId: projectName, closable: true });\n } else {\n updateTab(tabId, { title: name, metadata: { filePath: path, projectName } });\n }\n }\n\n return (\n <div ref={scrollRef} className={className}>\n {prefixParts.map((part, i) => (\n <div key={`prefix-${i}`} className=\"flex items-center shrink-0\">\n {i > 0 && <ChevronRight className=\"size-3 text-muted-foreground shrink-0 mx-0.5\" />}\n <span className=\"text-xs text-muted-foreground px-1 py-0.5\">{part}</span>\n </div>\n ))}\n {segments.map((seg, i) => (\n <div key={seg.fullPath} className=\"flex items-center shrink-0\">\n {(i > 0 || prefixParts.length > 0) && <ChevronRight className=\"size-3 text-muted-foreground shrink-0 mx-0.5\" />}\n <SegmentDropdown\n segment={seg}\n isLast={i === segments.length - 1}\n projectName={projectName}\n onFileClick={handleFileClick}\n />\n </div>\n ))}\n </div>\n );\n}\n\ninterface SegmentDropdownProps {\n segment: BreadcrumbSegment;\n isLast: boolean;\n projectName: string;\n onFileClick: (path: string, e: React.MouseEvent) => void;\n}\n\nfunction SegmentDropdown({ segment, isLast, projectName, onFileClick }: SegmentDropdownProps) {\n const loadChildren = useFileStore((s) => s.loadChildren);\n const loadedPaths = useFileStore((s) => s.loadedPaths);\n const sorted = useMemo(() => sortNodes(segment.siblings), [segment.siblings]);\n const isLoaded = loadedPaths.has(segment.parentPath);\n\n function handleOpenChange(open: boolean) {\n if (open && !isLoaded) {\n loadChildren(projectName, segment.parentPath);\n }\n }\n\n return (\n <DropdownMenu onOpenChange={handleOpenChange}>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n className={`text-xs px-1 py-0.5 rounded hover:bg-muted transition-colors truncate max-w-[120px] ${\n isLast ? \"text-foreground font-medium\" : \"text-muted-foreground\"\n }`}\n >\n {segment.name}\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"max-h-[300px] p-1\">\n {sorted.length === 0 ? (\n <DropdownMenuItem disabled className=\"text-xs text-muted-foreground\">\n Loading…\n </DropdownMenuItem>\n ) : (\n sorted.map((node) => (\n <NodeMenuItem\n key={node.path}\n node={node}\n projectName={projectName}\n activePath={segment.fullPath}\n onFileClick={onFileClick}\n />\n ))\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\ninterface NodeMenuItemProps {\n node: FileNode;\n projectName: string;\n activePath: string;\n onFileClick: (path: string, e: React.MouseEvent) => void;\n}\n\nfunction NodeMenuItem({ node, projectName, activePath, onFileClick }: NodeMenuItemProps) {\n const Icon = getIcon(node.name, node.type === \"directory\");\n const isActive = node.path === activePath;\n const loadChildren = useFileStore((s) => s.loadChildren);\n const loadedPaths = useFileStore((s) => s.loadedPaths);\n\n if (node.type === \"directory\") {\n const children = node.children ?? [];\n const isLoaded = loadedPaths.has(node.path);\n\n function handleSubOpen(open: boolean) {\n if (open && !isLoaded) {\n loadChildren(projectName, node.path);\n }\n }\n\n return (\n <DropdownMenuSub onOpenChange={handleSubOpen}>\n <DropdownMenuSubTrigger className={`text-xs gap-1.5 ${isActive ? \"bg-muted\" : \"\"}`}>\n <Icon className=\"size-3.5 shrink-0 text-muted-foreground\" />\n <span className=\"truncate\">{node.name}</span>\n </DropdownMenuSubTrigger>\n <DropdownMenuSubContent className=\"max-h-[300px] overflow-y-auto p-1\">\n {children.length === 0 ? (\n <DropdownMenuItem disabled className=\"text-xs text-muted-foreground\">\n Loading…\n </DropdownMenuItem>\n ) : (\n sortNodes(children).map((child) => (\n <NodeMenuItem\n key={child.path}\n node={child}\n projectName={projectName}\n activePath={activePath}\n onFileClick={onFileClick}\n />\n ))\n )}\n </DropdownMenuSubContent>\n </DropdownMenuSub>\n );\n }\n\n return (\n <DropdownMenuItem\n className={`text-xs gap-1.5 cursor-pointer ${isActive ? \"bg-muted\" : \"\"}`}\n onSelect={(e) => {\n // onSelect doesn't give MouseEvent, use click handler for Ctrl detection\n }}\n onClick={(e) => {\n onFileClick(node.path, e);\n }}\n >\n <Icon className=\"size-3.5 shrink-0 text-muted-foreground\" />\n <span className=\"truncate\">{node.name}</span>\n </DropdownMenuItem>\n );\n}\n","import { Code, Eye, WrapText, Table, Download } from \"lucide-react\";\nimport { downloadFile } from \"@/lib/file-download\";\n\ninterface EditorToolbarProps {\n ext: string;\n mdMode?: \"edit\" | \"preview\";\n onMdModeChange?: (mode: \"edit\" | \"preview\") => void;\n csvMode?: \"table\" | \"raw\";\n onCsvModeChange?: (mode: \"table\" | \"raw\") => void;\n wordWrap: boolean;\n onToggleWordWrap: () => void;\n filePath?: string;\n projectName?: string;\n className?: string;\n}\n\nfunction ToolbarButton({\n active,\n onClick,\n icon: Icon,\n label,\n}: {\n active: boolean;\n onClick: () => void;\n icon: React.ComponentType<{ className?: string }>;\n label: string;\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n className={`flex items-center gap-1 px-2 py-1 rounded text-xs transition-colors ${\n active ? \"bg-muted text-foreground\" : \"text-muted-foreground hover:text-foreground\"\n }`}\n >\n <Icon className=\"size-3\" />\n <span className=\"hidden sm:inline\">{label}</span>\n </button>\n );\n}\n\nexport function EditorToolbar({\n ext,\n mdMode,\n onMdModeChange,\n csvMode,\n onCsvModeChange,\n wordWrap,\n onToggleWordWrap,\n filePath,\n projectName,\n className,\n}: EditorToolbarProps) {\n const isMarkdown = ext === \"md\" || ext === \"mdx\";\n const isCsv = ext === \"csv\";\n\n return (\n <div className={className}>\n {isMarkdown && onMdModeChange && (\n <>\n <ToolbarButton active={mdMode === \"edit\"} onClick={() => onMdModeChange(\"edit\")} icon={Code} label=\"Edit\" />\n <ToolbarButton active={mdMode === \"preview\"} onClick={() => onMdModeChange(\"preview\")} icon={Eye} label=\"Preview\" />\n </>\n )}\n {isCsv && onCsvModeChange && (\n <>\n <ToolbarButton active={csvMode === \"table\"} onClick={() => onCsvModeChange(\"table\")} icon={Table} label=\"Table\" />\n <ToolbarButton active={csvMode === \"raw\"} onClick={() => onCsvModeChange(\"raw\")} icon={Code} label=\"Raw\" />\n </>\n )}\n <ToolbarButton\n active={wordWrap}\n onClick={onToggleWordWrap}\n icon={WrapText}\n label=\"Wrap\"\n />\n {filePath && projectName && (\n <ToolbarButton\n active={false}\n onClick={() => downloadFile(projectName, filePath)}\n icon={Download}\n label=\"Download\"\n />\n )}\n </div>\n );\n}\n","import { useState, useCallback } from \"react\";\nimport {\n Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter,\n} from \"@/components/ui/dialog\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { FileBrowserPicker } from \"@/components/ui/file-browser-picker\";\nimport { useProjectStore } from \"@/stores/project-store\";\n\ninterface SaveAsDialogProps {\n open: boolean;\n defaultName: string;\n content: string;\n onSave: (fullPath: string, content: string) => void;\n onCancel: () => void;\n}\n\nexport function SaveAsDialog({ open, defaultName, content, onSave, onCancel }: SaveAsDialogProps) {\n const [filename, setFilename] = useState(defaultName);\n const [showPicker, setShowPicker] = useState(false);\n const [error, setError] = useState(\"\");\n const activeProject = useProjectStore((s) => s.activeProject);\n\n const validateAndProceed = useCallback(() => {\n const trimmed = filename.trim();\n if (!trimmed) { setError(\"Filename cannot be empty\"); return; }\n if (/[/\\\\]/.test(trimmed)) { setError(\"Filename cannot contain / or \\\\\"); return; }\n setError(\"\");\n setShowPicker(true);\n }, [filename]);\n\n const handleFolderSelect = useCallback((dirPath: string) => {\n const sep = dirPath.includes(\"\\\\\") ? \"\\\\\" : \"/\";\n const fullPath = dirPath.endsWith(sep) ? `${dirPath}${filename.trim()}` : `${dirPath}${sep}${filename.trim()}`;\n onSave(fullPath, content);\n }, [filename, content, onSave]);\n\n if (showPicker) {\n return (\n <FileBrowserPicker\n open\n mode=\"folder\"\n root={activeProject?.path}\n title={`Save \"${filename.trim()}\" to...`}\n onSelect={handleFolderSelect}\n onCancel={() => setShowPicker(false)}\n />\n );\n }\n\n return (\n <Dialog open={open} onOpenChange={(v) => { if (!v) onCancel(); }}>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>Save As</DialogTitle>\n </DialogHeader>\n <div className=\"flex flex-col gap-2 py-2\">\n <label className=\"text-sm text-muted-foreground\">Filename</label>\n <Input\n value={filename}\n onChange={(e) => { setFilename(e.target.value); setError(\"\"); }}\n onKeyDown={(e) => { if (e.key === \"Enter\") validateAndProceed(); }}\n placeholder=\"e.g. my-file.ts\"\n autoFocus\n />\n {error && <p className=\"text-xs text-destructive\">{error}</p>}\n </div>\n <DialogFooter>\n <Button variant=\"outline\" onClick={onCancel}>Cancel</Button>\n <Button onClick={validateAndProceed}>Choose Folder...</Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n","import { useCallback, useRef, useState } from \"react\";\nimport { ClipboardPaste, Undo2, Redo2, X } from \"lucide-react\";\nimport type * as MonacoType from \"monaco-editor\";\n\n/** Clipboard API requires secure context (HTTPS / localhost) */\nconst isSecureContext = typeof window !== \"undefined\" && window.isSecureContext;\n\n/** Symbols commonly needed when coding on mobile — ordered by frequency */\nconst SYMBOL_KEYS = [\n \"(\", \")\", \"{\", \"}\", \"[\", \"]\",\n \"<\", \">\", \";\", \":\", \"=\",\n '\"', \"'\", \"`\", \"/\", \"\\\\\", \"_\", \"#\",\n];\n\nconst btnBase =\n \"px-2 py-1.5 rounded text-xs min-w-[36px] min-h-[32px] bg-surface-elevated text-text-primary active:bg-primary active:text-primary-foreground transition-colors select-none\";\nconst btnSymbol =\n \"px-3 py-1.5 rounded text-xs font-mono min-w-[36px] min-h-[32px] bg-surface-elevated text-text-primary active:bg-primary active:text-primary-foreground transition-colors select-none\";\nconst divider = \"w-px h-5 bg-border mx-0.5 shrink-0\";\n\ninterface EditorMobileToolbarProps {\n editorRef: React.RefObject<MonacoType.editor.IStandaloneCodeEditor | null>;\n readOnly?: boolean;\n}\n\nexport function EditorMobileToolbar({ editorRef, readOnly }: EditorMobileToolbarProps) {\n const getEditor = useCallback(() => editorRef.current, [editorRef]);\n\n /** Insert text at cursor position in Monaco */\n const insertText = useCallback((text: string) => {\n const editor = getEditor();\n if (!editor) return;\n editor.focus();\n const selection = editor.getSelection();\n if (selection) {\n editor.executeEdits(\"mobile-toolbar\", [{ range: selection, text }]);\n }\n }, [getEditor]);\n\n // --- Paste: two strategies based on secure context ---\n const [pasteMode, setPasteMode] = useState(false);\n const pasteRef = useRef<HTMLTextAreaElement | null>(null);\n\n // HTTPS: use Clipboard API directly (single tap)\n const handleClipboardPaste = useCallback(async () => {\n try {\n const text = await navigator.clipboard.readText();\n if (text) insertText(text);\n } catch { /* permission denied */ }\n }, [insertText]);\n\n // HTTP fallback: show textarea for native long-press paste\n const openPasteMode = useCallback(() => {\n setPasteMode(true);\n requestAnimationFrame(() => pasteRef.current?.focus());\n }, []);\n\n const handleNativePaste = useCallback((e: React.ClipboardEvent<HTMLTextAreaElement>) => {\n e.preventDefault();\n const text = e.clipboardData.getData(\"text/plain\");\n if (!text) return;\n setPasteMode(false);\n insertText(text);\n }, [insertText]);\n\n const handleUndo = useCallback(() => {\n const editor = getEditor();\n if (!editor) return;\n editor.focus();\n editor.trigger(\"mobile-toolbar\", \"undo\", null);\n }, [getEditor]);\n\n const handleRedo = useCallback(() => {\n const editor = getEditor();\n if (!editor) return;\n editor.focus();\n editor.trigger(\"mobile-toolbar\", \"redo\", null);\n }, [getEditor]);\n\n const handleTab = useCallback(() => {\n const editor = getEditor();\n if (!editor) return;\n editor.focus();\n editor.trigger(\"mobile-toolbar\", \"tab\", null);\n }, [getEditor]);\n\n if (readOnly) return null;\n\n return (\n <div className=\"shrink-0 border-t border-border bg-surface\">\n {/* HTTP-only: textarea for native paste via long-press */}\n {!isSecureContext && pasteMode && (\n <div className=\"flex items-center gap-2 px-2 py-1.5 border-b border-border bg-muted/50\">\n <textarea\n ref={pasteRef}\n onPaste={handleNativePaste}\n placeholder=\"Long-press here → Paste\"\n className=\"flex-1 h-8 rounded border border-border bg-background text-foreground text-xs px-2 py-1.5 resize-none focus:outline-none focus:ring-1 focus:ring-primary\"\n />\n <button\n type=\"button\"\n onClick={() => setPasteMode(false)}\n className=\"p-1.5 rounded text-muted-foreground active:bg-muted transition-colors\"\n >\n <X size={14} />\n </button>\n </div>\n )}\n\n {/* Toolbar buttons */}\n <div className=\"flex items-center gap-1 px-2 py-1.5 overflow-x-auto\">\n {/* Paste: Clipboard API on HTTPS, textarea fallback on HTTP */}\n <button\n type=\"button\"\n onClick={isSecureContext ? handleClipboardPaste : openPasteMode}\n className={btnBase}\n title=\"Paste\"\n >\n <ClipboardPaste size={14} />\n </button>\n <button type=\"button\" onClick={handleUndo} className={btnBase} title=\"Undo\">\n <Undo2 size={14} />\n </button>\n <button type=\"button\" onClick={handleRedo} className={btnBase} title=\"Redo\">\n <Redo2 size={14} />\n </button>\n\n <div className={divider} />\n\n <button type=\"button\" onClick={handleTab} className={btnSymbol}>\n Tab\n </button>\n\n <div className={divider} />\n\n {SYMBOL_KEYS.map((key) => (\n <button key={key} type=\"button\" onClick={() => insertText(key)} className={btnSymbol}>\n {key}\n </button>\n ))}\n </div>\n </div>\n );\n}\n","import { useEffect, useState, useCallback, useRef, useMemo, memo, lazy, Suspense } from \"react\";\nimport Editor, { type OnMount } from \"@monaco-editor/react\";\nimport type * as MonacoType from \"monaco-editor\";\nimport { api, projectUrl } from \"@/lib/api-client\";\nimport { useShallow } from \"zustand/react/shallow\";\nimport { useTabStore } from \"@/stores/tab-store\";\nimport { usePanelStore } from \"@/stores/panel-store\";\nimport { useSettingsStore } from \"@/stores/settings-store\";\nimport { basename } from \"@/lib/utils\";\nimport { useMonacoTheme } from \"@/lib/use-monaco-theme\";\nimport { Loader2, FileWarning, Play, Database, ExternalLink, X, GripHorizontal } from \"lucide-react\";\nimport { EditorBreadcrumb } from \"./editor-breadcrumb\";\nimport { EditorToolbar } from \"./editor-toolbar\";\nimport { SaveAsDialog } from \"./save-as-dialog\";\nimport { EditorMobileToolbar } from \"./editor-mobile-toolbar\";\nimport { createSqlCompletionProvider, clearCompletionCache, type SchemaInfo } from \"../database/sql-completion-provider\";\nimport { useConnections, type Connection } from \"../database/use-connections\";\nimport { GlideDataGrid } from \"../database/glide-data-grid\";\nimport type { GridColumnSchema } from \"../database/glide-grid-types\";\nimport type { DbQueryResult } from \"../database/use-database\";\n\nconst MarkdownRenderer = lazy(() =>\n import(\"@/components/shared/markdown-renderer\").then((m) => ({ default: m.MarkdownRenderer }))\n);\nconst CsvPreview = lazy(() => import(\"./csv-preview\").then((m) => ({ default: m.CsvPreview })));\nconst ImagePreview = lazy(() => import(\"./image-preview\").then((m) => ({ default: m.ImagePreview })));\nconst PdfPreview = lazy(() => import(\"./pdf-preview\").then((m) => ({ default: m.PdfPreview })));\nconst VideoPreview = lazy(() => import(\"./video-preview\").then((m) => ({ default: m.VideoPreview })));\nconst AudioPreview = lazy(() => import(\"./audio-preview\").then((m) => ({ default: m.AudioPreview })));\n\n/** Image extensions renderable inline */\nconst IMAGE_EXTS = new Set([\"png\", \"jpg\", \"jpeg\", \"gif\", \"webp\", \"svg\", \"ico\"]);\n/** Video extensions playable inline */\nconst VIDEO_EXTS = new Set([\"mp4\", \"webm\", \"mov\", \"ogg\", \"avi\", \"mkv\"]);\n/** Audio extensions playable inline */\nconst AUDIO_EXTS = new Set([\"mp3\", \"wav\", \"flac\", \"aac\", \"m4a\", \"wma\"]);\n/** SQLite extensions — redirect to sqlite viewer */\nconst SQLITE_EXTS = new Set([\"db\", \"sqlite\", \"sqlite3\"]);\n\nfunction getFileExt(filename: string): string {\n return filename.split(\".\").pop()?.toLowerCase() ?? \"\";\n}\n\nfunction getMonacoLanguage(filename: string): string {\n const ext = getFileExt(filename);\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 sql: \"sql\",\n };\n return map[ext] ?? \"plaintext\";\n}\n\ninterface CodeEditorProps {\n metadata?: Record<string, unknown>;\n tabId?: string;\n}\n\nexport const CodeEditor = memo(function CodeEditor({ metadata, tabId }: CodeEditorProps) {\n const filePath = metadata?.filePath as string | undefined;\n const projectName = metadata?.projectName as string | undefined;\n // Inline content mode: read-only Monaco with pre-loaded content (e.g. cell viewer)\n const inlineContent = metadata?.inlineContent as string | undefined;\n const inlineLanguage = metadata?.inlineLanguage as string | undefined;\n const [content, setContent] = useState<string | null>(inlineContent ?? null);\n const [encoding, setEncoding] = useState<string>(\"utf-8\");\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [unsaved, setUnsaved] = useState(false);\n const saveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const latestContentRef = useRef<string>(\"\");\n const editorRef = useRef<MonacoType.editor.IStandaloneCodeEditor | null>(null);\n const { tabs, updateTab } = useTabStore(useShallow((s) => ({ tabs: s.tabs, updateTab: s.updateTab })));\n const { wordWrap, toggleWordWrap } = useSettingsStore(useShallow((s) => ({ wordWrap: s.wordWrap, toggleWordWrap: s.toggleWordWrap })));\n const monacoTheme = useMonacoTheme();\n\n const isUntitled = metadata?.isUntitled === true;\n const savedContent = metadata?.unsavedContent as string | undefined;\n const [showSaveAs, setShowSaveAs] = useState(false);\n\n const ownTab = tabs.find((t) => t.id === tabId);\n const ext = filePath ? getFileExt(filePath) : \"\";\n const isImage = IMAGE_EXTS.has(ext);\n const isPdf = ext === \"pdf\";\n const isVideo = VIDEO_EXTS.has(ext);\n const isAudio = AUDIO_EXTS.has(ext);\n const isSqlite = SQLITE_EXTS.has(ext);\n const isMarkdown = ext === \"md\" || ext === \"mdx\";\n const isCsv = ext === \"csv\";\n const isSql = ext === \"sql\";\n const [mdMode, setMdMode] = useState<\"edit\" | \"preview\">(\"preview\");\n const [csvMode, setCsvMode] = useState<\"table\" | \"raw\">(\"table\");\n\n // SQL file: connection picker + autocomplete + run in DB viewer\n const { connections, cachedTables, refreshTables } = useConnections();\n const [sqlConnId, setSqlConnId] = useState<number | null>(() => {\n if (!isSql || !filePath) return null;\n const stored = localStorage.getItem(`ppm:sql-conn:${filePath}`);\n return stored ? Number(stored) : null;\n });\n const monacoInstanceRef = useRef<typeof MonacoType | null>(null);\n const completionDisposable = useRef<MonacoType.IDisposable | null>(null);\n\n const selectedSqlConn = useMemo(() => connections.find((c) => c.id === sqlConnId) ?? null, [connections, sqlConnId]);\n\n // Beautify for inline content (must be before early returns to maintain hook order)\n const canBeautifyInline = inlineContent != null && (inlineLanguage === \"json\" || inlineLanguage === \"xml\");\n const [isBeautified, setIsBeautified] = useState(false);\n const handleBeautifyInline = useCallback(() => {\n if (!inlineContent) return;\n if (isBeautified) {\n setContent(inlineContent);\n setIsBeautified(false);\n } else {\n const trimmed = inlineContent.trimStart();\n if (inlineLanguage === \"json\") {\n try { setContent(JSON.stringify(JSON.parse(trimmed), null, 2)); setIsBeautified(true); } catch { /* not valid */ }\n } else if (inlineLanguage === \"xml\") {\n let indent = 0;\n const formatted = trimmed.replace(/(>)(<)(\\/*)/g, \"$1\\n$2$3\")\n .split(\"\\n\")\n .map((line) => {\n const l = line.trim();\n if (l.startsWith(\"</\")) indent = Math.max(0, indent - 1);\n const padded = \" \".repeat(indent) + l;\n if (l.startsWith(\"<\") && !l.startsWith(\"</\") && !l.endsWith(\"/>\") && !l.includes(\"</\")) indent++;\n return padded;\n })\n .join(\"\\n\");\n setContent(formatted);\n setIsBeautified(true);\n }\n }\n }, [inlineContent, inlineLanguage, isBeautified]);\n\n // Persist selected connection per file\n const handleSqlConnChange = useCallback((connId: number) => {\n setSqlConnId(connId);\n if (filePath) localStorage.setItem(`ppm:sql-conn:${filePath}`, String(connId));\n // Refresh tables for autocomplete\n refreshTables(connId).catch(() => {});\n }, [filePath, refreshTables]);\n\n // Build SchemaInfo for .sql file autocomplete\n const sqlSchemaInfo = useMemo<SchemaInfo | undefined>(() => {\n if (!isSql || !sqlConnId) return undefined;\n const tables = (cachedTables.get(sqlConnId) ?? []).map((t) => ({ name: t.tableName, schema: t.schemaName }));\n if (tables.length === 0) return undefined;\n return {\n tables,\n getColumns: async (table: string, schema?: string) => {\n return api.get<{ name: string; type: string }[]>(\n `/api/db/connections/${sqlConnId}/schema?table=${encodeURIComponent(table)}${schema ? `&schema=${encodeURIComponent(schema)}` : \"\"}`,\n );\n },\n };\n }, [isSql, sqlConnId, cachedTables]);\n\n // Register/dispose completion provider when connection changes\n useEffect(() => {\n if (!monacoInstanceRef.current || !sqlSchemaInfo) return;\n completionDisposable.current?.dispose();\n clearCompletionCache();\n completionDisposable.current = monacoInstanceRef.current.languages.registerCompletionItemProvider(\n \"sql\",\n createSqlCompletionProvider(monacoInstanceRef.current, sqlSchemaInfo),\n );\n return () => { completionDisposable.current?.dispose(); };\n }, [sqlSchemaInfo]);\n\n // Run SQL inline — execute query and show results in bottom panel\n const openTab = useTabStore((s) => s.openTab);\n const [sqlResult, setSqlResult] = useState<DbQueryResult | null>(null);\n const [sqlError, setSqlError] = useState<string | null>(null);\n const [sqlLoading, setSqlLoading] = useState(false);\n const [sqlResultSql, setSqlResultSql] = useState<string>(\"\");\n const runSqlInViewer = useCallback(async (sqlText: string) => {\n if (!selectedSqlConn) return;\n setSqlLoading(true);\n setSqlError(null);\n setSqlResultSql(sqlText);\n try {\n const result = await api.post<DbQueryResult>(`/api/db/connections/${selectedSqlConn.id}/query`, { sql: sqlText });\n setSqlResult(result);\n } catch (e) {\n setSqlError((e as Error).message);\n setSqlResult(null);\n } finally {\n setSqlLoading(false);\n }\n }, [selectedSqlConn]);\n const openSqlResultInTab = useCallback(() => {\n if (!selectedSqlConn || !sqlResultSql) return;\n openTab({\n type: \"database\",\n title: `${selectedSqlConn.name} · Query`,\n projectId: null,\n closable: true,\n metadata: { connectionId: selectedSqlConn.id, connectionName: selectedSqlConn.name, dbType: selectedSqlConn.type, initialSql: sqlResultSql },\n });\n }, [selectedSqlConn, openTab, sqlResultSql]);\n\n const handleRunInDbViewer = useCallback(() => {\n if (!editorRef.current || !selectedSqlConn) return;\n const editor = editorRef.current;\n const selection = editor.getSelection();\n const sqlText = selection && !selection.isEmpty()\n ? editor.getModel()?.getValueInRange(selection) ?? editor.getValue()\n : editor.getValue();\n runSqlInViewer(sqlText);\n }, [selectedSqlConn, runSqlInViewer]);\n\n // Touch device detection for mobile toolbar\n const isMobile = typeof window !== \"undefined\" && \"ontouchstart\" in window;\n\n // Track visual viewport so toolbar stays above mobile keyboard\n const containerRef = useRef<HTMLDivElement | null>(null);\n const [mobileHeight, setMobileHeight] = useState<number | null>(null);\n useEffect(() => {\n if (!isMobile) return;\n const vv = window.visualViewport;\n if (!vv) return;\n const handle = () => {\n const el = containerRef.current;\n if (!el) return;\n // Calculate available height = viewport height - element's top offset from viewport\n const top = el.getBoundingClientRect().top;\n setMobileHeight(vv.height - Math.max(0, top));\n };\n vv.addEventListener(\"resize\", handle);\n vv.addEventListener(\"scroll\", handle);\n return () => {\n vv.removeEventListener(\"resize\", handle);\n vv.removeEventListener(\"scroll\", handle);\n };\n }, [isMobile]);\n\n // CodeLens: inline Run buttons between SQL statements\n const codeLensDisposable = useRef<MonacoType.IDisposable[]>([]);\n const runSqlRef = useRef(runSqlInViewer);\n runSqlRef.current = runSqlInViewer;\n\n // Cleanup CodeLens providers on unmount to prevent duplicate \"Run\" buttons\n useEffect(() => {\n return () => {\n codeLensDisposable.current.forEach((d) => d.dispose());\n codeLensDisposable.current = [];\n };\n }, []);\n\n // Redirect .db files to sqlite viewer by changing tab type\n useEffect(() => {\n if (isSqlite && tabId) updateTab(tabId, { type: \"sqlite\" });\n }, [isSqlite, tabId, updateTab]);\n\n // Detect external (absolute) file path — not relative to project\n const isExternalFile = filePath ? /^(\\/|[A-Za-z]:[/\\\\])/.test(filePath) : false;\n\n // Load file content\n useEffect(() => {\n if (inlineContent != null) { setLoading(false); return; }\n if (isUntitled) {\n setContent(savedContent ?? \"\");\n latestContentRef.current = savedContent ?? \"\";\n setLoading(false);\n if (savedContent) setUnsaved(true);\n return;\n }\n if (!filePath) return;\n if (!isExternalFile && !projectName) return;\n if (isImage || isPdf || isVideo || isAudio) { setLoading(false); return; }\n\n setLoading(true);\n setError(null);\n\n const readUrl = isExternalFile\n ? `/api/fs/read?path=${encodeURIComponent(filePath)}`\n : `${projectUrl(projectName!)}/files/read?path=${encodeURIComponent(filePath)}`;\n\n api\n .get<{ content: string; encoding?: string }>(readUrl)\n .then((data) => {\n setContent(data.content);\n if (data.encoding) setEncoding(data.encoding);\n latestContentRef.current = data.content;\n setLoading(false);\n })\n .catch((err) => {\n setError(err instanceof Error ? err.message : \"Failed to load file\");\n setLoading(false);\n });\n\n return () => { if (saveTimerRef.current) clearTimeout(saveTimerRef.current); };\n }, [filePath, projectName, isImage, isPdf, isExternalFile, isUntitled]);\n\n // Real-time reload: listen for file:changed WS events, re-fetch if editor is clean\n const unsavedRef = useRef(unsaved);\n unsavedRef.current = unsaved;\n useEffect(() => {\n if (!filePath || !projectName || inlineContent != null || isUntitled) return;\n const handler = (e: Event) => {\n const detail = (e as CustomEvent).detail;\n if (detail.projectName !== projectName || detail.path !== filePath) return;\n if (unsavedRef.current) return; // don't overwrite unsaved changes\n const readUrl = isExternalFile\n ? `/api/fs/read?path=${encodeURIComponent(filePath)}`\n : `${projectUrl(projectName)}/files/read?path=${encodeURIComponent(filePath)}`;\n api.get<{ content: string; encoding?: string }>(readUrl).then((data) => {\n if (data.content === latestContentRef.current) return; // skip if unchanged (e.g. self-save)\n setContent(data.content);\n latestContentRef.current = data.content;\n if (data.encoding) setEncoding(data.encoding);\n }).catch(() => {});\n };\n window.addEventListener(\"file:changed\", handler);\n return () => window.removeEventListener(\"file:changed\", handler);\n }, [filePath, projectName, isExternalFile, inlineContent, isUntitled]);\n\n // Update tab title unsaved indicator (skip for inline content — title set by caller)\n useEffect(() => {\n if (!ownTab || inlineContent != null) return;\n const baseName = isUntitled\n ? `Untitled-${metadata?.untitledNumber ?? 1}`\n : (filePath ? basename(filePath) : \"Untitled\");\n const newTitle = unsaved ? `${baseName} \\u25CF` : baseName;\n if (ownTab.title !== newTitle) updateTab(ownTab.id, { title: newTitle });\n }, [unsaved]); // eslint-disable-line react-hooks/exhaustive-deps\n\n const saveFile = useCallback(\n async (text: string) => {\n if (!filePath) return;\n if (!isExternalFile && !projectName) return;\n try {\n if (isExternalFile) {\n await api.put(\"/api/fs/write\", { path: filePath, content: text });\n } else {\n await api.put(`${projectUrl(projectName!)}/files/write`, { path: filePath, content: text });\n }\n setUnsaved(false);\n } catch { /* Silent — unsaved indicator persists */ }\n },\n [filePath, projectName, isExternalFile],\n );\n\n function handleChange(value: string | undefined) {\n const val = value ?? \"\";\n setContent(val);\n latestContentRef.current = val;\n setUnsaved(true);\n if (saveTimerRef.current) clearTimeout(saveTimerRef.current);\n if (isUntitled) {\n // Persist to metadata for localStorage survival\n saveTimerRef.current = setTimeout(() => {\n if (tabId) updateTab(tabId, { metadata: { ...metadata, unsavedContent: latestContentRef.current } });\n }, 2000);\n } else {\n saveTimerRef.current = setTimeout(() => saveFile(latestContentRef.current), 1000);\n }\n }\n\n // Save As completion — transitions untitled → saved file\n const handleSaveAs = useCallback(async (targetPath: string, savedText: string) => {\n try {\n // Clear any pending metadata persistence timer to prevent race condition\n if (saveTimerRef.current) clearTimeout(saveTimerRef.current);\n await api.put(\"/api/fs/write\", { path: targetPath, content: savedText });\n if (tabId) {\n // Close old untitled tab and open as proper file tab\n const { closeTab, openTab } = usePanelStore.getState();\n closeTab(tabId);\n openTab({\n type: \"editor\",\n title: basename(targetPath),\n projectId: null,\n metadata: { filePath: targetPath },\n closable: true,\n });\n }\n setUnsaved(false);\n setShowSaveAs(false);\n } catch { /* silent — user can retry */ }\n }, [tabId]);\n\n // Jump to line when metadata.lineNumber is set (e.g. from search panel)\n const lineNumber = metadata?.lineNumber as number | undefined;\n const handleEditorMount: OnMount = useCallback((editor, monaco) => {\n editorRef.current = editor;\n monacoInstanceRef.current = monaco;\n if (lineNumber && lineNumber > 0) {\n setTimeout(() => {\n editor.revealLineInCenter(lineNumber);\n editor.setPosition({ lineNumber, column: 1 });\n editor.focus();\n }, 100);\n }\n // Ctrl+S → Save As for untitled tabs\n if (isUntitled) {\n editor.addCommand(\n monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS,\n () => setShowSaveAs(true),\n );\n }\n editor.addCommand(\n monaco.KeyMod.Alt | monaco.KeyCode.KeyZ,\n () => useSettingsStore.getState().toggleWordWrap(),\n );\n monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({\n noSemanticValidation: true, noSyntaxValidation: true, noSuggestionDiagnostics: true,\n });\n monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({\n noSemanticValidation: true, noSyntaxValidation: true, noSuggestionDiagnostics: true,\n });\n // Register SQL completion if schema available\n if (sqlSchemaInfo) {\n completionDisposable.current?.dispose();\n completionDisposable.current = monaco.languages.registerCompletionItemProvider(\n \"sql\", createSqlCompletionProvider(monaco, sqlSchemaInfo),\n );\n }\n\n // Register CodeLens for inline Run buttons on .sql files (scoped to this editor's model)\n if (isSql) {\n codeLensDisposable.current.forEach((d) => d.dispose());\n codeLensDisposable.current = [];\n\n const thisModel = editor.getModel();\n const cmdId = editor.addCommand(0, (_accessor: unknown, sql: string) => {\n if (sql) runSqlRef.current(sql);\n });\n\n if (cmdId && thisModel) {\n const provider = monaco.languages.registerCodeLensProvider(\"sql\", {\n provideCodeLenses: (model: MonacoType.editor.ITextModel) => {\n // Only provide lenses for THIS editor's model, not all SQL models\n if (model !== thisModel) return { lenses: [], dispose: () => {} };\n\n const lenses: MonacoType.languages.CodeLens[] = [];\n const text = model.getValue();\n const lines = text.split(\"\\n\");\n let stmtStartLine = -1;\n let stmtLines: string[] = [];\n let dollarBlock = false; // Track DO $$ ... $$ blocks\n\n const addLens = (line: number, stmt: string) => {\n const trimmed = stmt.trim();\n if (!trimmed || trimmed.startsWith(\"--\")) return;\n lenses.push({\n range: { startLineNumber: line, startColumn: 1, endLineNumber: line, endColumn: 1 },\n command: { id: cmdId, title: \"\\u25B7 Run\", arguments: [trimmed] },\n });\n };\n\n for (let i = 0; i < lines.length; i++) {\n const trimmed = lines[i]!.trim();\n if (stmtStartLine === -1) {\n if (!trimmed || trimmed.startsWith(\"--\")) continue;\n stmtStartLine = i + 1;\n stmtLines = [];\n }\n stmtLines.push(lines[i]!);\n\n // Detect $$ dollar-quoted block start/end\n const dollarMatches = (trimmed.match(/\\$\\$/g) || []).length;\n if (dollarMatches % 2 === 1) dollarBlock = !dollarBlock;\n\n // Only split on ; when NOT inside a $$ block\n if (!dollarBlock && trimmed.endsWith(\";\")) {\n addLens(stmtStartLine, stmtLines.join(\"\\n\"));\n stmtStartLine = -1;\n stmtLines = [];\n }\n }\n if (stmtStartLine > 0 && stmtLines.join(\"\").trim()) {\n addLens(stmtStartLine, stmtLines.join(\"\\n\"));\n }\n return { lenses, dispose: () => {} };\n },\n });\n codeLensDisposable.current.push(provider);\n }\n }\n }, [sqlSchemaInfo]); // eslint-disable-line react-hooks/exhaustive-deps\n\n if (!inlineContent && !isUntitled && (!filePath || (!isExternalFile && !projectName))) {\n return (\n <div className=\"flex items-center justify-center h-full text-text-secondary text-sm\">\n No file selected.\n </div>\n );\n }\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center h-full gap-2 text-text-secondary\">\n <Loader2 className=\"size-5 animate-spin\" />\n <span className=\"text-sm\">Loading file...</span>\n </div>\n );\n }\n\n if (error) {\n return (\n <div className=\"flex items-center justify-center h-full text-error text-sm\">{error}</div>\n );\n }\n\n if (isImage) return <Suspense fallback={<LoadingSpinner />}><ImagePreview filePath={filePath!} projectName={projectName!} /></Suspense>;\n if (isPdf) return <Suspense fallback={<LoadingSpinner />}><PdfPreview filePath={filePath!} projectName={projectName!} /></Suspense>;\n if (isVideo) return <Suspense fallback={<LoadingSpinner />}><VideoPreview filePath={filePath!} projectName={projectName!} /></Suspense>;\n if (isAudio) return <Suspense fallback={<LoadingSpinner />}><AudioPreview filePath={filePath!} projectName={projectName!} /></Suspense>;\n\n if (encoding === \"base64\") {\n return (\n <div className=\"flex flex-col items-center justify-center h-full gap-3 text-text-secondary\">\n <FileWarning className=\"size-10 text-text-subtle\" />\n <p className=\"text-sm\">This file is a binary format and cannot be displayed.</p>\n <p className=\"text-xs text-text-subtle\">{filePath}</p>\n </div>\n );\n }\n\n /** SQL connection picker bar (shared between breadcrumb and standalone) */\n const sqlPickerBar = isSql ? (\n <div className=\"shrink-0 flex items-center gap-1 px-2 border-l border-border\">\n <Database className=\"size-3 text-muted-foreground\" />\n <select\n value={sqlConnId ?? \"\"}\n onChange={(e) => { const v = Number(e.target.value); if (v) handleSqlConnChange(v); }}\n className=\"h-5 text-[10px] bg-transparent border border-border rounded px-1 text-foreground outline-none max-w-[140px]\"\n title=\"Select connection for autocomplete\"\n >\n <option value=\"\">Connection…</option>\n {connections.map((c) => <option key={c.id} value={c.id}>{c.name}</option>)}\n </select>\n <button\n type=\"button\"\n onClick={handleRunInDbViewer}\n disabled={!selectedSqlConn}\n className=\"p-0.5 rounded text-muted-foreground hover:text-primary disabled:opacity-30 transition-colors\"\n title=\"Run SQL\"\n >\n <Play className=\"size-3.5\" />\n </button>\n </div>\n ) : null;\n\n return (\n <div\n ref={containerRef}\n className=\"flex flex-col h-full w-full overflow-hidden\"\n style={mobileHeight ? { height: `${mobileHeight}px`, maxHeight: `${mobileHeight}px` } : undefined}\n >\n {/* Inline content toolbar (cell viewer mode) */}\n {inlineContent != null && canBeautifyInline && (\n <div className=\"flex items-center h-7 border-b border-border bg-background shrink-0 px-2 gap-2\">\n <button type=\"button\" onClick={handleBeautifyInline}\n className=\"text-[10px] px-2 py-0.5 rounded border border-border hover:bg-muted transition-colors text-foreground\">\n {isBeautified ? \"Raw\" : \"Beautify\"}\n </button>\n </div>\n )}\n {/* Breadcrumb + Toolbar bar — desktop only */}\n {filePath && projectName && tabId && (\n <div className=\"hidden md:flex items-center h-7 border-b border-border bg-background shrink-0\">\n <EditorBreadcrumb\n filePath={filePath}\n projectName={projectName}\n tabId={tabId}\n className=\"flex items-center flex-1 min-w-0 overflow-x-auto scrollbar-none px-2 gap-0.5\"\n />\n {sqlPickerBar}\n <EditorToolbar\n ext={ext}\n mdMode={mdMode}\n onMdModeChange={setMdMode}\n csvMode={csvMode}\n onCsvModeChange={setCsvMode}\n wordWrap={wordWrap}\n onToggleWordWrap={toggleWordWrap}\n filePath={filePath}\n projectName={projectName}\n className=\"shrink-0 flex items-center gap-1 px-2\"\n />\n </div>\n )}\n\n {/* Standalone SQL toolbar for external files (no breadcrumb available) */}\n {isSql && (!projectName || !tabId) && (\n <div className=\"hidden md:flex items-center h-7 border-b border-border bg-background shrink-0 px-2\">\n <span className=\"text-xs text-muted-foreground truncate flex-1\">{filePath ? basename(filePath) : \"SQL\"}</span>\n {sqlPickerBar}\n </div>\n )}\n\n {/* Content area */}\n {isCsv && csvMode === \"table\" ? (\n <Suspense fallback={<div className=\"flex items-center justify-center h-full\"><Loader2 className=\"size-5 animate-spin text-text-subtle\" /></div>}>\n <CsvPreview content={content ?? \"\"} onContentChange={handleChange} wordWrap={wordWrap} />\n </Suspense>\n ) : isMarkdown && mdMode === \"preview\" ? (\n <MarkdownPreview content={content ?? \"\"} />\n ) : (\n <div className=\"flex-1 overflow-hidden min-h-0\">\n <Editor\n height=\"100%\"\n language={inlineLanguage ?? getMonacoLanguage(filePath ?? \"\")}\n value={content ?? \"\"}\n onChange={inlineContent != null ? undefined : handleChange}\n onMount={handleEditorMount}\n theme={monacoTheme}\n options={{\n fontSize: 13,\n fontFamily: \"Menlo, Monaco, Consolas, monospace\",\n wordWrap: wordWrap ? \"on\" : \"off\",\n minimap: { enabled: false },\n scrollBeyondLastLine: false,\n automaticLayout: true,\n lineNumbers: \"on\",\n folding: true,\n bracketPairColorization: { enabled: true },\n readOnly: inlineContent != null,\n }}\n loading={<Loader2 className=\"size-5 animate-spin text-text-subtle\" />}\n />\n </div>\n )}\n\n {/* Inline SQL result panel */}\n {isSql && (sqlResult || sqlError || sqlLoading) && (\n <SqlResultPanel\n result={sqlResult} error={sqlError} loading={sqlLoading}\n connName={selectedSqlConn?.name}\n onClose={() => { setSqlResult(null); setSqlError(null); setSqlLoading(false); }}\n onOpenInTab={openSqlResultInTab}\n />\n )}\n\n {/* Mobile toolbar — bottom, like terminal */}\n {isMobile && <EditorMobileToolbar editorRef={editorRef} readOnly={inlineContent != null} />}\n\n {/* Save As dialog for untitled tabs */}\n {showSaveAs && (\n <SaveAsDialog\n open={showSaveAs}\n defaultName={`Untitled-${metadata?.untitledNumber ?? 1}`}\n content={latestContentRef.current}\n onSave={handleSaveAs}\n onCancel={() => setShowSaveAs(false)}\n />\n )}\n </div>\n );\n});\n\nconst NOOP = () => {};\n\n/** Inline SQL result panel — shows query results below the editor */\nfunction SqlResultPanel({ result, error, loading, connName, onClose, onOpenInTab }: {\n result: DbQueryResult | null;\n error: string | null;\n loading: boolean;\n connName?: string;\n onClose: () => void;\n onOpenInTab: () => void;\n}) {\n const tableData = 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 const querySchema = useMemo<GridColumnSchema[]>(() => (\n (result?.columns ?? []).map((c) => ({ name: c, type: \"text\", nullable: true, pk: false, defaultValue: null }))\n ), [result?.columns]);\n\n const [panelHeight, setPanelHeight] = useState(250);\n const handleDrag = useCallback((e: React.MouseEvent) => {\n e.preventDefault();\n const startY = e.clientY;\n const startH = panelHeight;\n const onMove = (ev: MouseEvent) => setPanelHeight(Math.max(80, startH + (startY - ev.clientY)));\n const onUp = () => { document.removeEventListener(\"mousemove\", onMove); document.removeEventListener(\"mouseup\", onUp); };\n document.addEventListener(\"mousemove\", onMove);\n document.addEventListener(\"mouseup\", onUp);\n }, [panelHeight]);\n\n return (\n <div className=\"shrink-0 border-t border-border flex flex-col\" style={{ height: panelHeight }}>\n {/* Resize handle */}\n <div onMouseDown={handleDrag}\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 {/* Title bar */}\n <div className=\"flex items-center gap-2 px-2 py-1 bg-muted/50 border-b border-border shrink-0\">\n <Database className=\"size-3 text-muted-foreground\" />\n <span className=\"text-xs font-medium text-foreground truncate flex-1\">\n {connName ? `${connName} · Results` : \"Query Results\"}\n {result?.executionTimeMs != null && <span className=\"text-muted-foreground ml-1.5 font-normal\">{result.executionTimeMs}ms</span>}\n </span>\n <button type=\"button\" onClick={onOpenInTab} title=\"Open in DB Viewer tab\"\n className=\"flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] text-muted-foreground hover:text-foreground hover:bg-muted transition-colors\">\n <ExternalLink className=\"size-3\" />\n <span className=\"hidden sm:inline\">Open in Tab</span>\n </button>\n <button type=\"button\" onClick={onClose} title=\"Close results\"\n className=\"p-0.5 rounded text-muted-foreground hover:text-foreground transition-colors\">\n <X className=\"size-3\" />\n </button>\n </div>\n\n {/* Content */}\n <div className=\"flex-1 overflow-hidden min-h-0\">\n {loading && (\n <div className=\"flex items-center justify-center h-full\">\n <Loader2 className=\"size-4 animate-spin text-muted-foreground\" />\n </div>\n )}\n {error && <div className=\"px-3 py-2 text-xs text-destructive bg-destructive/5\">{error}</div>}\n {result?.changeType === \"modify\" && (\n <div className=\"px-3 py-2 text-xs text-green-500\">\n {result.rowsAffected} row(s) affected\n </div>\n )}\n {tableData && (\n <GlideDataGrid\n columns={tableData.columns} rows={tableData.rows} total={tableData.total} limit={tableData.limit}\n schema={querySchema} loading={false}\n page={1} onPageChange={NOOP} onCellUpdate={NOOP}\n orderBy={null} orderDir=\"ASC\" onToggleSort={NOOP}\n connectionName={connName}\n />\n )}\n {result?.changeType === \"select\" && result.rows.length === 0 && (\n <div className=\"px-3 py-2 text-xs text-muted-foreground\">No results</div>\n )}\n </div>\n </div>\n );\n}\n\nfunction LoadingSpinner() {\n return <div className=\"flex items-center justify-center h-full\"><Loader2 className=\"size-5 animate-spin text-text-subtle\" /></div>;\n}\n\nfunction MarkdownPreview({ content }: { content: string }) {\n return (\n <Suspense fallback={<div className=\"animate-pulse h-4 bg-muted rounded m-4\" />}>\n <MarkdownRenderer content={content} className=\"flex-1 overflow-auto p-4\" />\n </Suspense>\n );\n}\n\n"],"file":"code-editor-MXnkYRLp.js"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{o as e}from"./rolldown-runtime-FhOqtrmT.js";import{b as t,x as n}from"./vendor-markdown-0Mxgxy0L.js";import{i as r,t as i}from"./api-client-DIhJ5qVW.js";import{n as a}from"./settings-store-8FpQDjEA.js";import"./vendor-mermaid-D2KKkqNs.js";import{h as o,q as s}from"./index-
|
|
1
|
+
import{o as e}from"./rolldown-runtime-FhOqtrmT.js";import{b as t,x as n}from"./vendor-markdown-0Mxgxy0L.js";import{i as r,t as i}from"./api-client-DIhJ5qVW.js";import{n as a}from"./settings-store-8FpQDjEA.js";import"./vendor-mermaid-D2KKkqNs.js";import{h as o,q as s}from"./index-8_rE2Q1-.js";import{n as c,t as l}from"./use-monaco-theme-DEI-tJAh.js";var u=e(n(),1),d=t();function f(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`,go:`go`,rs:`rust`,java:`java`,rb:`ruby`,php:`php`,swift:`swift`,sql:`sql`,xml:`xml`,toml:`toml`}[e.split(`.`).pop()?.toLowerCase()??``]??`plaintext`}function p(e){let t=e.split(`
|
|
2
2
|
`),n=[],r=0,i=0;for(;r<t.length;){let e=t[r];if(e.startsWith(`<<<<<<<`)){let a=r,o=e.substring(7).trim(),s=[];for(r++;r<t.length&&!t[r].startsWith(`=======`);)s.push(t[r]),r++;if(r>=t.length)break;let c=r,l=[];for(r++;r<t.length&&!t[r].startsWith(`>>>>>>>`);)l.push(t[r]),r++;if(r>=t.length)break;let u=t[r].substring(7).trim();n.push({id:i++,startLine:a+1,separatorLine:c+1,endLine:r+1,currentContent:s.join(`
|
|
3
3
|
`),incomingContent:l.join(`
|
|
4
4
|
`),currentLabel:o,incomingLabel:u})}r++}return n}function m({metadata:e}){let t=e?.filePath,n=e?.projectName,[m,g]=(0,u.useState)(null),[_,v]=(0,u.useState)(!0),[y,b]=(0,u.useState)(null),[x,S]=(0,u.useState)(0),C=(0,u.useRef)(null),w=(0,u.useRef)(null),T=(0,u.useRef)([]),E=(0,u.useRef)(null),{wordWrap:D}=a(o(e=>({wordWrap:e.wordWrap}))),O=l(),k=(0,u.useRef)(null),[A,j]=(0,u.useState)();(0,u.useEffect)(()=>{let e=k.current;if(!e)return;let t=new ResizeObserver(([e])=>{e&&j(Math.floor(e.contentRect.height))});return t.observe(e),()=>t.disconnect()},[]),(0,u.useEffect)(()=>{!t||!n||(v(!0),i.get(`${r(n)}/files/read?path=${encodeURIComponent(t)}`).then(e=>{g(e.content),v(!1)}).catch(e=>{b(e.message||`Failed to load file`),v(!1)}))},[t,n]);let M=(0,u.useCallback)(()=>{let e=C.current,t=w.current;if(!e||!t)return;let n=p(e.getModel()?.getValue()||``);S(n.length);for(let t of T.current)e.removeContentWidget(t);if(T.current=[],E.current&&E.current.clear(),n.length===0)return;let r=[];for(let e of n)r.push({range:new t.Range(e.startLine,1,e.startLine,1),options:{isWholeLine:!0,className:`conflict-marker-line`,glyphMarginClassName:`conflict-glyph-current`}}),r.push({range:new t.Range(e.separatorLine,1,e.separatorLine,1),options:{isWholeLine:!0,className:`conflict-marker-line`}}),r.push({range:new t.Range(e.endLine,1,e.endLine,1),options:{isWholeLine:!0,className:`conflict-marker-line`,glyphMarginClassName:`conflict-glyph-incoming`}}),e.separatorLine-e.startLine>1&&r.push({range:new t.Range(e.startLine+1,1,e.separatorLine-1,1),options:{isWholeLine:!0,className:`conflict-current-content`}}),e.endLine-e.separatorLine>1&&r.push({range:new t.Range(e.separatorLine+1,1,e.endLine-1,1),options:{isWholeLine:!0,className:`conflict-incoming-content`}});E.current=e.createDecorationsCollection(r);for(let r of n){let n=`conflict-widget-${r.id}`,i=document.createElement(`div`);i.className=`conflict-actions`,i.innerHTML=`<span class="conflict-label">Current Change (${h(r.currentLabel||`HEAD`)})</span><button class="conflict-btn conflict-btn-current" data-action="current">Accept Current</button><button class="conflict-btn conflict-btn-incoming" data-action="incoming">Accept Incoming</button><button class="conflict-btn conflict-btn-both" data-action="both">Accept Both</button>`,i.addEventListener(`click`,e=>{let t=e.target.closest(`[data-action]`);if(!t)return;let n=t.getAttribute(`data-action`);N(r.id,n)});let a={getId:()=>n,getDomNode:()=>i,getPosition:()=>({position:{lineNumber:r.startLine,column:1},preference:[t.editor.ContentWidgetPositionPreference.ABOVE]})};e.addContentWidget(a),T.current.push(a)}},[]),N=(0,u.useCallback)((e,t)=>{let n=C.current,r=w.current;if(!n||!r)return;let i=n.getModel();if(!i)return;let a=p(i.getValue()).find(t=>t.id===e);if(!a)return;let o;switch(t){case`current`:o=a.currentContent;break;case`incoming`:o=a.incomingContent;break;case`both`:o=a.currentContent+`
|
|
@@ -17,4 +17,4 @@ import{o as e}from"./rolldown-runtime-FhOqtrmT.js";import{b as t,x as n}from"./v
|
|
|
17
17
|
.conflict-btn-incoming { color: #3b82f6; background: rgba(59, 130, 246, 0.15); }
|
|
18
18
|
.conflict-btn-both { color: #a855f7; background: rgba(168, 85, 247, 0.15); }
|
|
19
19
|
`,n.head?.appendChild(e)}M()},I=t?.split(/[\\/]/).pop()??`unknown`,L=f(I);return _?(0,d.jsx)(`div`,{className:`flex items-center justify-center h-full`,children:(0,d.jsx)(s,{className:`size-6 animate-spin text-primary`})}):y?(0,d.jsx)(`div`,{className:`flex items-center justify-center h-full text-destructive`,children:y}):(0,d.jsxs)(`div`,{className:`h-full w-full flex flex-col`,children:[(0,d.jsxs)(`div`,{className:`flex items-center gap-2 px-3 py-1.5 text-xs border-b border-border bg-muted/50 flex-shrink-0`,children:[(0,d.jsx)(`span`,{className:`font-medium`,children:I}),(0,d.jsx)(`span`,{className:`text-muted-foreground`,children:`—`}),x>0?(0,d.jsxs)(`span`,{className:`text-destructive font-medium`,children:[x,` conflict`,x===1?``:`s`,` remaining`]}):(0,d.jsx)(`span`,{className:`text-green-500 font-medium`,children:`All conflicts resolved`})]}),(0,d.jsx)(`div`,{ref:k,className:`flex-1 min-h-0`,children:m!==null&&A&&(0,d.jsx)(c,{height:A,language:L,value:m,onMount:F,theme:O,options:{fontSize:13,fontFamily:`Menlo, Monaco, Consolas, monospace`,wordWrap:D?`on`:`off`,glyphMargin:!0,readOnly:!1,automaticLayout:!0,scrollBeyondLastLine:!1,minimap:{enabled:!1}}})})]})}function h(e){return e.replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`)}export{m as ConflictEditor};
|
|
20
|
-
//# sourceMappingURL=conflict-editor-
|
|
20
|
+
//# sourceMappingURL=conflict-editor-C6wH5wV6.js.map
|