@hienlh/ppm 0.11.4 → 0.11.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/web/assets/{chat-tab-BNOBNzph.js → chat-tab-2znvnxzD.js} +3 -3
  3. package/dist/web/assets/code-editor-CUyFYiwJ.js +8 -0
  4. package/dist/web/assets/{conflict-editor-BGoXs-1S.js → conflict-editor-DXnqiS4c.js} +1 -1
  5. package/dist/web/assets/{database-viewer-B9H3s0Xv.js → database-viewer-DK4PfwVJ.js} +1 -1
  6. package/dist/web/assets/{diff-viewer-CwL9hvFQ.js → diff-viewer-Cy4oX3mb.js} +1 -1
  7. package/dist/web/assets/{extension-webview-dVgax0Uo.js → extension-webview-DIVrmdfu.js} +1 -1
  8. package/dist/web/assets/{index--W7nYakj.js → index-oqP6ldWs.js} +3 -3
  9. package/dist/web/assets/{markdown-renderer-BLksGuhd.js → markdown-renderer-BLhcNL42.js} +1 -1
  10. package/dist/web/assets/{port-forwarding-tab-CezzkDTK.js → port-forwarding-tab-iZost0RL.js} +1 -1
  11. package/dist/web/assets/{postgres-viewer-DkZHOBg1.js → postgres-viewer-CabnbH06.js} +1 -1
  12. package/dist/web/assets/{settings-tab-plcYxjwZ.js → settings-tab-D2th4Hfj.js} +1 -1
  13. package/dist/web/assets/{sqlite-viewer-BmKCNAih.js → sqlite-viewer-cGe2bGlF.js} +1 -1
  14. package/dist/web/assets/{terminal-tab-B0GxiD3-.js → terminal-tab-CPAMJoNK.js} +1 -1
  15. package/dist/web/index.html +1 -1
  16. package/dist/web/sw.js +1 -1
  17. package/package.json +1 -1
  18. package/src/server/ws/chat.ts +10 -0
  19. package/src/services/file-watcher.service.ts +72 -0
  20. package/src/web/components/editor/code-editor.tsx +23 -0
  21. package/src/web/components/explorer/file-tree.tsx +19 -7
  22. package/src/web/hooks/use-chat.ts +20 -0
  23. package/dist/web/assets/code-editor--pNv4j1V.js +0 -8
@@ -240,6 +240,29 @@ export const CodeEditor = memo(function CodeEditor({ metadata, tabId }: CodeEdit
240
240
  return () => { if (saveTimerRef.current) clearTimeout(saveTimerRef.current); };
241
241
  }, [filePath, projectName, isImage, isPdf, isExternalFile, isUntitled]);
242
242
 
243
+ // Real-time reload: listen for file:changed WS events, re-fetch if editor is clean
244
+ const unsavedRef = useRef(unsaved);
245
+ unsavedRef.current = unsaved;
246
+ useEffect(() => {
247
+ if (!filePath || !projectName || inlineContent != null || isUntitled) return;
248
+ const handler = (e: Event) => {
249
+ const detail = (e as CustomEvent).detail;
250
+ if (detail.projectName !== projectName || detail.path !== filePath) return;
251
+ if (unsavedRef.current) return; // don't overwrite unsaved changes
252
+ const readUrl = isExternalFile
253
+ ? `/api/fs/read?path=${encodeURIComponent(filePath)}`
254
+ : `${projectUrl(projectName)}/files/read?path=${encodeURIComponent(filePath)}`;
255
+ api.get<{ content: string; encoding?: string }>(readUrl).then((data) => {
256
+ if (data.content === latestContentRef.current) return; // skip if unchanged (e.g. self-save)
257
+ setContent(data.content);
258
+ latestContentRef.current = data.content;
259
+ if (data.encoding) setEncoding(data.encoding);
260
+ }).catch(() => {});
261
+ };
262
+ window.addEventListener("file:changed", handler);
263
+ return () => window.removeEventListener("file:changed", handler);
264
+ }, [filePath, projectName, isExternalFile, inlineContent, isUntitled]);
265
+
243
266
  // Update tab title unsaved indicator
244
267
  useEffect(() => {
245
268
  if (!ownTab) return;
@@ -256,7 +256,7 @@ interface FileTreeProps {
256
256
  }
257
257
 
258
258
  export function FileTree({ onFileOpen }: FileTreeProps = {}) {
259
- const { tree, loading, error, fetchTree, reset, selectedFiles, clearSelection } = useFileStore(useShallow((s) => ({ tree: s.tree, loading: s.loading, error: s.error, fetchTree: s.fetchTree, reset: s.reset, selectedFiles: s.selectedFiles, clearSelection: s.clearSelection })));
259
+ const { tree, loading, error, fetchTree, reset, selectedFiles, clearSelection, setExpanded } = useFileStore(useShallow((s) => ({ tree: s.tree, loading: s.loading, error: s.error, fetchTree: s.fetchTree, reset: s.reset, selectedFiles: s.selectedFiles, clearSelection: s.clearSelection, setExpanded: s.setExpanded })));
260
260
  const activeProject = useProjectStore((s) => s.activeProject);
261
261
  const openTab = useTabStore((s) => s.openTab);
262
262
  const [actionState, setActionState] = useState<{
@@ -277,12 +277,23 @@ export function FileTree({ onFileOpen }: FileTreeProps = {}) {
277
277
  }
278
278
  }, [activeProject?.name]); // eslint-disable-line react-hooks/exhaustive-deps
279
279
 
280
- // Auto-refresh file tree when window regains focus
281
- // TODO: Replace with fs.watch + WebSocket push for real-time sync without needing window focus
280
+ // Auto-refresh file tree on window focus and real-time file changes via WebSocket
282
281
  useEffect(() => {
283
- const handleFocus = () => { if (activeProject) fetchTree(activeProject.name); };
284
- window.addEventListener("focus", handleFocus);
285
- return () => window.removeEventListener("focus", handleFocus);
282
+ if (!activeProject) return;
283
+ const refresh = () => fetchTree(activeProject.name);
284
+ let debounceTimer: ReturnType<typeof setTimeout>;
285
+ const debouncedRefresh = () => { clearTimeout(debounceTimer); debounceTimer = setTimeout(refresh, 300); };
286
+ const handleFileChanged = (e: Event) => {
287
+ const detail = (e as CustomEvent).detail;
288
+ if (detail.projectName === activeProject.name) debouncedRefresh();
289
+ };
290
+ window.addEventListener("focus", refresh);
291
+ window.addEventListener("file:changed", handleFileChanged);
292
+ return () => {
293
+ clearTimeout(debounceTimer);
294
+ window.removeEventListener("focus", refresh);
295
+ window.removeEventListener("file:changed", handleFileChanged);
296
+ };
286
297
  }, [activeProject, fetchTree]);
287
298
 
288
299
  const uploadFiles = useCallback(async (targetDir: string, files: FileList) => {
@@ -304,10 +315,11 @@ export function FileTree({ onFileOpen }: FileTreeProps = {}) {
304
315
  console.error("Upload failed:", json.error);
305
316
  }
306
317
  loadTree();
318
+ if (targetDir) setExpanded(targetDir, true);
307
319
  } catch (e) {
308
320
  console.error("Upload error:", e);
309
321
  }
310
- }, [activeProject, loadTree]);
322
+ }, [activeProject, loadTree, setExpanded]);
311
323
 
312
324
  const [isRootDragOver, setIsRootDragOver] = useState(false);
313
325
  const rootDragCounter = useRef(0);
@@ -366,6 +366,7 @@ export function useChat(sessionId: string | null, providerId = "claude", project
366
366
  // Finalize the streaming message — preserve SDK UUID for fork/rewind
367
367
  const finalContent = streamingContentRef.current;
368
368
  const finalEvents = [...streamingEventsRef.current];
369
+ const finalAccount = streamingAccountRef.current;
369
370
  const doneUuid = ev.lastMessageUuid as string | undefined;
370
371
  setMessages((prev) => {
371
372
  const last = prev[prev.length - 1];
@@ -378,6 +379,19 @@ export function useChat(sessionId: string | null, providerId = "claude", project
378
379
  ...(doneUuid && { sdkUuid: doneUuid }),
379
380
  }];
380
381
  }
382
+ // No assistant message flushed yet (rAF was still pending when cancelled).
383
+ // Create one from accumulated refs so the response isn't silently lost.
384
+ if (finalContent || finalEvents.length > 0) {
385
+ return [...prev, {
386
+ id: `final-${Date.now()}`,
387
+ role: "assistant" as const,
388
+ content: finalContent,
389
+ events: finalEvents,
390
+ timestamp: new Date().toISOString(),
391
+ ...(doneUuid && { sdkUuid: doneUuid }),
392
+ ...finalAccount,
393
+ }];
394
+ }
381
395
  return prev;
382
396
  });
383
397
  streamingContentRef.current = "";
@@ -401,6 +415,12 @@ export function useChat(sessionId: string | null, providerId = "claude", project
401
415
  // Ignore keepalive pings
402
416
  if ((data as any).type === "ping") return;
403
417
 
418
+ // Dispatch file change events for real-time editor reload
419
+ if ((data as any).type === "file:changed") {
420
+ window.dispatchEvent(new CustomEvent("file:changed", { detail: data }));
421
+ return;
422
+ }
423
+
404
424
  // Dispatch global Jira events so components can listen via window events
405
425
  if (typeof (data as any).type === "string" && (data as any).type.startsWith("jira:")) {
406
426
  window.dispatchEvent(new CustomEvent((data as any).type, { detail: data }));
@@ -1,8 +0,0 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/markdown-renderer-BLksGuhd.js","assets/rolldown-runtime-FhOqtrmT.js","assets/index--W7nYakj.js","assets/vendor-mermaid-CylkVm4U.js","assets/vendor-ui-B-T_damt.js","assets/vendor-markdown-0Mxgxy0L.js","assets/input-CHRMley8.js","assets/utils-ChWX7pZv.js","assets/createLucideIcon-BjHrJDVb.js","assets/x-DlFGzN8d.js","assets/scroll-area-DwWF9FpN.js","assets/ai-settings-section-D2vqiydT.js","assets/dist-C5IgeqrV.js","assets/plus-51UQ45rf.js","assets/refresh-cw-CSFrDtiu.js","assets/trash-2-BgDIBl6f.js","assets/api-client-C3tXCh0r.js","assets/api-settings-2eTz4SgY.js","assets/chevron-right-BzAdxJRG.js","assets/database-D4DIhgi-.js","assets/react-BkWDCPD7.js","assets/extension-store-CkyOvGbF.js","assets/keybindings-store-CpP5_miA.js","assets/tab-store-Jvy1eZGM.js","assets/project-store-CczGNZyf.js","assets/settings-store-CuYjM0FF.js","assets/index-iZHWllzQ.css","assets/csv-preview-D37K2LRd.js","assets/lib-BqkcKGFq.js","assets/arrow-up-Dtrfv490.js","assets/csv-parser-BAa56Nnn.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-B-T_damt.js";import{t as r}from"./createLucideIcon-BjHrJDVb.js";import"./scroll-area-DwWF9FpN.js";import{t as i}from"./chevron-right-BzAdxJRG.js";import{a,l as o,n as s,o as c,r as l,s as u,t as d}from"./input-CHRMley8.js";import{t as f}from"./code-CuravVys.js";import{t as ee}from"./database-D4DIhgi-.js";import{n as p,r as m}from"./x-DlFGzN8d.js";import{t as h}from"./table-DbSviOmw.js";import{t as g}from"./text-wrap-DzvCTq_i.js";import{i as _,r as v,t as y}from"./api-client-C3tXCh0r.js";import{G as b}from"./vendor-mermaid-CylkVm4U.js";import{n as te}from"./settings-store-CuYjM0FF.js";import{t as x}from"./utils-ChWX7pZv.js";import{n as ne,t as re}from"./tab-store-Jvy1eZGM.js";import{n as ie}from"./project-store-CczGNZyf.js";import{B as S,E as ae,F as C,I as w,L as T,M as E,P as D,R as O,a as oe,c as se,d as k,f as A,g as j,h as M,l as N,m as P,o as F,p as ce,u as le,y as I,z as ue}from"./index--W7nYakj.js";import{n as de,t as fe}from"./use-monaco-theme-kjiAwvOp.js";import{n as pe,t as me}from"./sql-completion-provider-D3acAhav.js";var he=r(`file-exclamation-point`,[[`path`,{d:`M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z`,key:`1oefj6`}],[`path`,{d:`M12 9v4`,key:`juzpu7`}],[`path`,{d:`M12 17h.01`,key:`p32p05`}]]),L=e(n(),1),R=t(),ge={ts:O,tsx:O,js:O,jsx:O,py:O,rs:O,go:O,html:O,css:O,scss:O,json:ue,md:T,txt:T,yaml:w,yml:w};function z(e,t){return t?D:ge[e.split(`.`).pop()?.toLowerCase()??``]??C}function _e(e,t){let n=[],r=e;for(let e=0;e<t.length;e++){let i=t[e],a=t.slice(0,e+1).join(`/`),o=r.find(e=>e.name===i);if(n.push({name:i,fullPath:a,node:o??null,siblings:r}),o?.children)r=o.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:[]});break}}return n}function B(e){return[...e].sort((e,t)=>e.type===t.type?e.name.localeCompare(t.name):e.type===`directory`?-1:1)}function ve({filePath:e,projectName:t,tabId:n,className:r}){let a=I(e=>e.tree),{updateTab:o,openTab:s}=re(M(e=>({updateTab:e.updateTab,openTab:e.openTab}))),c=(0,L.useRef)(null),l=(0,L.useMemo)(()=>_e(a,e.split(`/`).filter(Boolean)),[a,e]);(0,L.useEffect)(()=>{c.current&&(c.current.scrollLeft=c.current.scrollWidth)},[l]);function u(e,r){let i=x(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,R.jsx)(`div`,{ref:c,className:r,children:l.map((e,n)=>(0,R.jsxs)(`div`,{className:`flex items-center shrink-0`,children:[n>0&&(0,R.jsx)(i,{className:`size-3 text-muted-foreground shrink-0 mx-0.5`}),e.siblings.length>0?(0,R.jsx)(V,{segment:e,isLast:n===l.length-1,projectName:t,onFileClick:u}):(0,R.jsx)(`span`,{className:`text-xs text-muted-foreground px-1 py-0.5`,children:e.name})]},e.fullPath))})}function V({segment:e,isLast:t,projectName:n,onFileClick:r}){let i=(0,L.useMemo)(()=>B(e.siblings),[e.siblings]);return(0,R.jsxs)(se,{children:[(0,R.jsx)(P,{asChild:!0,children:(0,R.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,R.jsx)(N,{align:`start`,className:`max-h-[300px] p-1`,children:i.map(t=>(0,R.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=z(e.name,e.type===`directory`),a=e.path===n;return e.type===`directory`&&e.children&&e.children.length>0?(0,R.jsxs)(k,{children:[(0,R.jsxs)(ce,{className:`text-xs gap-1.5 ${a?`bg-muted`:``}`,children:[(0,R.jsx)(i,{className:`size-3.5 shrink-0 text-muted-foreground`}),(0,R.jsx)(`span`,{className:`truncate`,children:e.name})]}),(0,R.jsx)(A,{className:`max-h-[300px] overflow-y-auto p-1`,children:B(e.children).map(e=>(0,R.jsx)(H,{node:e,projectName:t,activePath:n,onFileClick:r},e.path))})]}):(0,R.jsxs)(le,{className:`text-xs gap-1.5 cursor-pointer ${a?`bg-muted`:``}`,onSelect:e=>{},onClick:t=>{e.type!==`directory`&&r(e.path,t)},children:[(0,R.jsx)(i,{className:`size-3.5 shrink-0 text-muted-foreground`}),(0,R.jsx)(`span`,{className:`truncate`,children:e.name})]})}function U({active:e,onClick:t,icon:n,label:r}){return(0,R.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,R.jsx)(n,{className:`size-3`}),(0,R.jsx)(`span`,{className:`hidden sm:inline`,children:r})]})}function ye({ext:e,mdMode:t,onMdModeChange:n,csvMode:r,onCsvModeChange:i,wordWrap:a,onToggleWordWrap:o,filePath:s,projectName:c,className:l}){return(0,R.jsxs)(`div`,{className:l,children:[(e===`md`||e===`mdx`)&&n&&(0,R.jsxs)(R.Fragment,{children:[(0,R.jsx)(U,{active:t===`edit`,onClick:()=>n(`edit`),icon:f,label:`Edit`}),(0,R.jsx)(U,{active:t===`preview`,onClick:()=>n(`preview`),icon:p,label:`Preview`})]}),e===`csv`&&i&&(0,R.jsxs)(R.Fragment,{children:[(0,R.jsx)(U,{active:r===`table`,onClick:()=>i(`table`),icon:h,label:`Table`}),(0,R.jsx)(U,{active:r===`raw`,onClick:()=>i(`raw`),icon:f,label:`Raw`})]}),(0,R.jsx)(U,{active:a,onClick:o,icon:g,label:`Wrap`}),s&&c&&(0,R.jsx)(U,{active:!1,onClick:()=>j(c,s),icon:m,label:`Download`})]})}function be({open:e,defaultName:t,content:n,onSave:r,onCancel:i}){let[f,ee]=(0,L.useState)(t),[p,m]=(0,L.useState)(!1),[h,g]=(0,L.useState)(``),_=ie(e=>e.activeProject),v=(0,L.useCallback)(()=>{let e=f.trim();if(!e){g(`Filename cannot be empty`);return}if(/[/\\]/.test(e)){g(`Filename cannot contain / or \\`);return}g(``),m(!0)},[f]),y=(0,L.useCallback)(e=>{let t=e.includes(`\\`)?`\\`:`/`;r(e.endsWith(t)?`${e}${f.trim()}`:`${e}${t}${f.trim()}`,n)},[f,n,r]);return p?(0,R.jsx)(F,{open:!0,mode:`folder`,root:_?.path,title:`Save "${f.trim()}" to...`,onSelect:y,onCancel:()=>m(!1)}):(0,R.jsx)(s,{open:e,onOpenChange:e=>{e||i()},children:(0,R.jsxs)(l,{className:`sm:max-w-md`,children:[(0,R.jsx)(c,{children:(0,R.jsx)(u,{children:`Save As`})}),(0,R.jsxs)(`div`,{className:`flex flex-col gap-2 py-2`,children:[(0,R.jsx)(`label`,{className:`text-sm text-muted-foreground`,children:`Filename`}),(0,R.jsx)(d,{value:f,onChange:e=>{ee(e.target.value),g(``)},onKeyDown:e=>{e.key===`Enter`&&v()},placeholder:`e.g. my-file.ts`,autoFocus:!0}),h&&(0,R.jsx)(`p`,{className:`text-xs text-destructive`,children:h})]}),(0,R.jsxs)(a,{children:[(0,R.jsx)(o,{variant:`outline`,onClick:i,children:`Cancel`}),(0,R.jsx)(o,{onClick:v,children:`Choose Folder...`})]})]})})}var xe=(0,L.lazy)(()=>b(()=>import(`./markdown-renderer-BLksGuhd.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,25,26]))),Se=(0,L.lazy)(()=>b(()=>import(`./csv-preview-D37K2LRd.js`).then(e=>({default:e.CsvPreview})),__vite__mapDeps([27,1,4,5,28,8,29,30]))),Ce=new Set([`png`,`jpg`,`jpeg`,`gif`,`webp`,`svg`,`ico`]),we=new Set([`db`,`sqlite`,`sqlite3`]);function Te(e){return e.split(`.`).pop()?.toLowerCase()??``}function Ee(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`}[Te(e)]??`plaintext`}var W=(0,L.memo)(function({metadata:e,tabId:t}){let n=e?.filePath,r=e?.projectName,i=e?.inlineContent,a=e?.inlineLanguage,[o,s]=(0,L.useState)(i??null),[c,l]=(0,L.useState)(`utf-8`),[u,d]=(0,L.useState)(!0),[f,p]=(0,L.useState)(null),[m,h]=(0,L.useState)(!1),g=(0,L.useRef)(null),v=(0,L.useRef)(``),b=(0,L.useRef)(null),{tabs:ie,updateTab:S}=re(M(e=>({tabs:e.tabs,updateTab:e.updateTab}))),{wordWrap:C,toggleWordWrap:w}=te(M(e=>({wordWrap:e.wordWrap,toggleWordWrap:e.toggleWordWrap}))),T=fe(),D=e?.isUntitled===!0,O=e?.unsavedContent,[se,k]=(0,L.useState)(!1),A=ie.find(e=>e.id===t),j=n?Te(n):``,N=Ce.has(j),P=j===`pdf`,F=we.has(j),ce=j===`md`||j===`mdx`,le=j===`csv`,I=j===`sql`,[ue,ge]=(0,L.useState)(`preview`),[z,_e]=(0,L.useState)(`table`),{connections:B,cachedTables:V,refreshTables:H}=oe(),[U,xe]=(0,L.useState)(()=>{if(!I||!n)return null;let e=localStorage.getItem(`ppm:sql-conn:${n}`);return e?Number(e):null}),W=(0,L.useRef)(null),G=(0,L.useRef)(null),K=(0,L.useMemo)(()=>B.find(e=>e.id===U)??null,[B,U]),Ae=i!=null&&(a===`json`||a===`xml`),[q,J]=(0,L.useState)(!1),je=(0,L.useCallback)(()=>{if(i)if(q)s(i),J(!1);else{let e=i.trimStart();if(a===`json`)try{s(JSON.stringify(JSON.parse(e),null,2)),J(!0)}catch{}else if(a===`xml`){let t=0;s(e.replace(/(>)(<)(\/*)/g,`$1
3
- $2$3`).split(`
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
- `)),J(!0)}}},[i,a,q]),Me=(0,L.useCallback)(e=>{xe(e),n&&localStorage.setItem(`ppm:sql-conn:${n}`,String(e)),H(e).catch(()=>{})},[n,H]),Y=(0,L.useMemo)(()=>{if(!I||!U)return;let e=(V.get(U)??[]).map(e=>({name:e.tableName,schema:e.schemaName}));if(e.length!==0)return{tables:e,getColumns:async(e,t)=>y.get(`/api/db/connections/${U}/schema?table=${encodeURIComponent(e)}${t?`&schema=${encodeURIComponent(t)}`:``}`)}},[I,U,V]);(0,L.useEffect)(()=>{if(!(!W.current||!Y))return G.current?.dispose(),me(),G.current=W.current.languages.registerCompletionItemProvider(`sql`,pe(W.current,Y)),()=>{G.current?.dispose()}},[Y]);let Ne=re(e=>e.openTab),X=(0,L.useCallback)(e=>{K&&Ne({type:`database`,title:`${K.name} · Query`,projectId:null,closable:!0,metadata:{connectionId:K.id,connectionName:K.name,dbType:K.type,initialSql:e}})},[K,Ne]),Pe=(0,L.useCallback)(()=>{if(!b.current||!K)return;let e=b.current,t=e.getSelection();X(t&&!t.isEmpty()?e.getModel()?.getValueInRange(t)??e.getValue():e.getValue())},[K,X]),Z=(0,L.useRef)([]),Fe=(0,L.useRef)(X);Fe.current=X,(0,L.useEffect)(()=>()=>{Z.current.forEach(e=>e.dispose()),Z.current=[]},[]),(0,L.useEffect)(()=>{F&&t&&S(t,{type:`sqlite`})},[F,t,S]);let Q=n?/^(\/|[A-Za-z]:[/\\])/.test(n):!1;(0,L.useEffect)(()=>{if(i!=null){d(!1);return}if(D){s(O??``),v.current=O??``,d(!1),O&&h(!0);return}if(!n||!Q&&!r)return;if(N||P){d(!1);return}d(!0),p(null);let e=Q?`/api/fs/read?path=${encodeURIComponent(n)}`:`${_(r)}/files/read?path=${encodeURIComponent(n)}`;return y.get(e).then(e=>{s(e.content),e.encoding&&l(e.encoding),v.current=e.content,d(!1)}).catch(e=>{p(e instanceof Error?e.message:`Failed to load file`),d(!1)}),()=>{g.current&&clearTimeout(g.current)}},[n,r,N,P,Q,D]),(0,L.useEffect)(()=>{if(!A)return;let t=D?`Untitled-${e?.untitledNumber??1}`:n?x(n):`Untitled`,r=m?`${t} \u25CF`:t;A.title!==r&&S(A.id,{title:r})},[m]);let Ie=(0,L.useCallback)(async e=>{if(n&&!(!Q&&!r))try{Q?await y.put(`/api/fs/write`,{path:n,content:e}):await y.put(`${_(r)}/files/write`,{path:n,content:e}),h(!1)}catch{}},[n,r,Q]);function Le(n){let r=n??``;s(r),v.current=r,h(!0),g.current&&clearTimeout(g.current),D?g.current=setTimeout(()=>{t&&S(t,{metadata:{...e,unsavedContent:v.current}})},2e3):g.current=setTimeout(()=>Ie(v.current),1e3)}let Re=(0,L.useCallback)(async(e,n)=>{try{if(g.current&&clearTimeout(g.current),await y.put(`/api/fs/write`,{path:e,content:n}),t){let{closeTab:n,openTab:r}=ne.getState();n(t),r({type:`editor`,title:x(e),projectId:null,metadata:{filePath:e},closable:!0})}h(!1),k(!1)}catch{}},[t]),$=e?.lineNumber,ze=(0,L.useCallback)((e,t)=>{if(b.current=e,W.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,()=>te.getState().toggleWordWrap()),t.languages.typescript.typescriptDefaults.setDiagnosticsOptions({noSemanticValidation:!0,noSyntaxValidation:!0,noSuggestionDiagnostics:!0}),t.languages.typescript.javascriptDefaults.setDiagnosticsOptions({noSemanticValidation:!0,noSyntaxValidation:!0,noSuggestionDiagnostics:!0}),Y&&(G.current?.dispose(),G.current=t.languages.registerCompletionItemProvider(`sql`,pe(t,Y))),I){Z.current.forEach(e=>e.dispose()),Z.current=[];let n=e.getModel(),r=e.addCommand(0,(e,t)=>{t&&Fe.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
- `),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
- `)),a=-1,o=[])}return a>0&&o.join(``).trim()&&c(a,o.join(`
8
- `)),{lenses:t,dispose:()=>{}}}});Z.current.push(e)}}},[Y]);if(!i&&!D&&(!n||!Q&&!r))return(0,R.jsx)(`div`,{className:`flex items-center justify-center h-full text-text-secondary text-sm`,children:`No file selected.`});if(u)return(0,R.jsxs)(`div`,{className:`flex items-center justify-center h-full gap-2 text-text-secondary`,children:[(0,R.jsx)(E,{className:`size-5 animate-spin`}),(0,R.jsx)(`span`,{className:`text-sm`,children:`Loading file...`})]});if(f)return(0,R.jsx)(`div`,{className:`flex items-center justify-center h-full text-error text-sm`,children:f});if(N)return(0,R.jsx)(Oe,{filePath:n,projectName:r});if(P)return(0,R.jsx)(ke,{filePath:n,projectName:r});if(c===`base64`)return(0,R.jsxs)(`div`,{className:`flex flex-col items-center justify-center h-full gap-3 text-text-secondary`,children:[(0,R.jsx)(he,{className:`size-10 text-text-subtle`}),(0,R.jsx)(`p`,{className:`text-sm`,children:`This file is a binary format and cannot be displayed.`}),(0,R.jsx)(`p`,{className:`text-xs text-text-subtle`,children:n})]});let Be=I?(0,R.jsxs)(`div`,{className:`shrink-0 flex items-center gap-1 px-2 border-l border-border`,children:[(0,R.jsx)(ee,{className:`size-3 text-muted-foreground`}),(0,R.jsxs)(`select`,{value:U??``,onChange:e=>{let t=Number(e.target.value);t&&Me(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,R.jsx)(`option`,{value:``,children:`Connection…`}),B.map(e=>(0,R.jsx)(`option`,{value:e.id,children:e.name},e.id))]}),(0,R.jsx)(`button`,{type:`button`,onClick:Pe,disabled:!K,className:`p-0.5 rounded text-muted-foreground hover:text-primary disabled:opacity-30 transition-colors`,title:`Run all in DB Viewer`,children:(0,R.jsx)(ae,{className:`size-3.5`})})]}):null;return(0,R.jsxs)(`div`,{className:`flex flex-col h-full w-full overflow-hidden`,children:[i!=null&&Ae&&(0,R.jsx)(`div`,{className:`flex items-center h-7 border-b border-border bg-background shrink-0 px-2 gap-2`,children:(0,R.jsx)(`button`,{type:`button`,onClick:je,className:`text-[10px] px-2 py-0.5 rounded border border-border hover:bg-muted transition-colors text-foreground`,children:q?`Raw`:`Beautify`})}),n&&r&&t&&(0,R.jsxs)(`div`,{className:`hidden md:flex items-center h-7 border-b border-border bg-background shrink-0`,children:[(0,R.jsx)(ve,{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`}),Be,(0,R.jsx)(ye,{ext:j,mdMode:ue,onMdModeChange:ge,csvMode:z,onCsvModeChange:_e,wordWrap:C,onToggleWordWrap:w,filePath:n,projectName:r,className:`shrink-0 flex items-center gap-1 px-2`})]}),I&&(!r||!t)&&(0,R.jsxs)(`div`,{className:`hidden md:flex items-center h-7 border-b border-border bg-background shrink-0 px-2`,children:[(0,R.jsx)(`span`,{className:`text-xs text-muted-foreground truncate flex-1`,children:n?x(n):`SQL`}),Be]}),le&&z===`table`?(0,R.jsx)(L.Suspense,{fallback:(0,R.jsx)(`div`,{className:`flex items-center justify-center h-full`,children:(0,R.jsx)(E,{className:`size-5 animate-spin text-text-subtle`})}),children:(0,R.jsx)(Se,{content:o??``,onContentChange:Le,wordWrap:C})}):ce&&ue===`preview`?(0,R.jsx)(De,{content:o??``}):(0,R.jsx)(`div`,{className:`flex-1 overflow-hidden`,children:(0,R.jsx)(de,{height:`100%`,language:a??Ee(n??``),value:o??``,onChange:i==null?Le:void 0,onMount:ze,theme:T,options:{fontSize:13,fontFamily:`Menlo, Monaco, Consolas, monospace`,wordWrap:C?`on`:`off`,minimap:{enabled:!1},scrollBeyondLastLine:!1,automaticLayout:!0,lineNumbers:`on`,folding:!0,bracketPairColorization:{enabled:!0},readOnly:i!=null},loading:(0,R.jsx)(E,{className:`size-5 animate-spin text-text-subtle`})})}),se&&(0,R.jsx)(be,{open:se,defaultName:`Untitled-${e?.untitledNumber??1}`,content:v.current,onSave:Re,onCancel:()=>k(!1)})]})});function De({content:e}){return(0,R.jsx)(L.Suspense,{fallback:(0,R.jsx)(`div`,{className:`animate-pulse h-4 bg-muted rounded m-4`}),children:(0,R.jsx)(xe,{content:e,className:`flex-1 overflow-auto p-4`})})}function Oe({filePath:e,projectName:t}){let[n,r]=(0,L.useState)(null),[i,a]=(0,L.useState)(!1);return(0,L.useEffect)(()=>{let n,i=`${_(t)}/files/raw?path=${encodeURIComponent(e)}`,o=v();return fetch(i,{headers:o?{Authorization:`Bearer ${o}`}:{}}).then(e=>{if(!e.ok)throw Error(`Failed`);return e.blob()}).then(e=>{let t=URL.createObjectURL(e);n=t,r(t)}).catch(()=>a(!0)),()=>{n&&URL.revokeObjectURL(n)}},[e,t]),i?(0,R.jsxs)(`div`,{className:`flex flex-col items-center justify-center h-full gap-3 text-text-secondary`,children:[(0,R.jsx)(he,{className:`size-10 text-text-subtle`}),(0,R.jsx)(`p`,{className:`text-sm`,children:`Failed to load image.`})]}):n?(0,R.jsx)(`div`,{className:`flex items-center justify-center h-full p-4 bg-surface overflow-auto`,children:(0,R.jsx)(`img`,{src:n,alt:e,className:`max-w-full max-h-full object-contain`})}):(0,R.jsx)(`div`,{className:`flex items-center justify-center h-full`,children:(0,R.jsx)(E,{className:`size-5 animate-spin text-text-subtle`})})}function ke({filePath:e,projectName:t}){let[n,r]=(0,L.useState)(null),[i,a]=(0,L.useState)(!1);(0,L.useEffect)(()=>{let n,i=`${_(t)}/files/raw?path=${encodeURIComponent(e)}`,o=v();return fetch(i,{headers:o?{Authorization:`Bearer ${o}`}:{}}).then(e=>{if(!e.ok)throw Error(`Failed`);return e.blob()}).then(e=>{let t=URL.createObjectURL(new Blob([e],{type:`application/pdf`}));n=t,r(t)}).catch(()=>a(!0)),()=>{n&&URL.revokeObjectURL(n)}},[e,t]);let o=(0,L.useCallback)(()=>{n&&window.open(n,`_blank`)},[n]);return i?(0,R.jsxs)(`div`,{className:`flex flex-col items-center justify-center h-full gap-3 text-text-secondary`,children:[(0,R.jsx)(he,{className:`size-10 text-text-subtle`}),(0,R.jsx)(`p`,{className:`text-sm`,children:`Failed to load PDF.`})]}):n?(0,R.jsxs)(`div`,{className:`flex flex-col h-full`,children:[(0,R.jsxs)(`div`,{className:`flex items-center justify-between px-3 py-1.5 border-b border-border bg-background shrink-0`,children:[(0,R.jsx)(`span`,{className:`text-xs text-text-secondary truncate`,children:e}),(0,R.jsxs)(`button`,{onClick:o,className:`flex items-center gap-1 text-xs text-text-secondary hover:text-text-primary transition-colors`,children:[(0,R.jsx)(S,{className:`size-3`}),` Open in new tab`]})]}),(0,R.jsx)(`iframe`,{src:n,title:e,className:`flex-1 w-full border-none`})]}):(0,R.jsx)(`div`,{className:`flex items-center justify-center h-full`,children:(0,R.jsx)(E,{className:`size-5 animate-spin text-text-subtle`})})}export{W as CodeEditor};