@hienlh/ppm 0.13.48 → 0.13.49

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 (55) hide show
  1. package/assets/skills/ppm/SKILL.md +1 -1
  2. package/assets/skills/ppm/references/http-api.md +1 -1
  3. package/dist/web/assets/{audio-preview-BFc4v5Tx.js → audio-preview-CQ-l5P8I.js} +2 -2
  4. package/dist/web/assets/{audio-preview-BFc4v5Tx.js.map → audio-preview-CQ-l5P8I.js.map} +1 -1
  5. package/dist/web/assets/{chat-tab-x1rBGHj5.js → chat-tab-DbuBr2ax.js} +4 -4
  6. package/dist/web/assets/{chat-tab-x1rBGHj5.js.map → chat-tab-DbuBr2ax.js.map} +1 -1
  7. package/dist/web/assets/{code-editor-BgyAZcQX.js → code-editor-DEa0t62y.js} +3 -3
  8. package/dist/web/assets/{code-editor-BgyAZcQX.js.map → code-editor-DEa0t62y.js.map} +1 -1
  9. package/dist/web/assets/{conflict-editor-Dhc1lem7.js → conflict-editor-D5H9urYy.js} +2 -2
  10. package/dist/web/assets/{conflict-editor-Dhc1lem7.js.map → conflict-editor-D5H9urYy.js.map} +1 -1
  11. package/dist/web/assets/{database-viewer-DnnjO38W.js → database-viewer-CW60ytCl.js} +2 -2
  12. package/dist/web/assets/{database-viewer-DnnjO38W.js.map → database-viewer-CW60ytCl.js.map} +1 -1
  13. package/dist/web/assets/{diff-viewer-B6q9wXD6.js → diff-viewer-BfatMgWw.js} +2 -2
  14. package/dist/web/assets/{diff-viewer-B6q9wXD6.js.map → diff-viewer-BfatMgWw.js.map} +1 -1
  15. package/dist/web/assets/{extension-webview-CMBEb4FF.js → extension-webview-DKSDoW_g.js} +2 -2
  16. package/dist/web/assets/{extension-webview-CMBEb4FF.js.map → extension-webview-DKSDoW_g.js.map} +1 -1
  17. package/dist/web/assets/{glide-data-grid-BLcBAxgp.js → glide-data-grid-Bx48618B.js} +2 -2
  18. package/dist/web/assets/{glide-data-grid-BLcBAxgp.js.map → glide-data-grid-Bx48618B.js.map} +1 -1
  19. package/dist/web/assets/{image-preview-YpWk7AOb.js → image-preview-ClY2xl1B.js} +2 -2
  20. package/dist/web/assets/{image-preview-YpWk7AOb.js.map → image-preview-ClY2xl1B.js.map} +1 -1
  21. package/dist/web/assets/{index-hxAGD2rx.js → index-DkQ6jVSH.js} +5 -5
  22. package/dist/web/assets/{index-hxAGD2rx.js.map → index-DkQ6jVSH.js.map} +1 -1
  23. package/dist/web/assets/keybindings-store-DrAeg6Gw.js +1 -0
  24. package/dist/web/assets/{markdown-renderer-BAAmsTL9.js → markdown-renderer-BmMmo0F-.js} +2 -2
  25. package/dist/web/assets/{markdown-renderer-BAAmsTL9.js.map → markdown-renderer-BmMmo0F-.js.map} +1 -1
  26. package/dist/web/assets/notification-store-Bukl8bKo.js +1 -0
  27. package/dist/web/assets/{pdf-preview-DWe7ARXI.js → pdf-preview-YylEP_Su.js} +2 -2
  28. package/dist/web/assets/{pdf-preview-DWe7ARXI.js.map → pdf-preview-YylEP_Su.js.map} +1 -1
  29. package/dist/web/assets/{port-forwarding-tab-R5V7zkKO.js → port-forwarding-tab-COdo70kU.js} +2 -2
  30. package/dist/web/assets/{port-forwarding-tab-R5V7zkKO.js.map → port-forwarding-tab-COdo70kU.js.map} +1 -1
  31. package/dist/web/assets/{postgres-viewer-AxCUyDQP.js → postgres-viewer-3y9VshZZ.js} +2 -2
  32. package/dist/web/assets/{postgres-viewer-AxCUyDQP.js.map → postgres-viewer-3y9VshZZ.js.map} +1 -1
  33. package/dist/web/assets/{settings-tab-CJ6w6q2s.js → settings-tab-sYavdJk-.js} +1 -1
  34. package/dist/web/assets/{sql-query-editor-DhZvNbKv.js → sql-query-editor-DlBYx1Ye.js} +2 -2
  35. package/dist/web/assets/{sql-query-editor-DhZvNbKv.js.map → sql-query-editor-DlBYx1Ye.js.map} +1 -1
  36. package/dist/web/assets/{sqlite-viewer-Qv8TlhPb.js → sqlite-viewer-Bj8oPYho.js} +2 -2
  37. package/dist/web/assets/{sqlite-viewer-Qv8TlhPb.js.map → sqlite-viewer-Bj8oPYho.js.map} +1 -1
  38. package/dist/web/assets/{terminal-tab-9hLQtUUT.js → terminal-tab-B7ECmf95.js} +2 -2
  39. package/dist/web/assets/{terminal-tab-9hLQtUUT.js.map → terminal-tab-B7ECmf95.js.map} +1 -1
  40. package/dist/web/assets/{video-preview-C-tj7tok.js → video-preview-CT78iZBo.js} +2 -2
  41. package/dist/web/assets/{video-preview-C-tj7tok.js.map → video-preview-CT78iZBo.js.map} +1 -1
  42. package/dist/web/index.html +1 -1
  43. package/dist/web/sw.js +1 -1
  44. package/package.json +1 -1
  45. package/src/services/db.service.ts +28 -0
  46. package/src/types/extension.ts +1 -1
  47. package/src/web/components/settings/extension-manager-section.tsx +2 -2
  48. package/dist/web/assets/keybindings-store-Dn9ANcCK.js +0 -1
  49. package/dist/web/assets/notification-store-DyVQiPv9.js +0 -1
  50. package/packages/ext-database/package.json +0 -48
  51. package/packages/ext-database/src/connection-tree.ts +0 -201
  52. package/packages/ext-database/src/extension.ts +0 -578
  53. package/packages/ext-database/src/query-panel.ts +0 -133
  54. package/packages/ext-database/src/table-viewer-panel.ts +0 -420
  55. package/packages/ext-database/tsconfig.json +0 -8
@@ -1 +1 @@
1
- {"version":3,"file":"conflict-editor-Dhc1lem7.js","names":[],"sources":["../../../src/web/components/editor/conflict-editor.tsx"],"sourcesContent":["import { useEffect, useState, useRef, useCallback } 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 { useSettingsStore } from \"@/stores/settings-store\";\nimport { useMonacoTheme } from \"@/lib/use-monaco-theme\";\nimport { Loader2 } from \"lucide-react\";\n\nfunction getMonacoLanguage(filename: string): string {\n const ext = filename.split(\".\").pop()?.toLowerCase() ?? \"\";\n const map: Record<string, string> = {\n js: \"javascript\", jsx: \"javascript\",\n ts: \"typescript\", tsx: \"typescript\",\n py: \"python\", html: \"html\",\n css: \"css\", scss: \"scss\",\n json: \"json\", md: \"markdown\", mdx: \"markdown\",\n yaml: \"yaml\", yml: \"yaml\",\n sh: \"shell\", bash: \"shell\",\n go: \"go\", rs: \"rust\", java: \"java\",\n rb: \"ruby\", php: \"php\", swift: \"swift\",\n sql: \"sql\", xml: \"xml\", toml: \"toml\",\n };\n return map[ext] ?? \"plaintext\";\n}\n\ninterface ConflictRegion {\n id: number;\n startLine: number; // 1-indexed, line of <<<<<<< marker\n separatorLine: number; // line of =======\n endLine: number; // line of >>>>>>> marker\n currentContent: string;\n incomingContent: string;\n currentLabel: string;\n incomingLabel: string;\n}\n\nfunction parseConflicts(content: string): ConflictRegion[] {\n const lines = content.split(\"\\n\");\n const regions: ConflictRegion[] = [];\n let i = 0;\n let id = 0;\n\n while (i < lines.length) {\n const line = lines[i]!;\n if (line.startsWith(\"<<<<<<<\")) {\n const startLine = i;\n const currentLabel = line.substring(7).trim();\n const currentLines: string[] = [];\n i++;\n\n while (i < lines.length && !lines[i]!.startsWith(\"=======\")) {\n currentLines.push(lines[i]!);\n i++;\n }\n if (i >= lines.length) break;\n\n const separatorLine = i;\n const incomingLines: string[] = [];\n i++;\n\n while (i < lines.length && !lines[i]!.startsWith(\">>>>>>>\")) {\n incomingLines.push(lines[i]!);\n i++;\n }\n if (i >= lines.length) break;\n\n const incomingLabel = lines[i]!.substring(7).trim();\n\n regions.push({\n id: id++,\n startLine: startLine + 1,\n separatorLine: separatorLine + 1,\n endLine: i + 1,\n currentContent: currentLines.join(\"\\n\"),\n incomingContent: incomingLines.join(\"\\n\"),\n currentLabel,\n incomingLabel,\n });\n }\n i++;\n }\n return regions;\n}\n\ninterface ConflictEditorProps {\n metadata?: Record<string, unknown>;\n tabId?: string;\n}\n\nexport function ConflictEditor({ metadata }: ConflictEditorProps) {\n const filePath = metadata?.filePath as string | undefined;\n const projectName = metadata?.projectName as string | undefined;\n\n const [content, setContent] = useState<string | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [conflictCount, setConflictCount] = useState(0);\n const editorRef = useRef<MonacoType.editor.IStandaloneCodeEditor | null>(null);\n const monacoRef = useRef<typeof MonacoType | null>(null);\n const widgetsRef = useRef<MonacoType.editor.IContentWidget[]>([]);\n const decorationsRef = useRef<MonacoType.editor.IEditorDecorationsCollection | null>(null);\n\n const { wordWrap } = useSettingsStore(useShallow((s) => ({ wordWrap: s.wordWrap })));\n const monacoTheme = useMonacoTheme();\n\n const containerRef = useRef<HTMLDivElement>(null);\n const [containerHeight, setContainerHeight] = useState<number | undefined>();\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n const ro = new ResizeObserver(([entry]) => {\n if (entry) setContainerHeight(Math.floor(entry.contentRect.height));\n });\n ro.observe(el);\n return () => ro.disconnect();\n }, []);\n\n // Load file content\n useEffect(() => {\n if (!filePath || !projectName) return;\n setLoading(true);\n api\n .get<{ content: string }>(`${projectUrl(projectName)}/files/read?path=${encodeURIComponent(filePath)}`)\n .then((data) => {\n setContent(data.content);\n setLoading(false);\n })\n .catch((e: Error) => {\n setError(e.message || \"Failed to load file\");\n setLoading(false);\n });\n }, [filePath, projectName]);\n\n const refreshConflicts = useCallback(() => {\n const editor = editorRef.current;\n const monaco = monacoRef.current;\n if (!editor || !monaco) return;\n\n const value = editor.getModel()?.getValue() || \"\";\n const regions = parseConflicts(value);\n setConflictCount(regions.length);\n\n // Clear old widgets\n for (const w of widgetsRef.current) {\n editor.removeContentWidget(w);\n }\n widgetsRef.current = [];\n\n // Clear old decorations\n if (decorationsRef.current) {\n decorationsRef.current.clear();\n }\n\n if (regions.length === 0) return;\n\n // Apply decorations\n const decos: MonacoType.editor.IModelDeltaDecoration[] = [];\n for (const region of regions) {\n // Marker lines\n decos.push({\n range: new monaco.Range(region.startLine, 1, region.startLine, 1),\n options: { isWholeLine: true, className: \"conflict-marker-line\", glyphMarginClassName: \"conflict-glyph-current\" },\n });\n decos.push({\n range: new monaco.Range(region.separatorLine, 1, region.separatorLine, 1),\n options: { isWholeLine: true, className: \"conflict-marker-line\" },\n });\n decos.push({\n range: new monaco.Range(region.endLine, 1, region.endLine, 1),\n options: { isWholeLine: true, className: \"conflict-marker-line\", glyphMarginClassName: \"conflict-glyph-incoming\" },\n });\n // Current content (green)\n if (region.separatorLine - region.startLine > 1) {\n decos.push({\n range: new monaco.Range(region.startLine + 1, 1, region.separatorLine - 1, 1),\n options: { isWholeLine: true, className: \"conflict-current-content\" },\n });\n }\n // Incoming content (blue)\n if (region.endLine - region.separatorLine > 1) {\n decos.push({\n range: new monaco.Range(region.separatorLine + 1, 1, region.endLine - 1, 1),\n options: { isWholeLine: true, className: \"conflict-incoming-content\" },\n });\n }\n }\n\n decorationsRef.current = editor.createDecorationsCollection(decos);\n\n // Add accept widgets above each conflict\n for (const region of regions) {\n const widgetId = `conflict-widget-${region.id}`;\n\n const domNode = document.createElement(\"div\");\n domNode.className = \"conflict-actions\";\n domNode.innerHTML =\n `<span class=\"conflict-label\">Current Change (${escHtml(region.currentLabel || \"HEAD\")})</span>` +\n `<button class=\"conflict-btn conflict-btn-current\" data-action=\"current\">Accept Current</button>` +\n `<button class=\"conflict-btn conflict-btn-incoming\" data-action=\"incoming\">Accept Incoming</button>` +\n `<button class=\"conflict-btn conflict-btn-both\" data-action=\"both\">Accept Both</button>`;\n\n domNode.addEventListener(\"click\", (e) => {\n const btn = (e.target as HTMLElement).closest(\"[data-action]\");\n if (!btn) return;\n const action = btn.getAttribute(\"data-action\") as \"current\" | \"incoming\" | \"both\";\n acceptConflict(region.id, action);\n });\n\n const widget: MonacoType.editor.IContentWidget = {\n getId: () => widgetId,\n getDomNode: () => domNode,\n getPosition: () => ({\n position: { lineNumber: region.startLine, column: 1 },\n preference: [monaco.editor.ContentWidgetPositionPreference.ABOVE],\n }),\n };\n editor.addContentWidget(widget);\n widgetsRef.current.push(widget);\n }\n }, []);\n\n const acceptConflict = useCallback(\n (regionId: number, action: \"current\" | \"incoming\" | \"both\") => {\n const editor = editorRef.current;\n const monaco = monacoRef.current;\n if (!editor || !monaco) return;\n\n const model = editor.getModel();\n if (!model) return;\n\n const value = model.getValue();\n const regions = parseConflicts(value);\n const region = regions.find((r) => r.id === regionId);\n if (!region) return;\n\n let replacement: string;\n switch (action) {\n case \"current\":\n replacement = region.currentContent;\n break;\n case \"incoming\":\n replacement = region.incomingContent;\n break;\n case \"both\":\n replacement = region.currentContent + \"\\n\" + region.incomingContent;\n break;\n }\n\n const range = new monaco.Range(region.startLine, 1, region.endLine + 1, 1);\n model.pushEditOperations(\n [],\n [{ range, text: replacement + \"\\n\" }],\n () => null,\n );\n\n // Save and refresh\n saveFile(model.getValue());\n // Small delay to let the model update before refreshing decorations\n setTimeout(() => refreshConflicts(), 50);\n },\n [refreshConflicts],\n );\n\n const saveFile = useCallback(\n async (newContent: string) => {\n if (!filePath || !projectName) return;\n try {\n await api.put<{ written: boolean }>(`${projectUrl(projectName)}/files/write`, {\n path: filePath,\n content: newContent,\n });\n } catch (e) {\n console.error(\"[conflict-editor] save failed:\", e);\n }\n },\n [filePath, projectName],\n );\n\n const handleMount: OnMount = (editor, monaco) => {\n editorRef.current = editor;\n monacoRef.current = monaco;\n\n // Inject conflict editor styles (idempotent)\n const doc = editor.getDomNode()?.ownerDocument ?? document;\n if (!doc.getElementById(\"conflict-editor-styles\")) {\n const styleEl = doc.createElement(\"style\");\n styleEl.id = \"conflict-editor-styles\";\n styleEl.textContent = `\n .conflict-current-content { background: rgba(34, 197, 94, 0.1) !important; }\n .conflict-incoming-content { background: rgba(59, 130, 246, 0.1) !important; }\n .conflict-marker-line { background: rgba(100, 100, 100, 0.15) !important; font-style: italic; }\n .conflict-glyph-current { background: #22c55e !important; }\n .conflict-glyph-incoming { background: #3b82f6 !important; }\n .conflict-actions { display: flex; gap: 8px; align-items: center; padding: 2px 0; font-size: 12px; font-family: system-ui; }\n .conflict-label { color: #22c55e; font-weight: 600; margin-right: 8px; }\n .conflict-btn { padding: 1px 8px; border-radius: 3px; border: none; cursor: pointer; font-size: 11px; opacity: 0.9; }\n .conflict-btn:hover { opacity: 1; }\n .conflict-btn-current { color: #22c55e; background: rgba(34, 197, 94, 0.15); }\n .conflict-btn-incoming { color: #3b82f6; background: rgba(59, 130, 246, 0.15); }\n .conflict-btn-both { color: #a855f7; background: rgba(168, 85, 247, 0.15); }\n `;\n doc.head?.appendChild(styleEl);\n }\n\n refreshConflicts();\n };\n\n const fileName = filePath?.split(/[\\\\/]/).pop() ?? \"unknown\";\n const language = getMonacoLanguage(fileName);\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center h-full\">\n <Loader2 className=\"size-6 animate-spin text-primary\" />\n </div>\n );\n }\n\n if (error) {\n return (\n <div className=\"flex items-center justify-center h-full text-destructive\">\n {error}\n </div>\n );\n }\n\n return (\n <div className=\"h-full w-full flex flex-col\">\n <div className=\"flex items-center gap-2 px-3 py-1.5 text-xs border-b border-border bg-muted/50 flex-shrink-0\">\n <span className=\"font-medium\">{fileName}</span>\n <span className=\"text-muted-foreground\">—</span>\n {conflictCount > 0 ? (\n <span className=\"text-destructive font-medium\">\n {conflictCount} conflict{conflictCount !== 1 ? \"s\" : \"\"} remaining\n </span>\n ) : (\n <span className=\"text-green-500 font-medium\">All conflicts resolved</span>\n )}\n </div>\n <div ref={containerRef} className=\"flex-1 min-h-0\">\n {content !== null && containerHeight && (\n <Editor\n height={containerHeight}\n language={language}\n value={content}\n onMount={handleMount}\n theme={monacoTheme}\n options={{\n fontSize: 13,\n fontFamily: \"Menlo, Monaco, Consolas, monospace\",\n wordWrap: wordWrap ? \"on\" : \"off\",\n glyphMargin: true,\n readOnly: false,\n automaticLayout: true,\n scrollBeyondLastLine: false,\n minimap: { enabled: false },\n }}\n />\n )}\n </div>\n </div>\n );\n}\n\nfunction escHtml(s: string): string {\n return s.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\").replace(/\"/g, \"&quot;\");\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"conflict-editor-D5H9urYy.js","names":[],"sources":["../../../src/web/components/editor/conflict-editor.tsx"],"sourcesContent":["import { useEffect, useState, useRef, useCallback } 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 { useSettingsStore } from \"@/stores/settings-store\";\nimport { useMonacoTheme } from \"@/lib/use-monaco-theme\";\nimport { Loader2 } from \"lucide-react\";\n\nfunction getMonacoLanguage(filename: string): string {\n const ext = filename.split(\".\").pop()?.toLowerCase() ?? \"\";\n const map: Record<string, string> = {\n js: \"javascript\", jsx: \"javascript\",\n ts: \"typescript\", tsx: \"typescript\",\n py: \"python\", html: \"html\",\n css: \"css\", scss: \"scss\",\n json: \"json\", md: \"markdown\", mdx: \"markdown\",\n yaml: \"yaml\", yml: \"yaml\",\n sh: \"shell\", bash: \"shell\",\n go: \"go\", rs: \"rust\", java: \"java\",\n rb: \"ruby\", php: \"php\", swift: \"swift\",\n sql: \"sql\", xml: \"xml\", toml: \"toml\",\n };\n return map[ext] ?? \"plaintext\";\n}\n\ninterface ConflictRegion {\n id: number;\n startLine: number; // 1-indexed, line of <<<<<<< marker\n separatorLine: number; // line of =======\n endLine: number; // line of >>>>>>> marker\n currentContent: string;\n incomingContent: string;\n currentLabel: string;\n incomingLabel: string;\n}\n\nfunction parseConflicts(content: string): ConflictRegion[] {\n const lines = content.split(\"\\n\");\n const regions: ConflictRegion[] = [];\n let i = 0;\n let id = 0;\n\n while (i < lines.length) {\n const line = lines[i]!;\n if (line.startsWith(\"<<<<<<<\")) {\n const startLine = i;\n const currentLabel = line.substring(7).trim();\n const currentLines: string[] = [];\n i++;\n\n while (i < lines.length && !lines[i]!.startsWith(\"=======\")) {\n currentLines.push(lines[i]!);\n i++;\n }\n if (i >= lines.length) break;\n\n const separatorLine = i;\n const incomingLines: string[] = [];\n i++;\n\n while (i < lines.length && !lines[i]!.startsWith(\">>>>>>>\")) {\n incomingLines.push(lines[i]!);\n i++;\n }\n if (i >= lines.length) break;\n\n const incomingLabel = lines[i]!.substring(7).trim();\n\n regions.push({\n id: id++,\n startLine: startLine + 1,\n separatorLine: separatorLine + 1,\n endLine: i + 1,\n currentContent: currentLines.join(\"\\n\"),\n incomingContent: incomingLines.join(\"\\n\"),\n currentLabel,\n incomingLabel,\n });\n }\n i++;\n }\n return regions;\n}\n\ninterface ConflictEditorProps {\n metadata?: Record<string, unknown>;\n tabId?: string;\n}\n\nexport function ConflictEditor({ metadata }: ConflictEditorProps) {\n const filePath = metadata?.filePath as string | undefined;\n const projectName = metadata?.projectName as string | undefined;\n\n const [content, setContent] = useState<string | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [conflictCount, setConflictCount] = useState(0);\n const editorRef = useRef<MonacoType.editor.IStandaloneCodeEditor | null>(null);\n const monacoRef = useRef<typeof MonacoType | null>(null);\n const widgetsRef = useRef<MonacoType.editor.IContentWidget[]>([]);\n const decorationsRef = useRef<MonacoType.editor.IEditorDecorationsCollection | null>(null);\n\n const { wordWrap } = useSettingsStore(useShallow((s) => ({ wordWrap: s.wordWrap })));\n const monacoTheme = useMonacoTheme();\n\n const containerRef = useRef<HTMLDivElement>(null);\n const [containerHeight, setContainerHeight] = useState<number | undefined>();\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n const ro = new ResizeObserver(([entry]) => {\n if (entry) setContainerHeight(Math.floor(entry.contentRect.height));\n });\n ro.observe(el);\n return () => ro.disconnect();\n }, []);\n\n // Load file content\n useEffect(() => {\n if (!filePath || !projectName) return;\n setLoading(true);\n api\n .get<{ content: string }>(`${projectUrl(projectName)}/files/read?path=${encodeURIComponent(filePath)}`)\n .then((data) => {\n setContent(data.content);\n setLoading(false);\n })\n .catch((e: Error) => {\n setError(e.message || \"Failed to load file\");\n setLoading(false);\n });\n }, [filePath, projectName]);\n\n const refreshConflicts = useCallback(() => {\n const editor = editorRef.current;\n const monaco = monacoRef.current;\n if (!editor || !monaco) return;\n\n const value = editor.getModel()?.getValue() || \"\";\n const regions = parseConflicts(value);\n setConflictCount(regions.length);\n\n // Clear old widgets\n for (const w of widgetsRef.current) {\n editor.removeContentWidget(w);\n }\n widgetsRef.current = [];\n\n // Clear old decorations\n if (decorationsRef.current) {\n decorationsRef.current.clear();\n }\n\n if (regions.length === 0) return;\n\n // Apply decorations\n const decos: MonacoType.editor.IModelDeltaDecoration[] = [];\n for (const region of regions) {\n // Marker lines\n decos.push({\n range: new monaco.Range(region.startLine, 1, region.startLine, 1),\n options: { isWholeLine: true, className: \"conflict-marker-line\", glyphMarginClassName: \"conflict-glyph-current\" },\n });\n decos.push({\n range: new monaco.Range(region.separatorLine, 1, region.separatorLine, 1),\n options: { isWholeLine: true, className: \"conflict-marker-line\" },\n });\n decos.push({\n range: new monaco.Range(region.endLine, 1, region.endLine, 1),\n options: { isWholeLine: true, className: \"conflict-marker-line\", glyphMarginClassName: \"conflict-glyph-incoming\" },\n });\n // Current content (green)\n if (region.separatorLine - region.startLine > 1) {\n decos.push({\n range: new monaco.Range(region.startLine + 1, 1, region.separatorLine - 1, 1),\n options: { isWholeLine: true, className: \"conflict-current-content\" },\n });\n }\n // Incoming content (blue)\n if (region.endLine - region.separatorLine > 1) {\n decos.push({\n range: new monaco.Range(region.separatorLine + 1, 1, region.endLine - 1, 1),\n options: { isWholeLine: true, className: \"conflict-incoming-content\" },\n });\n }\n }\n\n decorationsRef.current = editor.createDecorationsCollection(decos);\n\n // Add accept widgets above each conflict\n for (const region of regions) {\n const widgetId = `conflict-widget-${region.id}`;\n\n const domNode = document.createElement(\"div\");\n domNode.className = \"conflict-actions\";\n domNode.innerHTML =\n `<span class=\"conflict-label\">Current Change (${escHtml(region.currentLabel || \"HEAD\")})</span>` +\n `<button class=\"conflict-btn conflict-btn-current\" data-action=\"current\">Accept Current</button>` +\n `<button class=\"conflict-btn conflict-btn-incoming\" data-action=\"incoming\">Accept Incoming</button>` +\n `<button class=\"conflict-btn conflict-btn-both\" data-action=\"both\">Accept Both</button>`;\n\n domNode.addEventListener(\"click\", (e) => {\n const btn = (e.target as HTMLElement).closest(\"[data-action]\");\n if (!btn) return;\n const action = btn.getAttribute(\"data-action\") as \"current\" | \"incoming\" | \"both\";\n acceptConflict(region.id, action);\n });\n\n const widget: MonacoType.editor.IContentWidget = {\n getId: () => widgetId,\n getDomNode: () => domNode,\n getPosition: () => ({\n position: { lineNumber: region.startLine, column: 1 },\n preference: [monaco.editor.ContentWidgetPositionPreference.ABOVE],\n }),\n };\n editor.addContentWidget(widget);\n widgetsRef.current.push(widget);\n }\n }, []);\n\n const acceptConflict = useCallback(\n (regionId: number, action: \"current\" | \"incoming\" | \"both\") => {\n const editor = editorRef.current;\n const monaco = monacoRef.current;\n if (!editor || !monaco) return;\n\n const model = editor.getModel();\n if (!model) return;\n\n const value = model.getValue();\n const regions = parseConflicts(value);\n const region = regions.find((r) => r.id === regionId);\n if (!region) return;\n\n let replacement: string;\n switch (action) {\n case \"current\":\n replacement = region.currentContent;\n break;\n case \"incoming\":\n replacement = region.incomingContent;\n break;\n case \"both\":\n replacement = region.currentContent + \"\\n\" + region.incomingContent;\n break;\n }\n\n const range = new monaco.Range(region.startLine, 1, region.endLine + 1, 1);\n model.pushEditOperations(\n [],\n [{ range, text: replacement + \"\\n\" }],\n () => null,\n );\n\n // Save and refresh\n saveFile(model.getValue());\n // Small delay to let the model update before refreshing decorations\n setTimeout(() => refreshConflicts(), 50);\n },\n [refreshConflicts],\n );\n\n const saveFile = useCallback(\n async (newContent: string) => {\n if (!filePath || !projectName) return;\n try {\n await api.put<{ written: boolean }>(`${projectUrl(projectName)}/files/write`, {\n path: filePath,\n content: newContent,\n });\n } catch (e) {\n console.error(\"[conflict-editor] save failed:\", e);\n }\n },\n [filePath, projectName],\n );\n\n const handleMount: OnMount = (editor, monaco) => {\n editorRef.current = editor;\n monacoRef.current = monaco;\n\n // Inject conflict editor styles (idempotent)\n const doc = editor.getDomNode()?.ownerDocument ?? document;\n if (!doc.getElementById(\"conflict-editor-styles\")) {\n const styleEl = doc.createElement(\"style\");\n styleEl.id = \"conflict-editor-styles\";\n styleEl.textContent = `\n .conflict-current-content { background: rgba(34, 197, 94, 0.1) !important; }\n .conflict-incoming-content { background: rgba(59, 130, 246, 0.1) !important; }\n .conflict-marker-line { background: rgba(100, 100, 100, 0.15) !important; font-style: italic; }\n .conflict-glyph-current { background: #22c55e !important; }\n .conflict-glyph-incoming { background: #3b82f6 !important; }\n .conflict-actions { display: flex; gap: 8px; align-items: center; padding: 2px 0; font-size: 12px; font-family: system-ui; }\n .conflict-label { color: #22c55e; font-weight: 600; margin-right: 8px; }\n .conflict-btn { padding: 1px 8px; border-radius: 3px; border: none; cursor: pointer; font-size: 11px; opacity: 0.9; }\n .conflict-btn:hover { opacity: 1; }\n .conflict-btn-current { color: #22c55e; background: rgba(34, 197, 94, 0.15); }\n .conflict-btn-incoming { color: #3b82f6; background: rgba(59, 130, 246, 0.15); }\n .conflict-btn-both { color: #a855f7; background: rgba(168, 85, 247, 0.15); }\n `;\n doc.head?.appendChild(styleEl);\n }\n\n refreshConflicts();\n };\n\n const fileName = filePath?.split(/[\\\\/]/).pop() ?? \"unknown\";\n const language = getMonacoLanguage(fileName);\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center h-full\">\n <Loader2 className=\"size-6 animate-spin text-primary\" />\n </div>\n );\n }\n\n if (error) {\n return (\n <div className=\"flex items-center justify-center h-full text-destructive\">\n {error}\n </div>\n );\n }\n\n return (\n <div className=\"h-full w-full flex flex-col\">\n <div className=\"flex items-center gap-2 px-3 py-1.5 text-xs border-b border-border bg-muted/50 flex-shrink-0\">\n <span className=\"font-medium\">{fileName}</span>\n <span className=\"text-muted-foreground\">—</span>\n {conflictCount > 0 ? (\n <span className=\"text-destructive font-medium\">\n {conflictCount} conflict{conflictCount !== 1 ? \"s\" : \"\"} remaining\n </span>\n ) : (\n <span className=\"text-green-500 font-medium\">All conflicts resolved</span>\n )}\n </div>\n <div ref={containerRef} className=\"flex-1 min-h-0\">\n {content !== null && containerHeight && (\n <Editor\n height={containerHeight}\n language={language}\n value={content}\n onMount={handleMount}\n theme={monacoTheme}\n options={{\n fontSize: 13,\n fontFamily: \"Menlo, Monaco, Consolas, monospace\",\n wordWrap: wordWrap ? \"on\" : \"off\",\n glyphMargin: true,\n readOnly: false,\n automaticLayout: true,\n scrollBeyondLastLine: false,\n minimap: { enabled: false },\n }}\n />\n )}\n </div>\n </div>\n );\n}\n\nfunction escHtml(s: string): string {\n return s.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\").replace(/\"/g, \"&quot;\");\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{o as e}from"./rolldown-runtime-FhOqtrmT.js";import{b as t,x as n}from"./vendor-markdown-0Mxgxy0L.js";import"./vendor-ui-UXCWAcmi.js";import{t as r}from"./database-DOWH9-Vv.js";import{n as i,t as a}from"./glide-data-grid-BLcBAxgp.js";import{t as o}from"./refresh-cw-BjrAbUJe.js";import{t as s}from"./api-client-DIhJ5qVW.js";import"./settings-store-8FpQDjEA.js";import"./vendor-mermaid-D2KKkqNs.js";import"./panel-store-Dy8-7E_g.js";import"./tab-store-Dtg1_qL0.js";import{J as c,q as l}from"./index-hxAGD2rx.js";import"./data-grid-types-D2cHE8hx.js";import"./use-monaco-theme-DEI-tJAh.js";import{t as u}from"./sql-query-editor-DhZvNbKv.js";var d=e(n(),1);function f(e,t,n,r){return`ppm-db-${e}-${n}.${t}-p${r}`}function p(e,t,n,r){try{let i=sessionStorage.getItem(f(e,t,n,r));return i?JSON.parse(i):null}catch{return null}}function m(e,t,n,r,i,a){try{sessionStorage.setItem(f(e,t,n,r),JSON.stringify({data:i,cols:a}))}catch{}}function h(e){let t=`/api/db/connections/${e}`,[n,r]=(0,d.useState)(null),[i,a]=(0,d.useState)(`public`),[o,c]=(0,d.useState)(null),[l,u]=(0,d.useState)([]),[f,h]=(0,d.useState)(!1),[g,_]=(0,d.useState)(null),[v,y]=(0,d.useState)(1),[b,x]=(0,d.useState)(null),[S,C]=(0,d.useState)(null),[w,T]=(0,d.useState)(!1),[E,D]=(0,d.useState)(null),[O,k]=(0,d.useState)(`ASC`),A=(0,d.useCallback)(async(r,a,o,l,d)=>{let f=r??n,p=a??i;if(!f)return;h(!0);let g=l===void 0?E:l,y=d??O;try{let n=g?`&orderBy=${encodeURIComponent(g)}&orderDir=${y}`:``,[r,i]=await Promise.all([s.get(`${t}/data?table=${encodeURIComponent(f)}&schema=${p}&page=${o??v}&limit=100${n}`),s.get(`${t}/schema?table=${encodeURIComponent(f)}&schema=${p}`)]);c(r),u(i),m(e,f,p,o??v,r,i)}catch(e){_(e.message)}finally{h(!1)}},[t,e,n,i,v,E,O]),j=(0,d.useCallback)((t,n=`public`)=>{r(t),a(n),y(1),x(null);let i=p(e,t,n,1);i?(c(i.data),u(i.cols),h(!1),A(t,n,1)):A(t,n,1)},[e,A]),M=(0,d.useCallback)(e=>{y(e),A(void 0,void 0,e)},[A]),N=(0,d.useCallback)(async e=>{T(!0),C(null);try{let r=await s.post(`${t}/query`,{sql:e});x(r),r.changeType===`modify`&&A(n??void 0,i)}catch(e){C(e.message)}finally{T(!1)}},[t,n,i,A]),P=(0,d.useCallback)(async(e,r,a,o)=>{if(!n)return;let c=n,l=i;try{await s.put(`${t}/cell`,{table:c,schema:l,pkColumn:e,pkValue:r,column:a,value:o}),A(c,l)}catch(e){_(e.message)}},[t,n,i,A]),F=(0,d.useCallback)(async(e,r)=>{if(!n)return;let a=n,o=i;try{await s.del(`${t}/row`,{table:a,schema:o,pkColumn:e,pkValue:r}),A(a,o)}catch(e){_(e.message)}},[t,n,i,A]);return{selectedTable:n,selectedSchema:i,selectTable:j,tableData:o,schema:l,loading:f,error:g,page:v,setPage:M,orderBy:E,orderDir:O,toggleSort:(0,d.useCallback)(e=>{let t,n=`ASC`;E===e?O===`ASC`?(t=e,n=`DESC`):(t=null,n=`ASC`):(t=e,n=`ASC`),D(t),k(n),y(1),A(void 0,void 0,1,t,n)},[E,O,A]),clearSort:(0,d.useCallback)(()=>{D(null),k(`ASC`),y(1),A(void 0,void 0,1,null,`ASC`)},[A]),queryResult:b,queryError:S,queryLoading:w,executeQuery:N,updateCell:P,deleteRow:F,bulkDelete:(0,d.useCallback)(async(e,r)=>{if(!n)return;let a=n,o=i;try{await s.post(`${t}/rows/delete`,{table:a,schema:o,pkColumn:e,pkValues:r}),A(a,o)}catch(e){_(e.message)}},[t,n,i,A]),insertRow:(0,d.useCallback)(async e=>{if(!n)return;let r=n,a=i;try{await s.post(`${t}/row`,{table:r,schema:a,values:e}),A(r,a)}catch(e){throw _(e.message),e}},[t,n,i,A]),refreshData:A,queryAsTable:(0,d.useCallback)(async e=>{h(!0);try{let n=await s.post(`${t}/query`,{sql:e});n.changeType===`select`&&c({columns:n.columns,rows:n.rows,total:n.rows.length,page:1,limit:n.rows.length})}catch(e){_(e.message)}finally{h(!1)}},[t])}}var g=t();function _(e){let t={},n=/"(\w+)"\s+ILIKE\s+'%([^']*?)%'/gi,r;for(;(r=n.exec(e))!==null;)t[r[1]]=r[2].replace(/''/g,`'`);return t}function v({metadata:e}){let t=e?.connectionId,n=e?.connectionName,l=e?.tableName,f=e?.schemaName??`public`,p=e?.initialSql,m=h(t),[v,y]=(0,d.useState)([]),[x,S]=(0,d.useState)(180),C=(0,d.useRef)(null),[w,T]=(0,d.useState)({}),E=(0,d.useMemo)(()=>{if(p&&!m.selectedTable)return p;if(m.selectedTable){let e=`SELECT * FROM "${m.selectedTable}"`,t=Object.entries(w).filter(([,e])=>e.trim()).map(([e,t])=>`"${e}" ILIKE '%${t.replace(/'/g,`''`)}%'`);t.length>0&&(e+=` WHERE ${t.join(` AND `)}`),m.orderBy&&(e+=` ORDER BY "${m.orderBy}" ${m.orderDir}`);let n=(m.page-1)*100;return e+=` LIMIT 100`,n>0&&(e+=` OFFSET ${n}`),e}return`SELECT * FROM `},[p,m.selectedTable,m.orderBy,m.orderDir,m.page,w]),D=(0,d.useCallback)(e=>{T(e)},[]),O=(0,d.useRef)(void 0);(0,d.useEffect)(()=>{if(!(!m.selectedTable||Object.keys(w).length===0))return clearTimeout(O.current),O.current=setTimeout(()=>{m.queryAsTable(E)},500),()=>clearTimeout(O.current)},[w]);let k=(0,d.useCallback)(e=>{e.preventDefault();let t=e.clientY,n=x,r=e=>{let r=e.clientY-t;S(Math.max(80,Math.min(n+r,(C.current?.clientHeight??600)-100)))},i=()=>{document.removeEventListener(`mousemove`,r),document.removeEventListener(`mouseup`,i)};document.addEventListener(`mousemove`,r),document.addEventListener(`mouseup`,i)},[x]);(0,d.useEffect)(()=>{s.get(`/api/db/connections/${t}/tables?cached=1`).then(e=>y(e.map(e=>({name:e.name,schema:e.schema})))).catch(()=>{})},[t]);let A=(0,d.useMemo)(()=>{if(v.length!==0)return{tables:v,getColumns:async(e,n)=>await s.get(`/api/db/connections/${t}/schema?table=${encodeURIComponent(e)}${n?`&schema=${encodeURIComponent(n)}`:``}`)}},[t,v]),j=(0,d.useRef)(!1);(0,d.useEffect)(()=>{j.current||(j.current=!0,p?m.executeQuery(p):l&&m.selectTable(l,f))},[l,f,p]);let[M,N]=(0,d.useState)(!!p),P=(0,d.useCallback)(e=>{let t=e.trim();if(m.selectedTable&&RegExp(`^SELECT\\s+\\*\\s+FROM\\s+"?${m.selectedTable.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}"?\\b`,`i`).test(t)){T(_(t)),m.queryAsTable(t);return}N(!0),m.executeQuery(e)},[m.executeQuery,m.queryAsTable,m.selectedTable]),F=(0,d.useCallback)(e=>{N(!1),m.toggleSort(e)},[m.toggleSort]),I=(0,d.useCallback)(()=>{N(!1),m.clearSort()},[m.clearSort]),L=(0,d.useCallback)(e=>{N(!1),m.setPage(e)},[m.setPage]),R=m.queryResult,z=M&&!!(R||m.queryError),B=m.selectedTable&&!z;return(0,g.jsx)(`div`,{ref:C,className:`flex h-full w-full overflow-hidden`,children:(0,g.jsxs)(`div`,{className:`flex-1 flex flex-col overflow-hidden`,children:[(0,g.jsxs)(`div`,{className:`flex items-center gap-2 px-3 py-1.5 border-b border-border bg-background shrink-0`,children:[(0,g.jsx)(r,{className:`size-3.5 text-muted-foreground`}),(0,g.jsx)(`span`,{className:`text-xs text-muted-foreground truncate`,children:n??`Database`}),m.selectedTable&&(0,g.jsxs)(`span`,{className:`text-xs text-muted-foreground`,children:[`/ `,m.selectedTable]}),(0,g.jsxs)(`div`,{className:`ml-auto flex items-center gap-1`,children:[m.tableData&&(0,g.jsx)(i,{columns:m.tableData.columns,rows:m.tableData.rows,filename:n?`${n}-${m.selectedTable??`data`}`:m.selectedTable??`data`,exportAllUrl:m.selectedTable?`/api/db/connections/${t}/export?table=${encodeURIComponent(m.selectedTable)}&schema=${m.selectedSchema}`:void 0}),(0,g.jsx)(`button`,{type:`button`,onClick:()=>m.refreshData(),title:`Reload data`,className:`p-1 rounded text-muted-foreground hover:text-foreground transition-colors`,children:(0,g.jsx)(o,{className:`size-3 ${m.loading?`animate-spin`:``}`})})]})]}),(0,g.jsx)(`div`,{className:`shrink-0 border-b border-border`,style:{height:x},children:(0,g.jsx)(u,{onExecute:P,loading:m.queryLoading,defaultValue:E,schemaInfo:A,cacheKey:t?String(t):void 0})}),(0,g.jsx)(`div`,{onMouseDown:k,className:`shrink-0 h-1.5 cursor-row-resize bg-border/50 hover:bg-primary/30 flex items-center justify-center transition-colors`,children:(0,g.jsx)(c,{className:`size-3 text-muted-foreground/50`})}),(0,g.jsxs)(`div`,{className:`flex-1 overflow-hidden`,children:[B&&m.tableData&&(0,g.jsx)(a,{columns:m.tableData.columns,rows:m.tableData.rows,total:m.tableData.total,limit:m.tableData.limit,schema:m.schema,loading:m.loading,page:m.page,onPageChange:L,onCellUpdate:m.updateCell,onRowDelete:m.deleteRow,orderBy:m.orderBy,orderDir:m.orderDir,onToggleSort:F,onClearSort:I,onBulkDelete:m.bulkDelete,onInsertRow:m.insertRow,connectionId:t,selectedTable:m.selectedTable,selectedSchema:m.selectedSchema,connectionName:n,columnFilters:w,onColumnFilter:D}),z&&(0,g.jsx)(b,{result:R,error:m.queryError,loading:m.queryLoading,schema:m.schema,connectionName:n})]})]})})}var y=()=>{};function b({result:e,error:t,loading:n,schema:r,connectionName:i}){let o=(0,d.useMemo)(()=>e?.changeType===`select`&&e.rows.length>0?{columns:e.columns,rows:e.rows,total:e.rows.length,limit:e.rows.length}:null,[e]),s=(0,d.useMemo)(()=>r?.length?r:(e?.columns??[]).map(e=>({name:e,type:`text`,nullable:!0,pk:!1,defaultValue:null})),[r,e?.columns]);return(0,g.jsxs)(`div`,{className:`flex flex-col h-full overflow-hidden text-xs`,children:[t&&(0,g.jsx)(`div`,{className:`px-3 py-2 text-destructive bg-destructive/5 shrink-0`,children:t}),e?.changeType===`modify`&&(0,g.jsxs)(`div`,{className:`px-3 py-2 text-green-500 shrink-0`,children:[e.rowsAffected,` row(s) affected`,e.executionTimeMs!=null&&(0,g.jsxs)(`span`,{className:`text-muted-foreground ml-2`,children:[e.executionTimeMs,`ms`]})]}),o&&(0,g.jsxs)(`div`,{className:`flex-1 overflow-hidden`,children:[(0,g.jsx)(a,{columns:o.columns,rows:o.rows,total:o.total,limit:o.limit,schema:s,loading:!!n,page:1,onPageChange:y,onCellUpdate:y,orderBy:null,orderDir:`ASC`,onToggleSort:y,connectionName:i}),e?.executionTimeMs!=null&&(0,g.jsxs)(`div`,{className:`px-3 py-0.5 border-t border-border text-[10px] text-muted-foreground shrink-0`,children:[e.rows.length,` rows · `,e.executionTimeMs,`ms`]})]}),e?.changeType===`select`&&e.rows.length===0&&(0,g.jsxs)(`div`,{className:`px-3 py-2 text-muted-foreground shrink-0`,children:[`No results`,e.executionTimeMs!=null&&(0,g.jsxs)(`span`,{className:`ml-2 text-muted-foreground/60`,children:[e.executionTimeMs,`ms`]})]}),!e&&!t&&(0,g.jsx)(`div`,{className:`flex items-center justify-center h-full text-muted-foreground`,children:n?(0,g.jsx)(l,{className:`size-4 animate-spin`}):`Run a query to see results`})]})}export{v as DatabaseViewer};
2
- //# sourceMappingURL=database-viewer-DnnjO38W.js.map
1
+ import{o as e}from"./rolldown-runtime-FhOqtrmT.js";import{b as t,x as n}from"./vendor-markdown-0Mxgxy0L.js";import"./vendor-ui-UXCWAcmi.js";import{t as r}from"./database-DOWH9-Vv.js";import{n as i,t as a}from"./glide-data-grid-Bx48618B.js";import{t as o}from"./refresh-cw-BjrAbUJe.js";import{t as s}from"./api-client-DIhJ5qVW.js";import"./settings-store-8FpQDjEA.js";import"./vendor-mermaid-D2KKkqNs.js";import"./panel-store-Dy8-7E_g.js";import"./tab-store-Dtg1_qL0.js";import{J as c,q as l}from"./index-DkQ6jVSH.js";import"./data-grid-types-D2cHE8hx.js";import"./use-monaco-theme-DEI-tJAh.js";import{t as u}from"./sql-query-editor-DlBYx1Ye.js";var d=e(n(),1);function f(e,t,n,r){return`ppm-db-${e}-${n}.${t}-p${r}`}function p(e,t,n,r){try{let i=sessionStorage.getItem(f(e,t,n,r));return i?JSON.parse(i):null}catch{return null}}function m(e,t,n,r,i,a){try{sessionStorage.setItem(f(e,t,n,r),JSON.stringify({data:i,cols:a}))}catch{}}function h(e){let t=`/api/db/connections/${e}`,[n,r]=(0,d.useState)(null),[i,a]=(0,d.useState)(`public`),[o,c]=(0,d.useState)(null),[l,u]=(0,d.useState)([]),[f,h]=(0,d.useState)(!1),[g,_]=(0,d.useState)(null),[v,y]=(0,d.useState)(1),[b,x]=(0,d.useState)(null),[S,C]=(0,d.useState)(null),[w,T]=(0,d.useState)(!1),[E,D]=(0,d.useState)(null),[O,k]=(0,d.useState)(`ASC`),A=(0,d.useCallback)(async(r,a,o,l,d)=>{let f=r??n,p=a??i;if(!f)return;h(!0);let g=l===void 0?E:l,y=d??O;try{let n=g?`&orderBy=${encodeURIComponent(g)}&orderDir=${y}`:``,[r,i]=await Promise.all([s.get(`${t}/data?table=${encodeURIComponent(f)}&schema=${p}&page=${o??v}&limit=100${n}`),s.get(`${t}/schema?table=${encodeURIComponent(f)}&schema=${p}`)]);c(r),u(i),m(e,f,p,o??v,r,i)}catch(e){_(e.message)}finally{h(!1)}},[t,e,n,i,v,E,O]),j=(0,d.useCallback)((t,n=`public`)=>{r(t),a(n),y(1),x(null);let i=p(e,t,n,1);i?(c(i.data),u(i.cols),h(!1),A(t,n,1)):A(t,n,1)},[e,A]),M=(0,d.useCallback)(e=>{y(e),A(void 0,void 0,e)},[A]),N=(0,d.useCallback)(async e=>{T(!0),C(null);try{let r=await s.post(`${t}/query`,{sql:e});x(r),r.changeType===`modify`&&A(n??void 0,i)}catch(e){C(e.message)}finally{T(!1)}},[t,n,i,A]),P=(0,d.useCallback)(async(e,r,a,o)=>{if(!n)return;let c=n,l=i;try{await s.put(`${t}/cell`,{table:c,schema:l,pkColumn:e,pkValue:r,column:a,value:o}),A(c,l)}catch(e){_(e.message)}},[t,n,i,A]),F=(0,d.useCallback)(async(e,r)=>{if(!n)return;let a=n,o=i;try{await s.del(`${t}/row`,{table:a,schema:o,pkColumn:e,pkValue:r}),A(a,o)}catch(e){_(e.message)}},[t,n,i,A]);return{selectedTable:n,selectedSchema:i,selectTable:j,tableData:o,schema:l,loading:f,error:g,page:v,setPage:M,orderBy:E,orderDir:O,toggleSort:(0,d.useCallback)(e=>{let t,n=`ASC`;E===e?O===`ASC`?(t=e,n=`DESC`):(t=null,n=`ASC`):(t=e,n=`ASC`),D(t),k(n),y(1),A(void 0,void 0,1,t,n)},[E,O,A]),clearSort:(0,d.useCallback)(()=>{D(null),k(`ASC`),y(1),A(void 0,void 0,1,null,`ASC`)},[A]),queryResult:b,queryError:S,queryLoading:w,executeQuery:N,updateCell:P,deleteRow:F,bulkDelete:(0,d.useCallback)(async(e,r)=>{if(!n)return;let a=n,o=i;try{await s.post(`${t}/rows/delete`,{table:a,schema:o,pkColumn:e,pkValues:r}),A(a,o)}catch(e){_(e.message)}},[t,n,i,A]),insertRow:(0,d.useCallback)(async e=>{if(!n)return;let r=n,a=i;try{await s.post(`${t}/row`,{table:r,schema:a,values:e}),A(r,a)}catch(e){throw _(e.message),e}},[t,n,i,A]),refreshData:A,queryAsTable:(0,d.useCallback)(async e=>{h(!0);try{let n=await s.post(`${t}/query`,{sql:e});n.changeType===`select`&&c({columns:n.columns,rows:n.rows,total:n.rows.length,page:1,limit:n.rows.length})}catch(e){_(e.message)}finally{h(!1)}},[t])}}var g=t();function _(e){let t={},n=/"(\w+)"\s+ILIKE\s+'%([^']*?)%'/gi,r;for(;(r=n.exec(e))!==null;)t[r[1]]=r[2].replace(/''/g,`'`);return t}function v({metadata:e}){let t=e?.connectionId,n=e?.connectionName,l=e?.tableName,f=e?.schemaName??`public`,p=e?.initialSql,m=h(t),[v,y]=(0,d.useState)([]),[x,S]=(0,d.useState)(180),C=(0,d.useRef)(null),[w,T]=(0,d.useState)({}),E=(0,d.useMemo)(()=>{if(p&&!m.selectedTable)return p;if(m.selectedTable){let e=`SELECT * FROM "${m.selectedTable}"`,t=Object.entries(w).filter(([,e])=>e.trim()).map(([e,t])=>`"${e}" ILIKE '%${t.replace(/'/g,`''`)}%'`);t.length>0&&(e+=` WHERE ${t.join(` AND `)}`),m.orderBy&&(e+=` ORDER BY "${m.orderBy}" ${m.orderDir}`);let n=(m.page-1)*100;return e+=` LIMIT 100`,n>0&&(e+=` OFFSET ${n}`),e}return`SELECT * FROM `},[p,m.selectedTable,m.orderBy,m.orderDir,m.page,w]),D=(0,d.useCallback)(e=>{T(e)},[]),O=(0,d.useRef)(void 0);(0,d.useEffect)(()=>{if(!(!m.selectedTable||Object.keys(w).length===0))return clearTimeout(O.current),O.current=setTimeout(()=>{m.queryAsTable(E)},500),()=>clearTimeout(O.current)},[w]);let k=(0,d.useCallback)(e=>{e.preventDefault();let t=e.clientY,n=x,r=e=>{let r=e.clientY-t;S(Math.max(80,Math.min(n+r,(C.current?.clientHeight??600)-100)))},i=()=>{document.removeEventListener(`mousemove`,r),document.removeEventListener(`mouseup`,i)};document.addEventListener(`mousemove`,r),document.addEventListener(`mouseup`,i)},[x]);(0,d.useEffect)(()=>{s.get(`/api/db/connections/${t}/tables?cached=1`).then(e=>y(e.map(e=>({name:e.name,schema:e.schema})))).catch(()=>{})},[t]);let A=(0,d.useMemo)(()=>{if(v.length!==0)return{tables:v,getColumns:async(e,n)=>await s.get(`/api/db/connections/${t}/schema?table=${encodeURIComponent(e)}${n?`&schema=${encodeURIComponent(n)}`:``}`)}},[t,v]),j=(0,d.useRef)(!1);(0,d.useEffect)(()=>{j.current||(j.current=!0,p?m.executeQuery(p):l&&m.selectTable(l,f))},[l,f,p]);let[M,N]=(0,d.useState)(!!p),P=(0,d.useCallback)(e=>{let t=e.trim();if(m.selectedTable&&RegExp(`^SELECT\\s+\\*\\s+FROM\\s+"?${m.selectedTable.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}"?\\b`,`i`).test(t)){T(_(t)),m.queryAsTable(t);return}N(!0),m.executeQuery(e)},[m.executeQuery,m.queryAsTable,m.selectedTable]),F=(0,d.useCallback)(e=>{N(!1),m.toggleSort(e)},[m.toggleSort]),I=(0,d.useCallback)(()=>{N(!1),m.clearSort()},[m.clearSort]),L=(0,d.useCallback)(e=>{N(!1),m.setPage(e)},[m.setPage]),R=m.queryResult,z=M&&!!(R||m.queryError),B=m.selectedTable&&!z;return(0,g.jsx)(`div`,{ref:C,className:`flex h-full w-full overflow-hidden`,children:(0,g.jsxs)(`div`,{className:`flex-1 flex flex-col overflow-hidden`,children:[(0,g.jsxs)(`div`,{className:`flex items-center gap-2 px-3 py-1.5 border-b border-border bg-background shrink-0`,children:[(0,g.jsx)(r,{className:`size-3.5 text-muted-foreground`}),(0,g.jsx)(`span`,{className:`text-xs text-muted-foreground truncate`,children:n??`Database`}),m.selectedTable&&(0,g.jsxs)(`span`,{className:`text-xs text-muted-foreground`,children:[`/ `,m.selectedTable]}),(0,g.jsxs)(`div`,{className:`ml-auto flex items-center gap-1`,children:[m.tableData&&(0,g.jsx)(i,{columns:m.tableData.columns,rows:m.tableData.rows,filename:n?`${n}-${m.selectedTable??`data`}`:m.selectedTable??`data`,exportAllUrl:m.selectedTable?`/api/db/connections/${t}/export?table=${encodeURIComponent(m.selectedTable)}&schema=${m.selectedSchema}`:void 0}),(0,g.jsx)(`button`,{type:`button`,onClick:()=>m.refreshData(),title:`Reload data`,className:`p-1 rounded text-muted-foreground hover:text-foreground transition-colors`,children:(0,g.jsx)(o,{className:`size-3 ${m.loading?`animate-spin`:``}`})})]})]}),(0,g.jsx)(`div`,{className:`shrink-0 border-b border-border`,style:{height:x},children:(0,g.jsx)(u,{onExecute:P,loading:m.queryLoading,defaultValue:E,schemaInfo:A,cacheKey:t?String(t):void 0})}),(0,g.jsx)(`div`,{onMouseDown:k,className:`shrink-0 h-1.5 cursor-row-resize bg-border/50 hover:bg-primary/30 flex items-center justify-center transition-colors`,children:(0,g.jsx)(c,{className:`size-3 text-muted-foreground/50`})}),(0,g.jsxs)(`div`,{className:`flex-1 overflow-hidden`,children:[B&&m.tableData&&(0,g.jsx)(a,{columns:m.tableData.columns,rows:m.tableData.rows,total:m.tableData.total,limit:m.tableData.limit,schema:m.schema,loading:m.loading,page:m.page,onPageChange:L,onCellUpdate:m.updateCell,onRowDelete:m.deleteRow,orderBy:m.orderBy,orderDir:m.orderDir,onToggleSort:F,onClearSort:I,onBulkDelete:m.bulkDelete,onInsertRow:m.insertRow,connectionId:t,selectedTable:m.selectedTable,selectedSchema:m.selectedSchema,connectionName:n,columnFilters:w,onColumnFilter:D}),z&&(0,g.jsx)(b,{result:R,error:m.queryError,loading:m.queryLoading,schema:m.schema,connectionName:n})]})]})})}var y=()=>{};function b({result:e,error:t,loading:n,schema:r,connectionName:i}){let o=(0,d.useMemo)(()=>e?.changeType===`select`&&e.rows.length>0?{columns:e.columns,rows:e.rows,total:e.rows.length,limit:e.rows.length}:null,[e]),s=(0,d.useMemo)(()=>r?.length?r:(e?.columns??[]).map(e=>({name:e,type:`text`,nullable:!0,pk:!1,defaultValue:null})),[r,e?.columns]);return(0,g.jsxs)(`div`,{className:`flex flex-col h-full overflow-hidden text-xs`,children:[t&&(0,g.jsx)(`div`,{className:`px-3 py-2 text-destructive bg-destructive/5 shrink-0`,children:t}),e?.changeType===`modify`&&(0,g.jsxs)(`div`,{className:`px-3 py-2 text-green-500 shrink-0`,children:[e.rowsAffected,` row(s) affected`,e.executionTimeMs!=null&&(0,g.jsxs)(`span`,{className:`text-muted-foreground ml-2`,children:[e.executionTimeMs,`ms`]})]}),o&&(0,g.jsxs)(`div`,{className:`flex-1 overflow-hidden`,children:[(0,g.jsx)(a,{columns:o.columns,rows:o.rows,total:o.total,limit:o.limit,schema:s,loading:!!n,page:1,onPageChange:y,onCellUpdate:y,orderBy:null,orderDir:`ASC`,onToggleSort:y,connectionName:i}),e?.executionTimeMs!=null&&(0,g.jsxs)(`div`,{className:`px-3 py-0.5 border-t border-border text-[10px] text-muted-foreground shrink-0`,children:[e.rows.length,` rows · `,e.executionTimeMs,`ms`]})]}),e?.changeType===`select`&&e.rows.length===0&&(0,g.jsxs)(`div`,{className:`px-3 py-2 text-muted-foreground shrink-0`,children:[`No results`,e.executionTimeMs!=null&&(0,g.jsxs)(`span`,{className:`ml-2 text-muted-foreground/60`,children:[e.executionTimeMs,`ms`]})]}),!e&&!t&&(0,g.jsx)(`div`,{className:`flex items-center justify-center h-full text-muted-foreground`,children:n?(0,g.jsx)(l,{className:`size-4 animate-spin`}):`Run a query to see results`})]})}export{v as DatabaseViewer};
2
+ //# sourceMappingURL=database-viewer-CW60ytCl.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"database-viewer-DnnjO38W.js","names":[],"sources":["../../../src/web/components/database/use-database.ts","../../../src/web/components/database/database-viewer.tsx"],"sourcesContent":["import { useState, useCallback } from \"react\";\nimport { api } from \"@/lib/api-client\";\n\nexport interface DbTableInfo { name: string; schema: string; rowCount: number }\nexport interface DbColumnInfo { name: string; type: string; nullable: boolean; pk: boolean; defaultValue: string | null; fk?: { table: string; column: string } | null }\nexport interface DbQueryResult { columns: string[]; rows: Record<string, unknown>[]; rowsAffected: number; changeType: \"select\" | \"modify\"; executionTimeMs?: number }\ninterface DbTableData { columns: string[]; rows: Record<string, unknown>[]; total: number; page: number; limit: number }\n\n/** SessionStorage cache key for table data */\nfunction cacheKey(connectionId: number, table: string, schema: string, page: number) {\n return `ppm-db-${connectionId}-${schema}.${table}-p${page}`;\n}\n\nfunction readCache(connectionId: number, table: string, schema: string, page: number): { data: DbTableData; cols: DbColumnInfo[] } | null {\n try {\n const raw = sessionStorage.getItem(cacheKey(connectionId, table, schema, page));\n return raw ? JSON.parse(raw) : null;\n } catch { return null; }\n}\n\nfunction writeCache(connectionId: number, table: string, schema: string, page: number, data: DbTableData, cols: DbColumnInfo[]) {\n try { sessionStorage.setItem(cacheKey(connectionId, table, schema, page), JSON.stringify({ data, cols })); } catch { /* quota */ }\n}\n\n/**\n * Generic database hook for unified API (/api/db/connections/:id/...).\n * Works for any DB type (postgres, sqlite, mysql, etc.) via adapter pattern.\n * No auto-fetch on mount — viewer calls selectTable() to start loading.\n */\nexport function useDatabase(connectionId: number) {\n const base = `/api/db/connections/${connectionId}`;\n const [selectedTable, setSelectedTable] = useState<string | null>(null);\n const [selectedSchema, setSelectedSchema] = useState(\"public\");\n const [tableData, setTableData] = useState<DbTableData | null>(null);\n const [schema, setSchema] = useState<DbColumnInfo[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [page, setPageState] = useState(1);\n const [queryResult, setQueryResult] = useState<DbQueryResult | null>(null);\n const [queryError, setQueryError] = useState<string | null>(null);\n const [queryLoading, setQueryLoading] = useState(false);\n // Sort state\n const [orderBy, setOrderBy] = useState<string | null>(null);\n const [orderDir, setOrderDir] = useState<\"ASC\" | \"DESC\">(\"ASC\");\n\n // Fetch table data + schema for current selection\n const fetchTableData = useCallback(async (table?: string, tableSchema?: string, p?: number, sortCol?: string | null, sortDir?: \"ASC\" | \"DESC\") => {\n const t = table ?? selectedTable;\n const s = tableSchema ?? selectedSchema;\n if (!t) return;\n setLoading(true);\n const ob = sortCol !== undefined ? sortCol : orderBy;\n const od = sortDir ?? orderDir;\n try {\n const orderParams = ob ? `&orderBy=${encodeURIComponent(ob)}&orderDir=${od}` : \"\";\n const [data, cols] = await Promise.all([\n api.get<DbTableData>(`${base}/data?table=${encodeURIComponent(t)}&schema=${s}&page=${p ?? page}&limit=100${orderParams}`),\n api.get<DbColumnInfo[]>(`${base}/schema?table=${encodeURIComponent(t)}&schema=${s}`),\n ]);\n setTableData(data);\n setSchema(cols);\n writeCache(connectionId, t, s, p ?? page, data, cols);\n } catch (e) {\n setError((e as Error).message);\n } finally {\n setLoading(false);\n }\n }, [base, connectionId, selectedTable, selectedSchema, page, orderBy, orderDir]);\n\n const selectTable = useCallback((name: string, tableSchema = \"public\") => {\n setSelectedTable(name);\n setSelectedSchema(tableSchema);\n setPageState(1);\n setQueryResult(null);\n // Show cached data instantly, then refresh in background\n const cached = readCache(connectionId, name, tableSchema, 1);\n if (cached) {\n setTableData(cached.data);\n setSchema(cached.cols);\n setLoading(false);\n // Still fetch fresh data in background\n fetchTableData(name, tableSchema, 1);\n } else {\n fetchTableData(name, tableSchema, 1);\n }\n }, [connectionId, fetchTableData]);\n\n const changePage = useCallback((p: number) => {\n setPageState(p);\n fetchTableData(undefined, undefined, p);\n }, [fetchTableData]);\n\n const executeQuery = useCallback(async (sqlText: string) => {\n setQueryLoading(true);\n setQueryError(null);\n try {\n const result = await api.post<DbQueryResult>(`${base}/query`, { sql: sqlText });\n setQueryResult(result);\n if (result.changeType === \"modify\") fetchTableData(selectedTable ?? undefined, selectedSchema);\n } catch (e) {\n setQueryError((e as Error).message);\n } finally {\n setQueryLoading(false);\n }\n }, [base, selectedTable, selectedSchema, fetchTableData]);\n\n const updateCell = useCallback(async (pkColumn: string, pkValue: unknown, column: string, value: unknown) => {\n if (!selectedTable) return;\n const t = selectedTable;\n const s = selectedSchema;\n try {\n await api.put(`${base}/cell`, { table: t, schema: s, pkColumn, pkValue, column, value });\n // Re-fetch with explicit args to avoid stale closure\n fetchTableData(t, s);\n } catch (e) {\n setError((e as Error).message);\n }\n }, [base, selectedTable, selectedSchema, fetchTableData]);\n\n const deleteRow = useCallback(async (pkColumn: string, pkValue: unknown) => {\n if (!selectedTable) return;\n const t = selectedTable;\n const s = selectedSchema;\n try {\n await api.del(`${base}/row`, { table: t, schema: s, pkColumn, pkValue });\n fetchTableData(t, s);\n } catch (e) {\n setError((e as Error).message);\n }\n }, [base, selectedTable, selectedSchema, fetchTableData]);\n\n /** Toggle sort: none → ASC → DESC → none */\n const toggleSort = useCallback((column: string) => {\n let newCol: string | null;\n let newDir: \"ASC\" | \"DESC\" = \"ASC\";\n if (orderBy !== column) {\n newCol = column; newDir = \"ASC\";\n } else if (orderDir === \"ASC\") {\n newCol = column; newDir = \"DESC\";\n } else {\n newCol = null; newDir = \"ASC\";\n }\n setOrderBy(newCol);\n setOrderDir(newDir);\n setPageState(1);\n fetchTableData(undefined, undefined, 1, newCol, newDir);\n }, [orderBy, orderDir, fetchTableData]);\n\n /** Clear sort entirely */\n const clearSort = useCallback(() => {\n setOrderBy(null);\n setOrderDir(\"ASC\");\n setPageState(1);\n fetchTableData(undefined, undefined, 1, null, \"ASC\");\n }, [fetchTableData]);\n\n /** Bulk delete rows */\n const bulkDelete = useCallback(async (pkColumn: string, pkValues: unknown[]) => {\n if (!selectedTable) return;\n const t = selectedTable;\n const s = selectedSchema;\n try {\n await api.post(`${base}/rows/delete`, { table: t, schema: s, pkColumn, pkValues });\n fetchTableData(t, s);\n } catch (e) {\n setError((e as Error).message);\n }\n }, [base, selectedTable, selectedSchema, fetchTableData]);\n\n /** Insert a new row */\n const insertRow = useCallback(async (values: Record<string, unknown>) => {\n if (!selectedTable) return;\n const t = selectedTable;\n const s = selectedSchema;\n try {\n await api.post(`${base}/row`, { table: t, schema: s, values });\n fetchTableData(t, s);\n } catch (e) {\n setError((e as Error).message);\n throw e;\n }\n }, [base, selectedTable, selectedSchema, fetchTableData]);\n\n /** Execute SQL but put results into tableData (for column filters) */\n const queryAsTable = useCallback(async (sqlText: string) => {\n setLoading(true);\n try {\n const result = await api.post<DbQueryResult>(`${base}/query`, { sql: sqlText });\n if (result.changeType === \"select\") {\n setTableData({ columns: result.columns, rows: result.rows, total: result.rows.length, page: 1, limit: result.rows.length });\n }\n } catch (e) {\n setError((e as Error).message);\n } finally {\n setLoading(false);\n }\n }, [base]);\n\n return {\n selectedTable, selectedSchema, selectTable, tableData, schema,\n loading, error, page, setPage: changePage,\n orderBy, orderDir, toggleSort, clearSort,\n queryResult, queryError, queryLoading, executeQuery,\n updateCell, deleteRow, bulkDelete, insertRow,\n refreshData: fetchTableData, queryAsTable,\n };\n}\n","import { useState, useMemo, useEffect, useRef, useCallback } from \"react\";\nimport { Database, RefreshCw, GripHorizontal, Loader2 } from \"lucide-react\";\nimport { api } from \"@/lib/api-client\";\nimport { useDatabase, type DbColumnInfo } from \"./use-database\";\nimport { SqlQueryEditor } from \"./sql-query-editor\";\nimport { ExportButton } from \"./export-button\";\nimport { GlideDataGrid } from \"./glide-data-grid\";\nimport type { SchemaInfo } from \"./sql-completion-provider\";\n\n/** Parse WHERE \"col\" ILIKE '%val%' clauses from SQL */\nfunction parseSqlFilters(sql: string): Record<string, string> {\n const filters: Record<string, string> = {};\n const re = /\"(\\w+)\"\\s+ILIKE\\s+'%([^']*?)%'/gi;\n let m: RegExpExecArray | null;\n while ((m = re.exec(sql)) !== null) {\n filters[m[1]!] = m[2]!.replace(/''/g, \"'\");\n }\n return filters;\n}\n\ninterface Props { metadata?: Record<string, unknown>; tabId?: string }\n\n/** Generic database viewer — works for any DB type via unified API */\nexport function DatabaseViewer({ metadata }: Props) {\n const connectionId = metadata?.connectionId as number;\n const connectionName = metadata?.connectionName as string | undefined;\n const initialTable = metadata?.tableName as string | undefined;\n const initialSchema = (metadata?.schemaName as string) ?? \"public\";\n const initialSql = metadata?.initialSql as string | undefined;\n\n const db = useDatabase(connectionId);\n const [cachedTableNames, setCachedTableNames] = useState<{ name: string; schema: string }[]>([]);\n const [queryHeight, setQueryHeight] = useState(180);\n const containerRef = useRef<HTMLDivElement>(null);\n\n // Column ILIKE filters from DataGrid header\n const [columnFilters, setColumnFilters] = useState<Record<string, string>>({});\n\n // Build query text reflecting current table state (sort, page, filters)\n const defaultQuery = useMemo(() => {\n if (initialSql && !db.selectedTable) return initialSql;\n if (db.selectedTable) {\n let sql = `SELECT * FROM \"${db.selectedTable}\"`;\n const whereParts = Object.entries(columnFilters)\n .filter(([, v]) => v.trim())\n .map(([col, v]) => `\"${col}\" ILIKE '%${v.replace(/'/g, \"''\")}%'`);\n if (whereParts.length > 0) sql += ` WHERE ${whereParts.join(\" AND \")}`;\n if (db.orderBy) sql += ` ORDER BY \"${db.orderBy}\" ${db.orderDir}`;\n const offset = (db.page - 1) * 100;\n sql += ` LIMIT 100`;\n if (offset > 0) sql += ` OFFSET ${offset}`;\n return sql;\n }\n return \"SELECT * FROM \";\n }, [initialSql, db.selectedTable, db.orderBy, db.orderDir, db.page, columnFilters]);\n\n // When column filters change, auto-execute the query with debounce\n const handleColumnFilter = useCallback((filters: Record<string, string>) => {\n setColumnFilters(filters);\n }, []);\n const filterTimerRef = useRef<ReturnType<typeof setTimeout>>(undefined);\n useEffect(() => {\n if (!db.selectedTable || Object.keys(columnFilters).length === 0) return;\n clearTimeout(filterTimerRef.current);\n filterTimerRef.current = setTimeout(() => {\n // Execute filter query into tableData — stays in table grid mode\n db.queryAsTable(defaultQuery);\n }, 500);\n return () => clearTimeout(filterTimerRef.current);\n }, [columnFilters]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Drag resize handler\n const handleDragStart = useCallback((e: React.MouseEvent) => {\n e.preventDefault();\n const startY = e.clientY;\n const startH = queryHeight;\n const onMove = (ev: MouseEvent) => {\n const delta = ev.clientY - startY;\n const newH = Math.max(80, Math.min(startH + delta, (containerRef.current?.clientHeight ?? 600) - 100));\n setQueryHeight(newH);\n };\n const onUp = () => { document.removeEventListener(\"mousemove\", onMove); document.removeEventListener(\"mouseup\", onUp); };\n document.addEventListener(\"mousemove\", onMove);\n document.addEventListener(\"mouseup\", onUp);\n }, [queryHeight]);\n\n // Fetch table names for autocomplete once on mount\n useEffect(() => {\n api.get<{ name: string; schema: string; rowCount: number }[]>(`/api/db/connections/${connectionId}/tables?cached=1`)\n .then((tables) => setCachedTableNames(tables.map((t) => ({ name: t.name, schema: t.schema }))))\n .catch(() => {});\n }, [connectionId]);\n\n // Build SchemaInfo for autocomplete\n const schemaInfo = useMemo<SchemaInfo | undefined>(() => {\n if (cachedTableNames.length === 0) return undefined;\n return {\n tables: cachedTableNames,\n getColumns: async (table: string, schema?: string) => {\n const cols = await api.get<{ name: string; type: string }[]>(\n `/api/db/connections/${connectionId}/schema?table=${encodeURIComponent(table)}${schema ? `&schema=${encodeURIComponent(schema)}` : \"\"}`,\n );\n return cols;\n },\n };\n }, [connectionId, cachedTableNames]);\n\n // Jump to initial table\n const didInit = useRef(false);\n useEffect(() => {\n if (didInit.current) return;\n didInit.current = true;\n if (initialSql) {\n db.executeQuery(initialSql);\n } else if (initialTable) {\n db.selectTable(initialTable, initialSchema);\n }\n }, [initialTable, initialSchema, initialSql]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Track whether user ran a custom query (show results instead of table grid)\n const [showingQueryResult, setShowingQueryResult] = useState(!!initialSql);\n const handleExecuteQuery = useCallback((sql: string) => {\n const trimmed = sql.trim();\n // Check if query is a simple SELECT on the current table — stay in table grid mode\n if (db.selectedTable) {\n const tablePattern = new RegExp(`^SELECT\\\\s+\\\\*\\\\s+FROM\\\\s+\"?${db.selectedTable.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}\"?\\\\b`, \"i\");\n if (tablePattern.test(trimmed)) {\n // Parse ILIKE filters from SQL and sync to columnFilters\n const parsed = parseSqlFilters(trimmed);\n setColumnFilters(parsed);\n db.queryAsTable(trimmed);\n return;\n }\n }\n setShowingQueryResult(true);\n db.executeQuery(sql);\n }, [db.executeQuery, db.queryAsTable, db.selectedTable]);\n\n // When user interacts with DataGrid (sort/page), switch back to table view\n const handleToggleSort = useCallback((col: string) => {\n setShowingQueryResult(false);\n db.toggleSort(col);\n }, [db.toggleSort]);\n const handleClearSort = useCallback(() => {\n setShowingQueryResult(false);\n db.clearSort();\n }, [db.clearSort]);\n const handlePageChange = useCallback((p: number) => {\n setShowingQueryResult(false);\n db.setPage(p);\n }, [db.setPage]);\n\n const qr = db.queryResult;\n const showQueryResults = showingQueryResult && !!(qr || db.queryError);\n const showTableGrid = db.selectedTable && !showQueryResults;\n\n return (\n <div ref={containerRef} className=\"flex h-full w-full overflow-hidden\">\n <div className=\"flex-1 flex flex-col overflow-hidden\">\n {/* Toolbar */}\n <div className=\"flex items-center gap-2 px-3 py-1.5 border-b border-border bg-background shrink-0\">\n <Database className=\"size-3.5 text-muted-foreground\" />\n <span className=\"text-xs text-muted-foreground truncate\">{connectionName ?? \"Database\"}</span>\n {db.selectedTable && <span className=\"text-xs text-muted-foreground\">/ {db.selectedTable}</span>}\n <div className=\"ml-auto flex items-center gap-1\">\n {db.tableData && (\n <ExportButton\n columns={db.tableData.columns}\n rows={db.tableData.rows}\n filename={connectionName ? `${connectionName}-${db.selectedTable ?? \"data\"}` : db.selectedTable ?? \"data\"}\n exportAllUrl={db.selectedTable ? `/api/db/connections/${connectionId}/export?table=${encodeURIComponent(db.selectedTable)}&schema=${db.selectedSchema}` : undefined}\n />\n )}\n <button type=\"button\" onClick={() => db.refreshData()} title=\"Reload data\"\n className=\"p-1 rounded text-muted-foreground hover:text-foreground transition-colors\">\n <RefreshCw className={`size-3 ${db.loading ? \"animate-spin\" : \"\"}`} />\n </button>\n </div>\n </div>\n\n {/* Always-visible query editor at top */}\n <div className=\"shrink-0 border-b border-border\" style={{ height: queryHeight }}>\n <SqlQueryEditor\n onExecute={handleExecuteQuery} loading={db.queryLoading}\n defaultValue={defaultQuery} schemaInfo={schemaInfo}\n cacheKey={connectionId ? String(connectionId) : undefined} />\n </div>\n\n {/* Resize handle */}\n <div onMouseDown={handleDragStart}\n className=\"shrink-0 h-1.5 cursor-row-resize bg-border/50 hover:bg-primary/30 flex items-center justify-center transition-colors\">\n <GripHorizontal className=\"size-3 text-muted-foreground/50\" />\n </div>\n\n {/* Bottom panel: table data OR query results */}\n <div className=\"flex-1 overflow-hidden\">\n {showTableGrid && db.tableData && (\n <GlideDataGrid\n columns={db.tableData.columns} rows={db.tableData.rows}\n total={db.tableData.total} limit={db.tableData.limit}\n schema={db.schema} loading={db.loading}\n page={db.page} onPageChange={handlePageChange}\n onCellUpdate={db.updateCell} onRowDelete={db.deleteRow}\n orderBy={db.orderBy} orderDir={db.orderDir} onToggleSort={handleToggleSort} onClearSort={handleClearSort}\n onBulkDelete={db.bulkDelete} onInsertRow={db.insertRow}\n connectionId={connectionId} selectedTable={db.selectedTable} selectedSchema={db.selectedSchema}\n connectionName={connectionName} columnFilters={columnFilters} onColumnFilter={handleColumnFilter} />\n )}\n\n {showQueryResults && (\n <QueryResultPanel result={qr} error={db.queryError} loading={db.queryLoading} schema={db.schema} connectionName={connectionName} />\n )}\n </div>\n </div>\n </div>\n );\n}\n\nconst NOOP = () => {};\n\n/** Read-only result panel for ad-hoc query results — uses DataGrid for SELECT to get checkboxes + export */\nfunction QueryResultPanel({ result, error, loading, schema, connectionName }: {\n result: { columns: string[]; rows: Record<string, unknown>[]; rowsAffected: number; changeType: \"select\" | \"modify\"; executionTimeMs?: number } | null;\n error: string | null;\n loading?: boolean;\n schema?: DbColumnInfo[];\n connectionName?: string;\n}) {\n // Build a read-only DataGrid-compatible tableData from query result\n const queryTableData = useMemo(() => (\n result?.changeType === \"select\" && result.rows.length > 0\n ? { columns: result.columns, rows: result.rows, total: result.rows.length, limit: result.rows.length }\n : null\n ), [result]);\n\n // Use schema if available, otherwise build minimal schema from column names\n const querySchema = useMemo(() => (\n schema?.length ? schema : (result?.columns ?? []).map((c) => ({\n name: c, type: \"text\", nullable: true, pk: false, defaultValue: null,\n }))\n ), [schema, result?.columns]);\n\n return (\n <div className=\"flex flex-col h-full overflow-hidden text-xs\">\n {error && <div className=\"px-3 py-2 text-destructive bg-destructive/5 shrink-0\">{error}</div>}\n\n {result?.changeType === \"modify\" && (\n <div className=\"px-3 py-2 text-green-500 shrink-0\">\n {result.rowsAffected} row(s) affected\n {result.executionTimeMs != null && <span className=\"text-muted-foreground ml-2\">{result.executionTimeMs}ms</span>}\n </div>\n )}\n\n {queryTableData && (\n <div className=\"flex-1 overflow-hidden\">\n <GlideDataGrid\n columns={queryTableData.columns} rows={queryTableData.rows}\n total={queryTableData.total} limit={queryTableData.limit}\n schema={querySchema} loading={!!loading}\n page={1} onPageChange={NOOP} onCellUpdate={NOOP}\n orderBy={null} orderDir=\"ASC\" onToggleSort={NOOP}\n connectionName={connectionName}\n />\n {result?.executionTimeMs != null && (\n <div className=\"px-3 py-0.5 border-t border-border text-[10px] text-muted-foreground shrink-0\">\n {result.rows.length} rows · {result.executionTimeMs}ms\n </div>\n )}\n </div>\n )}\n\n {result?.changeType === \"select\" && result.rows.length === 0 && (\n <div className=\"px-3 py-2 text-muted-foreground shrink-0\">\n No results\n {result.executionTimeMs != null && <span className=\"ml-2 text-muted-foreground/60\">{result.executionTimeMs}ms</span>}\n </div>\n )}\n\n {!result && !error && (\n <div className=\"flex items-center justify-center h-full text-muted-foreground\">\n {loading ? <Loader2 className=\"size-4 animate-spin\" /> : \"Run a query to see results\"}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"database-viewer-CW60ytCl.js","names":[],"sources":["../../../src/web/components/database/use-database.ts","../../../src/web/components/database/database-viewer.tsx"],"sourcesContent":["import { useState, useCallback } from \"react\";\nimport { api } from \"@/lib/api-client\";\n\nexport interface DbTableInfo { name: string; schema: string; rowCount: number }\nexport interface DbColumnInfo { name: string; type: string; nullable: boolean; pk: boolean; defaultValue: string | null; fk?: { table: string; column: string } | null }\nexport interface DbQueryResult { columns: string[]; rows: Record<string, unknown>[]; rowsAffected: number; changeType: \"select\" | \"modify\"; executionTimeMs?: number }\ninterface DbTableData { columns: string[]; rows: Record<string, unknown>[]; total: number; page: number; limit: number }\n\n/** SessionStorage cache key for table data */\nfunction cacheKey(connectionId: number, table: string, schema: string, page: number) {\n return `ppm-db-${connectionId}-${schema}.${table}-p${page}`;\n}\n\nfunction readCache(connectionId: number, table: string, schema: string, page: number): { data: DbTableData; cols: DbColumnInfo[] } | null {\n try {\n const raw = sessionStorage.getItem(cacheKey(connectionId, table, schema, page));\n return raw ? JSON.parse(raw) : null;\n } catch { return null; }\n}\n\nfunction writeCache(connectionId: number, table: string, schema: string, page: number, data: DbTableData, cols: DbColumnInfo[]) {\n try { sessionStorage.setItem(cacheKey(connectionId, table, schema, page), JSON.stringify({ data, cols })); } catch { /* quota */ }\n}\n\n/**\n * Generic database hook for unified API (/api/db/connections/:id/...).\n * Works for any DB type (postgres, sqlite, mysql, etc.) via adapter pattern.\n * No auto-fetch on mount — viewer calls selectTable() to start loading.\n */\nexport function useDatabase(connectionId: number) {\n const base = `/api/db/connections/${connectionId}`;\n const [selectedTable, setSelectedTable] = useState<string | null>(null);\n const [selectedSchema, setSelectedSchema] = useState(\"public\");\n const [tableData, setTableData] = useState<DbTableData | null>(null);\n const [schema, setSchema] = useState<DbColumnInfo[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [page, setPageState] = useState(1);\n const [queryResult, setQueryResult] = useState<DbQueryResult | null>(null);\n const [queryError, setQueryError] = useState<string | null>(null);\n const [queryLoading, setQueryLoading] = useState(false);\n // Sort state\n const [orderBy, setOrderBy] = useState<string | null>(null);\n const [orderDir, setOrderDir] = useState<\"ASC\" | \"DESC\">(\"ASC\");\n\n // Fetch table data + schema for current selection\n const fetchTableData = useCallback(async (table?: string, tableSchema?: string, p?: number, sortCol?: string | null, sortDir?: \"ASC\" | \"DESC\") => {\n const t = table ?? selectedTable;\n const s = tableSchema ?? selectedSchema;\n if (!t) return;\n setLoading(true);\n const ob = sortCol !== undefined ? sortCol : orderBy;\n const od = sortDir ?? orderDir;\n try {\n const orderParams = ob ? `&orderBy=${encodeURIComponent(ob)}&orderDir=${od}` : \"\";\n const [data, cols] = await Promise.all([\n api.get<DbTableData>(`${base}/data?table=${encodeURIComponent(t)}&schema=${s}&page=${p ?? page}&limit=100${orderParams}`),\n api.get<DbColumnInfo[]>(`${base}/schema?table=${encodeURIComponent(t)}&schema=${s}`),\n ]);\n setTableData(data);\n setSchema(cols);\n writeCache(connectionId, t, s, p ?? page, data, cols);\n } catch (e) {\n setError((e as Error).message);\n } finally {\n setLoading(false);\n }\n }, [base, connectionId, selectedTable, selectedSchema, page, orderBy, orderDir]);\n\n const selectTable = useCallback((name: string, tableSchema = \"public\") => {\n setSelectedTable(name);\n setSelectedSchema(tableSchema);\n setPageState(1);\n setQueryResult(null);\n // Show cached data instantly, then refresh in background\n const cached = readCache(connectionId, name, tableSchema, 1);\n if (cached) {\n setTableData(cached.data);\n setSchema(cached.cols);\n setLoading(false);\n // Still fetch fresh data in background\n fetchTableData(name, tableSchema, 1);\n } else {\n fetchTableData(name, tableSchema, 1);\n }\n }, [connectionId, fetchTableData]);\n\n const changePage = useCallback((p: number) => {\n setPageState(p);\n fetchTableData(undefined, undefined, p);\n }, [fetchTableData]);\n\n const executeQuery = useCallback(async (sqlText: string) => {\n setQueryLoading(true);\n setQueryError(null);\n try {\n const result = await api.post<DbQueryResult>(`${base}/query`, { sql: sqlText });\n setQueryResult(result);\n if (result.changeType === \"modify\") fetchTableData(selectedTable ?? undefined, selectedSchema);\n } catch (e) {\n setQueryError((e as Error).message);\n } finally {\n setQueryLoading(false);\n }\n }, [base, selectedTable, selectedSchema, fetchTableData]);\n\n const updateCell = useCallback(async (pkColumn: string, pkValue: unknown, column: string, value: unknown) => {\n if (!selectedTable) return;\n const t = selectedTable;\n const s = selectedSchema;\n try {\n await api.put(`${base}/cell`, { table: t, schema: s, pkColumn, pkValue, column, value });\n // Re-fetch with explicit args to avoid stale closure\n fetchTableData(t, s);\n } catch (e) {\n setError((e as Error).message);\n }\n }, [base, selectedTable, selectedSchema, fetchTableData]);\n\n const deleteRow = useCallback(async (pkColumn: string, pkValue: unknown) => {\n if (!selectedTable) return;\n const t = selectedTable;\n const s = selectedSchema;\n try {\n await api.del(`${base}/row`, { table: t, schema: s, pkColumn, pkValue });\n fetchTableData(t, s);\n } catch (e) {\n setError((e as Error).message);\n }\n }, [base, selectedTable, selectedSchema, fetchTableData]);\n\n /** Toggle sort: none → ASC → DESC → none */\n const toggleSort = useCallback((column: string) => {\n let newCol: string | null;\n let newDir: \"ASC\" | \"DESC\" = \"ASC\";\n if (orderBy !== column) {\n newCol = column; newDir = \"ASC\";\n } else if (orderDir === \"ASC\") {\n newCol = column; newDir = \"DESC\";\n } else {\n newCol = null; newDir = \"ASC\";\n }\n setOrderBy(newCol);\n setOrderDir(newDir);\n setPageState(1);\n fetchTableData(undefined, undefined, 1, newCol, newDir);\n }, [orderBy, orderDir, fetchTableData]);\n\n /** Clear sort entirely */\n const clearSort = useCallback(() => {\n setOrderBy(null);\n setOrderDir(\"ASC\");\n setPageState(1);\n fetchTableData(undefined, undefined, 1, null, \"ASC\");\n }, [fetchTableData]);\n\n /** Bulk delete rows */\n const bulkDelete = useCallback(async (pkColumn: string, pkValues: unknown[]) => {\n if (!selectedTable) return;\n const t = selectedTable;\n const s = selectedSchema;\n try {\n await api.post(`${base}/rows/delete`, { table: t, schema: s, pkColumn, pkValues });\n fetchTableData(t, s);\n } catch (e) {\n setError((e as Error).message);\n }\n }, [base, selectedTable, selectedSchema, fetchTableData]);\n\n /** Insert a new row */\n const insertRow = useCallback(async (values: Record<string, unknown>) => {\n if (!selectedTable) return;\n const t = selectedTable;\n const s = selectedSchema;\n try {\n await api.post(`${base}/row`, { table: t, schema: s, values });\n fetchTableData(t, s);\n } catch (e) {\n setError((e as Error).message);\n throw e;\n }\n }, [base, selectedTable, selectedSchema, fetchTableData]);\n\n /** Execute SQL but put results into tableData (for column filters) */\n const queryAsTable = useCallback(async (sqlText: string) => {\n setLoading(true);\n try {\n const result = await api.post<DbQueryResult>(`${base}/query`, { sql: sqlText });\n if (result.changeType === \"select\") {\n setTableData({ columns: result.columns, rows: result.rows, total: result.rows.length, page: 1, limit: result.rows.length });\n }\n } catch (e) {\n setError((e as Error).message);\n } finally {\n setLoading(false);\n }\n }, [base]);\n\n return {\n selectedTable, selectedSchema, selectTable, tableData, schema,\n loading, error, page, setPage: changePage,\n orderBy, orderDir, toggleSort, clearSort,\n queryResult, queryError, queryLoading, executeQuery,\n updateCell, deleteRow, bulkDelete, insertRow,\n refreshData: fetchTableData, queryAsTable,\n };\n}\n","import { useState, useMemo, useEffect, useRef, useCallback } from \"react\";\nimport { Database, RefreshCw, GripHorizontal, Loader2 } from \"lucide-react\";\nimport { api } from \"@/lib/api-client\";\nimport { useDatabase, type DbColumnInfo } from \"./use-database\";\nimport { SqlQueryEditor } from \"./sql-query-editor\";\nimport { ExportButton } from \"./export-button\";\nimport { GlideDataGrid } from \"./glide-data-grid\";\nimport type { SchemaInfo } from \"./sql-completion-provider\";\n\n/** Parse WHERE \"col\" ILIKE '%val%' clauses from SQL */\nfunction parseSqlFilters(sql: string): Record<string, string> {\n const filters: Record<string, string> = {};\n const re = /\"(\\w+)\"\\s+ILIKE\\s+'%([^']*?)%'/gi;\n let m: RegExpExecArray | null;\n while ((m = re.exec(sql)) !== null) {\n filters[m[1]!] = m[2]!.replace(/''/g, \"'\");\n }\n return filters;\n}\n\ninterface Props { metadata?: Record<string, unknown>; tabId?: string }\n\n/** Generic database viewer — works for any DB type via unified API */\nexport function DatabaseViewer({ metadata }: Props) {\n const connectionId = metadata?.connectionId as number;\n const connectionName = metadata?.connectionName as string | undefined;\n const initialTable = metadata?.tableName as string | undefined;\n const initialSchema = (metadata?.schemaName as string) ?? \"public\";\n const initialSql = metadata?.initialSql as string | undefined;\n\n const db = useDatabase(connectionId);\n const [cachedTableNames, setCachedTableNames] = useState<{ name: string; schema: string }[]>([]);\n const [queryHeight, setQueryHeight] = useState(180);\n const containerRef = useRef<HTMLDivElement>(null);\n\n // Column ILIKE filters from DataGrid header\n const [columnFilters, setColumnFilters] = useState<Record<string, string>>({});\n\n // Build query text reflecting current table state (sort, page, filters)\n const defaultQuery = useMemo(() => {\n if (initialSql && !db.selectedTable) return initialSql;\n if (db.selectedTable) {\n let sql = `SELECT * FROM \"${db.selectedTable}\"`;\n const whereParts = Object.entries(columnFilters)\n .filter(([, v]) => v.trim())\n .map(([col, v]) => `\"${col}\" ILIKE '%${v.replace(/'/g, \"''\")}%'`);\n if (whereParts.length > 0) sql += ` WHERE ${whereParts.join(\" AND \")}`;\n if (db.orderBy) sql += ` ORDER BY \"${db.orderBy}\" ${db.orderDir}`;\n const offset = (db.page - 1) * 100;\n sql += ` LIMIT 100`;\n if (offset > 0) sql += ` OFFSET ${offset}`;\n return sql;\n }\n return \"SELECT * FROM \";\n }, [initialSql, db.selectedTable, db.orderBy, db.orderDir, db.page, columnFilters]);\n\n // When column filters change, auto-execute the query with debounce\n const handleColumnFilter = useCallback((filters: Record<string, string>) => {\n setColumnFilters(filters);\n }, []);\n const filterTimerRef = useRef<ReturnType<typeof setTimeout>>(undefined);\n useEffect(() => {\n if (!db.selectedTable || Object.keys(columnFilters).length === 0) return;\n clearTimeout(filterTimerRef.current);\n filterTimerRef.current = setTimeout(() => {\n // Execute filter query into tableData — stays in table grid mode\n db.queryAsTable(defaultQuery);\n }, 500);\n return () => clearTimeout(filterTimerRef.current);\n }, [columnFilters]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Drag resize handler\n const handleDragStart = useCallback((e: React.MouseEvent) => {\n e.preventDefault();\n const startY = e.clientY;\n const startH = queryHeight;\n const onMove = (ev: MouseEvent) => {\n const delta = ev.clientY - startY;\n const newH = Math.max(80, Math.min(startH + delta, (containerRef.current?.clientHeight ?? 600) - 100));\n setQueryHeight(newH);\n };\n const onUp = () => { document.removeEventListener(\"mousemove\", onMove); document.removeEventListener(\"mouseup\", onUp); };\n document.addEventListener(\"mousemove\", onMove);\n document.addEventListener(\"mouseup\", onUp);\n }, [queryHeight]);\n\n // Fetch table names for autocomplete once on mount\n useEffect(() => {\n api.get<{ name: string; schema: string; rowCount: number }[]>(`/api/db/connections/${connectionId}/tables?cached=1`)\n .then((tables) => setCachedTableNames(tables.map((t) => ({ name: t.name, schema: t.schema }))))\n .catch(() => {});\n }, [connectionId]);\n\n // Build SchemaInfo for autocomplete\n const schemaInfo = useMemo<SchemaInfo | undefined>(() => {\n if (cachedTableNames.length === 0) return undefined;\n return {\n tables: cachedTableNames,\n getColumns: async (table: string, schema?: string) => {\n const cols = await api.get<{ name: string; type: string }[]>(\n `/api/db/connections/${connectionId}/schema?table=${encodeURIComponent(table)}${schema ? `&schema=${encodeURIComponent(schema)}` : \"\"}`,\n );\n return cols;\n },\n };\n }, [connectionId, cachedTableNames]);\n\n // Jump to initial table\n const didInit = useRef(false);\n useEffect(() => {\n if (didInit.current) return;\n didInit.current = true;\n if (initialSql) {\n db.executeQuery(initialSql);\n } else if (initialTable) {\n db.selectTable(initialTable, initialSchema);\n }\n }, [initialTable, initialSchema, initialSql]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Track whether user ran a custom query (show results instead of table grid)\n const [showingQueryResult, setShowingQueryResult] = useState(!!initialSql);\n const handleExecuteQuery = useCallback((sql: string) => {\n const trimmed = sql.trim();\n // Check if query is a simple SELECT on the current table — stay in table grid mode\n if (db.selectedTable) {\n const tablePattern = new RegExp(`^SELECT\\\\s+\\\\*\\\\s+FROM\\\\s+\"?${db.selectedTable.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}\"?\\\\b`, \"i\");\n if (tablePattern.test(trimmed)) {\n // Parse ILIKE filters from SQL and sync to columnFilters\n const parsed = parseSqlFilters(trimmed);\n setColumnFilters(parsed);\n db.queryAsTable(trimmed);\n return;\n }\n }\n setShowingQueryResult(true);\n db.executeQuery(sql);\n }, [db.executeQuery, db.queryAsTable, db.selectedTable]);\n\n // When user interacts with DataGrid (sort/page), switch back to table view\n const handleToggleSort = useCallback((col: string) => {\n setShowingQueryResult(false);\n db.toggleSort(col);\n }, [db.toggleSort]);\n const handleClearSort = useCallback(() => {\n setShowingQueryResult(false);\n db.clearSort();\n }, [db.clearSort]);\n const handlePageChange = useCallback((p: number) => {\n setShowingQueryResult(false);\n db.setPage(p);\n }, [db.setPage]);\n\n const qr = db.queryResult;\n const showQueryResults = showingQueryResult && !!(qr || db.queryError);\n const showTableGrid = db.selectedTable && !showQueryResults;\n\n return (\n <div ref={containerRef} className=\"flex h-full w-full overflow-hidden\">\n <div className=\"flex-1 flex flex-col overflow-hidden\">\n {/* Toolbar */}\n <div className=\"flex items-center gap-2 px-3 py-1.5 border-b border-border bg-background shrink-0\">\n <Database className=\"size-3.5 text-muted-foreground\" />\n <span className=\"text-xs text-muted-foreground truncate\">{connectionName ?? \"Database\"}</span>\n {db.selectedTable && <span className=\"text-xs text-muted-foreground\">/ {db.selectedTable}</span>}\n <div className=\"ml-auto flex items-center gap-1\">\n {db.tableData && (\n <ExportButton\n columns={db.tableData.columns}\n rows={db.tableData.rows}\n filename={connectionName ? `${connectionName}-${db.selectedTable ?? \"data\"}` : db.selectedTable ?? \"data\"}\n exportAllUrl={db.selectedTable ? `/api/db/connections/${connectionId}/export?table=${encodeURIComponent(db.selectedTable)}&schema=${db.selectedSchema}` : undefined}\n />\n )}\n <button type=\"button\" onClick={() => db.refreshData()} title=\"Reload data\"\n className=\"p-1 rounded text-muted-foreground hover:text-foreground transition-colors\">\n <RefreshCw className={`size-3 ${db.loading ? \"animate-spin\" : \"\"}`} />\n </button>\n </div>\n </div>\n\n {/* Always-visible query editor at top */}\n <div className=\"shrink-0 border-b border-border\" style={{ height: queryHeight }}>\n <SqlQueryEditor\n onExecute={handleExecuteQuery} loading={db.queryLoading}\n defaultValue={defaultQuery} schemaInfo={schemaInfo}\n cacheKey={connectionId ? String(connectionId) : undefined} />\n </div>\n\n {/* Resize handle */}\n <div onMouseDown={handleDragStart}\n className=\"shrink-0 h-1.5 cursor-row-resize bg-border/50 hover:bg-primary/30 flex items-center justify-center transition-colors\">\n <GripHorizontal className=\"size-3 text-muted-foreground/50\" />\n </div>\n\n {/* Bottom panel: table data OR query results */}\n <div className=\"flex-1 overflow-hidden\">\n {showTableGrid && db.tableData && (\n <GlideDataGrid\n columns={db.tableData.columns} rows={db.tableData.rows}\n total={db.tableData.total} limit={db.tableData.limit}\n schema={db.schema} loading={db.loading}\n page={db.page} onPageChange={handlePageChange}\n onCellUpdate={db.updateCell} onRowDelete={db.deleteRow}\n orderBy={db.orderBy} orderDir={db.orderDir} onToggleSort={handleToggleSort} onClearSort={handleClearSort}\n onBulkDelete={db.bulkDelete} onInsertRow={db.insertRow}\n connectionId={connectionId} selectedTable={db.selectedTable} selectedSchema={db.selectedSchema}\n connectionName={connectionName} columnFilters={columnFilters} onColumnFilter={handleColumnFilter} />\n )}\n\n {showQueryResults && (\n <QueryResultPanel result={qr} error={db.queryError} loading={db.queryLoading} schema={db.schema} connectionName={connectionName} />\n )}\n </div>\n </div>\n </div>\n );\n}\n\nconst NOOP = () => {};\n\n/** Read-only result panel for ad-hoc query results — uses DataGrid for SELECT to get checkboxes + export */\nfunction QueryResultPanel({ result, error, loading, schema, connectionName }: {\n result: { columns: string[]; rows: Record<string, unknown>[]; rowsAffected: number; changeType: \"select\" | \"modify\"; executionTimeMs?: number } | null;\n error: string | null;\n loading?: boolean;\n schema?: DbColumnInfo[];\n connectionName?: string;\n}) {\n // Build a read-only DataGrid-compatible tableData from query result\n const queryTableData = useMemo(() => (\n result?.changeType === \"select\" && result.rows.length > 0\n ? { columns: result.columns, rows: result.rows, total: result.rows.length, limit: result.rows.length }\n : null\n ), [result]);\n\n // Use schema if available, otherwise build minimal schema from column names\n const querySchema = useMemo(() => (\n schema?.length ? schema : (result?.columns ?? []).map((c) => ({\n name: c, type: \"text\", nullable: true, pk: false, defaultValue: null,\n }))\n ), [schema, result?.columns]);\n\n return (\n <div className=\"flex flex-col h-full overflow-hidden text-xs\">\n {error && <div className=\"px-3 py-2 text-destructive bg-destructive/5 shrink-0\">{error}</div>}\n\n {result?.changeType === \"modify\" && (\n <div className=\"px-3 py-2 text-green-500 shrink-0\">\n {result.rowsAffected} row(s) affected\n {result.executionTimeMs != null && <span className=\"text-muted-foreground ml-2\">{result.executionTimeMs}ms</span>}\n </div>\n )}\n\n {queryTableData && (\n <div className=\"flex-1 overflow-hidden\">\n <GlideDataGrid\n columns={queryTableData.columns} rows={queryTableData.rows}\n total={queryTableData.total} limit={queryTableData.limit}\n schema={querySchema} loading={!!loading}\n page={1} onPageChange={NOOP} onCellUpdate={NOOP}\n orderBy={null} orderDir=\"ASC\" onToggleSort={NOOP}\n connectionName={connectionName}\n />\n {result?.executionTimeMs != null && (\n <div className=\"px-3 py-0.5 border-t border-border text-[10px] text-muted-foreground shrink-0\">\n {result.rows.length} rows · {result.executionTimeMs}ms\n </div>\n )}\n </div>\n )}\n\n {result?.changeType === \"select\" && result.rows.length === 0 && (\n <div className=\"px-3 py-2 text-muted-foreground shrink-0\">\n No results\n {result.executionTimeMs != null && <span className=\"ml-2 text-muted-foreground/60\">{result.executionTimeMs}ms</span>}\n </div>\n )}\n\n {!result && !error && (\n <div className=\"flex items-center justify-center h-full text-muted-foreground\">\n {loading ? <Loader2 className=\"size-4 animate-spin\" /> : \"Run a query to see results\"}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import{o as e}from"./rolldown-runtime-FhOqtrmT.js";import{b as t,x as n}from"./vendor-markdown-0Mxgxy0L.js";import{t as r}from"./text-wrap-DJz9Bgpa.js";import{i,t as a}from"./api-client-DIhJ5qVW.js";import{n as o}from"./settings-store-8FpQDjEA.js";import"./vendor-mermaid-D2KKkqNs.js";import{$ as s,h as c,q as l}from"./index-hxAGD2rx.js";import{r as u,t as d}from"./use-monaco-theme-DEI-tJAh.js";var f=e(n(),1),p=t();function m(e){return{js:`javascript`,jsx:`javascript`,ts:`typescript`,tsx:`typescript`,py:`python`,html:`html`,css:`css`,scss:`scss`,json:`json`,md:`markdown`,mdx:`markdown`,yaml:`yaml`,yml:`yaml`,sh:`shell`,bash:`shell`}[e.split(`.`).pop()?.toLowerCase()??``]??`plaintext`}function h({metadata:e}){let t=e?.filePath,n=e?.projectName,h=e?.ref1,_=e?.ref2,v=e?.file1,y=e?.file2,b=e?.original,x=e?.modified,S=b!=null||x!=null,C=!!(v&&y),[w,T]=(0,f.useState)(null),[E,D]=(0,f.useState)(null),[O,k]=(0,f.useState)(null),[A,j]=(0,f.useState)(!S),[M,N]=(0,f.useState)(null),{wordWrap:P,toggleWordWrap:F}=o(c(e=>({wordWrap:e.wordWrap,toggleWordWrap:e.toggleWordWrap}))),I=d(),L=(0,f.useRef)(null),R=(0,f.useRef)(null),[z,B]=(0,f.useState)(!1),[V,H]=(0,f.useState)();(0,f.useEffect)(()=>{let e=L.current;if(!e)return;let t=new ResizeObserver(([e])=>{e&&H(Math.floor(e.contentRect.height))});return t.observe(e),()=>t.disconnect()},[A,M]),(0,f.useEffect)(()=>{if(S||!n)return;if(j(!0),N(null),k(null),D(null),T(null),v&&y){let e=new URLSearchParams({file1:v,file2:y});a.get(`${i(n)}/files/compare?${e}`).then(e=>{D(e),j(!1)}).catch(e=>{N(e instanceof Error?e.message:`Failed to compare files`),j(!1)});return}if(t){let e=new URLSearchParams({file:t});h&&e.set(`ref`,h),a.get(`${i(n)}/git/file-full-diff?${e}`).then(e=>{k(e),j(!1)}).catch(e=>{N(e instanceof Error?e.message:`Failed to load diff`),j(!1)});return}let e;if(h||_){let t=new URLSearchParams;h&&t.set(`ref1`,h),_&&t.set(`ref2`,_),e=`${i(n)}/git/diff?${t}`}else e=`${i(n)}/git/diff`;a.get(e).then(e=>{T(e.diff),j(!1)}).catch(e=>{N(e instanceof Error?e.message:`Failed to load diff`),j(!1)})},[t,n,h,_,v,y,S]);let{original:U,modified:W}=(0,f.useMemo)(()=>S?{original:b??``,modified:x??``}:C&&E?E:O||(w?g(w):{original:``,modified:``}),[w,S,b,x,C,E,O]),G=(0,f.useMemo)(()=>{let e=t??y??v;return e?m(e):`plaintext`},[t,v,y]),K=typeof window<`u`&&window.innerWidth<768,q=!K;return(0,f.useEffect)(()=>{let e=R.current;if(!e)return;let t=K||P?`on`:`off`;e.updateOptions({diffWordWrap:t}),e.getOriginalEditor().updateOptions({wordWrapOverride2:t}),e.getModifiedEditor().updateOptions({wordWrapOverride2:t})},[P,K,z]),!n&&!S?(0,p.jsx)(`div`,{className:`flex items-center justify-center h-full text-muted-foreground text-sm`,children:`No project selected.`}):A?(0,p.jsxs)(`div`,{className:`flex items-center justify-center h-full gap-2 text-muted-foreground`,children:[(0,p.jsx)(l,{className:`size-5 animate-spin`}),(0,p.jsx)(`span`,{className:`text-sm`,children:`Loading diff...`})]}):M?(0,p.jsx)(`div`,{className:`flex items-center justify-center h-full text-destructive text-sm`,children:M}):!S&&!C&&!O&&!U&&!W?(0,p.jsxs)(`div`,{className:`flex flex-col items-center justify-center h-full gap-2 text-muted-foreground`,children:[(0,p.jsx)(s,{className:`size-8`}),(0,p.jsx)(`p`,{className:`text-sm`,children:`No content changes`}),t&&(0,p.jsx)(`p`,{className:`text-xs font-mono`,children:t})]}):(0,p.jsxs)(`div`,{className:`flex flex-col h-full`,children:[!K&&(0,p.jsx)(`div`,{className:`flex items-center justify-end gap-0.5 px-2 py-0.5 border-b border-border shrink-0`,children:(0,p.jsx)(`button`,{type:`button`,onClick:F,title:`Toggle word wrap`,className:`p-1 rounded hover:bg-muted transition-colors ${P?`bg-muted text-foreground`:``}`,children:(0,p.jsx)(r,{className:`size-3.5`})})}),(0,p.jsx)(`div`,{ref:L,className:`flex-1 overflow-hidden`,children:V&&V>0?(0,p.jsx)(u,{height:V,language:G,original:U,modified:W,theme:I,onMount:e=>{R.current=e,B(!0)},options:{fontSize:K?11:13,fontFamily:`Menlo, Monaco, Consolas, monospace`,diffWordWrap:K||P?`on`:`off`,renderSideBySide:q,useInlineViewWhenSpaceIsLimited:!1,readOnly:!0,automaticLayout:!0,scrollBeyondLastLine:!1},loading:(0,p.jsx)(l,{className:`size-5 animate-spin text-muted-foreground`})}):(0,p.jsx)(`div`,{className:`flex items-center justify-center h-full`,children:(0,p.jsx)(l,{className:`size-5 animate-spin text-muted-foreground`})})})]})}function g(e){let t=e.split(`
1
+ import{o as e}from"./rolldown-runtime-FhOqtrmT.js";import{b as t,x as n}from"./vendor-markdown-0Mxgxy0L.js";import{t as r}from"./text-wrap-DJz9Bgpa.js";import{i,t as a}from"./api-client-DIhJ5qVW.js";import{n as o}from"./settings-store-8FpQDjEA.js";import"./vendor-mermaid-D2KKkqNs.js";import{$ as s,h as c,q as l}from"./index-DkQ6jVSH.js";import{r as u,t as d}from"./use-monaco-theme-DEI-tJAh.js";var f=e(n(),1),p=t();function m(e){return{js:`javascript`,jsx:`javascript`,ts:`typescript`,tsx:`typescript`,py:`python`,html:`html`,css:`css`,scss:`scss`,json:`json`,md:`markdown`,mdx:`markdown`,yaml:`yaml`,yml:`yaml`,sh:`shell`,bash:`shell`}[e.split(`.`).pop()?.toLowerCase()??``]??`plaintext`}function h({metadata:e}){let t=e?.filePath,n=e?.projectName,h=e?.ref1,_=e?.ref2,v=e?.file1,y=e?.file2,b=e?.original,x=e?.modified,S=b!=null||x!=null,C=!!(v&&y),[w,T]=(0,f.useState)(null),[E,D]=(0,f.useState)(null),[O,k]=(0,f.useState)(null),[A,j]=(0,f.useState)(!S),[M,N]=(0,f.useState)(null),{wordWrap:P,toggleWordWrap:F}=o(c(e=>({wordWrap:e.wordWrap,toggleWordWrap:e.toggleWordWrap}))),I=d(),L=(0,f.useRef)(null),R=(0,f.useRef)(null),[z,B]=(0,f.useState)(!1),[V,H]=(0,f.useState)();(0,f.useEffect)(()=>{let e=L.current;if(!e)return;let t=new ResizeObserver(([e])=>{e&&H(Math.floor(e.contentRect.height))});return t.observe(e),()=>t.disconnect()},[A,M]),(0,f.useEffect)(()=>{if(S||!n)return;if(j(!0),N(null),k(null),D(null),T(null),v&&y){let e=new URLSearchParams({file1:v,file2:y});a.get(`${i(n)}/files/compare?${e}`).then(e=>{D(e),j(!1)}).catch(e=>{N(e instanceof Error?e.message:`Failed to compare files`),j(!1)});return}if(t){let e=new URLSearchParams({file:t});h&&e.set(`ref`,h),a.get(`${i(n)}/git/file-full-diff?${e}`).then(e=>{k(e),j(!1)}).catch(e=>{N(e instanceof Error?e.message:`Failed to load diff`),j(!1)});return}let e;if(h||_){let t=new URLSearchParams;h&&t.set(`ref1`,h),_&&t.set(`ref2`,_),e=`${i(n)}/git/diff?${t}`}else e=`${i(n)}/git/diff`;a.get(e).then(e=>{T(e.diff),j(!1)}).catch(e=>{N(e instanceof Error?e.message:`Failed to load diff`),j(!1)})},[t,n,h,_,v,y,S]);let{original:U,modified:W}=(0,f.useMemo)(()=>S?{original:b??``,modified:x??``}:C&&E?E:O||(w?g(w):{original:``,modified:``}),[w,S,b,x,C,E,O]),G=(0,f.useMemo)(()=>{let e=t??y??v;return e?m(e):`plaintext`},[t,v,y]),K=typeof window<`u`&&window.innerWidth<768,q=!K;return(0,f.useEffect)(()=>{let e=R.current;if(!e)return;let t=K||P?`on`:`off`;e.updateOptions({diffWordWrap:t}),e.getOriginalEditor().updateOptions({wordWrapOverride2:t}),e.getModifiedEditor().updateOptions({wordWrapOverride2:t})},[P,K,z]),!n&&!S?(0,p.jsx)(`div`,{className:`flex items-center justify-center h-full text-muted-foreground text-sm`,children:`No project selected.`}):A?(0,p.jsxs)(`div`,{className:`flex items-center justify-center h-full gap-2 text-muted-foreground`,children:[(0,p.jsx)(l,{className:`size-5 animate-spin`}),(0,p.jsx)(`span`,{className:`text-sm`,children:`Loading diff...`})]}):M?(0,p.jsx)(`div`,{className:`flex items-center justify-center h-full text-destructive text-sm`,children:M}):!S&&!C&&!O&&!U&&!W?(0,p.jsxs)(`div`,{className:`flex flex-col items-center justify-center h-full gap-2 text-muted-foreground`,children:[(0,p.jsx)(s,{className:`size-8`}),(0,p.jsx)(`p`,{className:`text-sm`,children:`No content changes`}),t&&(0,p.jsx)(`p`,{className:`text-xs font-mono`,children:t})]}):(0,p.jsxs)(`div`,{className:`flex flex-col h-full`,children:[!K&&(0,p.jsx)(`div`,{className:`flex items-center justify-end gap-0.5 px-2 py-0.5 border-b border-border shrink-0`,children:(0,p.jsx)(`button`,{type:`button`,onClick:F,title:`Toggle word wrap`,className:`p-1 rounded hover:bg-muted transition-colors ${P?`bg-muted text-foreground`:``}`,children:(0,p.jsx)(r,{className:`size-3.5`})})}),(0,p.jsx)(`div`,{ref:L,className:`flex-1 overflow-hidden`,children:V&&V>0?(0,p.jsx)(u,{height:V,language:G,original:U,modified:W,theme:I,onMount:e=>{R.current=e,B(!0)},options:{fontSize:K?11:13,fontFamily:`Menlo, Monaco, Consolas, monospace`,diffWordWrap:K||P?`on`:`off`,renderSideBySide:q,useInlineViewWhenSpaceIsLimited:!1,readOnly:!0,automaticLayout:!0,scrollBeyondLastLine:!1},loading:(0,p.jsx)(l,{className:`size-5 animate-spin text-muted-foreground`})}):(0,p.jsx)(`div`,{className:`flex items-center justify-center h-full`,children:(0,p.jsx)(l,{className:`size-5 animate-spin text-muted-foreground`})})})]})}function g(e){let t=e.split(`
2
2
  `),n=[],r=[],i=!1;for(let e of t)if(!(e.startsWith(`diff --git`)||e.startsWith(`diff --no-index`)||e.startsWith(`index `)||e.startsWith(`new file`)||e.startsWith(`deleted file`)||e.startsWith(`old mode`)||e.startsWith(`new mode`)||e.startsWith(`---`)||e.startsWith(`+++`)||e.startsWith(`Binary files`)||e.startsWith(`\\ No newline`))){if(e.startsWith(`@@`)){i=!0;continue}if(i)if(e.startsWith(`-`))n.push(e.slice(1));else if(e.startsWith(`+`))r.push(e.slice(1));else{let t=e.startsWith(` `)?e.slice(1):e;n.push(t),r.push(t)}}return{original:n.join(`
3
3
  `),modified:r.join(`
4
4
  `)}}export{h as DiffViewer};
5
- //# sourceMappingURL=diff-viewer-B6q9wXD6.js.map
5
+ //# sourceMappingURL=diff-viewer-BfatMgWw.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"diff-viewer-B6q9wXD6.js","names":[],"sources":["../../../src/web/components/editor/diff-viewer.tsx"],"sourcesContent":["import { useEffect, useState, useMemo, useRef } from \"react\";\nimport { DiffEditor } from \"@monaco-editor/react\";\nimport { api, projectUrl } from \"@/lib/api-client\";\nimport { useShallow } from \"zustand/react/shallow\";\nimport { useSettingsStore } from \"@/stores/settings-store\";\nimport { useMonacoTheme } from \"@/lib/use-monaco-theme\";\nimport { Loader2, FileCode, WrapText } from \"lucide-react\";\n\nfunction getMonacoLanguage(filename: string): string {\n const ext = filename.split(\".\").pop()?.toLowerCase() ?? \"\";\n const map: Record<string, string> = {\n js: \"javascript\", jsx: \"javascript\",\n ts: \"typescript\", tsx: \"typescript\",\n py: \"python\", html: \"html\",\n css: \"css\", scss: \"scss\",\n json: \"json\", md: \"markdown\", mdx: \"markdown\",\n yaml: \"yaml\", yml: \"yaml\",\n sh: \"shell\", bash: \"shell\",\n };\n return map[ext] ?? \"plaintext\";\n}\n\ninterface DiffViewerProps {\n metadata?: Record<string, unknown>;\n}\n\nexport function DiffViewer({ metadata }: DiffViewerProps) {\n const filePath = metadata?.filePath as string | undefined;\n const projectName = metadata?.projectName as string | undefined;\n const ref1 = metadata?.ref1 as string | undefined;\n const ref2 = metadata?.ref2 as string | undefined;\n const file1 = metadata?.file1 as string | undefined;\n const file2 = metadata?.file2 as string | undefined;\n const inlineOriginal = metadata?.original as string | undefined;\n const inlineModified = metadata?.modified as string | undefined;\n const isInline = inlineOriginal != null || inlineModified != null;\n const isFileCompare = Boolean(file1 && file2);\n\n const [diffText, setDiffText] = useState<string | null>(null);\n const [fileContents, setFileContents] = useState<{ original: string; modified: string } | null>(null);\n const [fullFileDiff, setFullFileDiff] = useState<{ original: string; modified: string } | null>(null);\n const [loading, setLoading] = useState(!isInline);\n const [error, setError] = useState<string | null>(null);\n const { wordWrap, toggleWordWrap } = useSettingsStore(useShallow((s) => ({ wordWrap: s.wordWrap, toggleWordWrap: s.toggleWordWrap })));\n const monacoTheme = useMonacoTheme();\n\n // Measure container height — Monaco needs explicit pixel height on mobile\n const containerRef = useRef<HTMLDivElement>(null);\n const diffEditorRef = useRef<import(\"monaco-editor\").editor.IStandaloneDiffEditor | null>(null);\n const [editorReady, setEditorReady] = useState(false);\n const [containerHeight, setContainerHeight] = useState<number | undefined>();\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n const ro = new ResizeObserver(([entry]) => {\n if (entry) setContainerHeight(Math.floor(entry.contentRect.height));\n });\n ro.observe(el);\n return () => ro.disconnect();\n }, [loading, error]);\n\n useEffect(() => {\n if (isInline) return;\n if (!projectName) return;\n setLoading(true);\n setError(null);\n setFullFileDiff(null);\n setFileContents(null);\n setDiffText(null);\n\n if (file1 && file2) {\n const params = new URLSearchParams({ file1, file2 });\n api\n .get<{ original: string; modified: string }>(\n `${projectUrl(projectName)}/files/compare?${params}`,\n )\n .then((data) => { setFileContents(data); setLoading(false); })\n .catch((err) => { setError(err instanceof Error ? err.message : \"Failed to compare files\"); setLoading(false); });\n return;\n }\n\n // Single-file diff → fetch FULL file contents on both sides (VSCode-style).\n // Monaco DiffEditor computes the diff itself, giving full-file view instead\n // of just the changed hunks + 3 lines of context that `git diff` returns.\n if (filePath) {\n const params = new URLSearchParams({ file: filePath });\n if (ref1) params.set(\"ref\", ref1);\n api\n .get<{ original: string; modified: string }>(\n `${projectUrl(projectName)}/git/file-full-diff?${params}`,\n )\n .then((data) => { setFullFileDiff(data); setLoading(false); })\n .catch((err) => { setError(err instanceof Error ? err.message : \"Failed to load diff\"); setLoading(false); });\n return;\n }\n\n let url: string;\n if (ref1 || ref2) {\n const params = new URLSearchParams();\n if (ref1) params.set(\"ref1\", ref1);\n if (ref2) params.set(\"ref2\", ref2);\n url = `${projectUrl(projectName)}/git/diff?${params}`;\n } else {\n url = `${projectUrl(projectName)}/git/diff`;\n }\n\n api\n .get<{ diff: string }>(url)\n .then((data) => { setDiffText(data.diff); setLoading(false); })\n .catch((err) => { setError(err instanceof Error ? err.message : \"Failed to load diff\"); setLoading(false); });\n }, [filePath, projectName, ref1, ref2, file1, file2, isInline]);\n\n const { original, modified } = useMemo(() => {\n if (isInline) return { original: inlineOriginal ?? \"\", modified: inlineModified ?? \"\" };\n if (isFileCompare && fileContents) return fileContents;\n if (fullFileDiff) return fullFileDiff;\n if (!diffText) return { original: \"\", modified: \"\" };\n return parseDiff(diffText);\n }, [diffText, isInline, inlineOriginal, inlineModified, isFileCompare, fileContents, fullFileDiff]);\n\n const language = useMemo(() => {\n const langFile = filePath ?? file2 ?? file1;\n return langFile ? getMonacoLanguage(langFile) : \"plaintext\";\n }, [filePath, file1, file2]);\n\n // Force inline on mobile (<768px) since side-by-side is too narrow\n const isMobile = typeof window !== \"undefined\" && window.innerWidth < 768;\n const renderSideBySide = !isMobile;\n\n // Sync word wrap on both sub-editors.\n // Monaco DiffEditor has a bug: during init when container width is 0,\n // useInlineViewWhenSpaceIsLimited briefly triggers inline mode which sets\n // wordWrapOverride2='off' on the original editor. When side-by-side resumes,\n // wordWrapOverride2 is never cleared, permanently blocking word wrap on the\n // left side. We disable that option and also force wordWrapOverride2 to clear it.\n useEffect(() => {\n const editor = diffEditorRef.current;\n if (!editor) return;\n const val: \"on\" | \"off\" = isMobile ? \"on\" : wordWrap ? \"on\" : \"off\";\n editor.updateOptions({ diffWordWrap: val });\n editor.getOriginalEditor().updateOptions({ wordWrapOverride2: val } as any);\n editor.getModifiedEditor().updateOptions({ wordWrapOverride2: val } as any);\n }, [wordWrap, isMobile, editorReady]);\n\n if (!projectName && !isInline) {\n return (\n <div className=\"flex items-center justify-center h-full text-muted-foreground text-sm\">\n No project selected.\n </div>\n );\n }\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center h-full gap-2 text-muted-foreground\">\n <Loader2 className=\"size-5 animate-spin\" />\n <span className=\"text-sm\">Loading diff...</span>\n </div>\n );\n }\n\n if (error) {\n return (\n <div className=\"flex items-center justify-center h-full text-destructive text-sm\">{error}</div>\n );\n }\n\n // Catch diffs with metadata-only changes (mode, rename) where parseDiff returns empty\n if (!isInline && !isFileCompare && !fullFileDiff && !original && !modified) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full gap-2 text-muted-foreground\">\n <FileCode className=\"size-8\" />\n <p className=\"text-sm\">No content changes</p>\n {filePath && <p className=\"text-xs font-mono\">{filePath}</p>}\n </div>\n );\n }\n\n return (\n <div className=\"flex flex-col h-full\">\n {/* Toolbar */}\n {!isMobile && (\n <div className=\"flex items-center justify-end gap-0.5 px-2 py-0.5 border-b border-border shrink-0\">\n <button type=\"button\" onClick={toggleWordWrap} title=\"Toggle word wrap\"\n className={`p-1 rounded hover:bg-muted transition-colors ${wordWrap ? \"bg-muted text-foreground\" : \"\"}`}\n >\n <WrapText className=\"size-3.5\" />\n </button>\n </div>\n )}\n {/* Monaco DiffEditor */}\n <div ref={containerRef} className=\"flex-1 overflow-hidden\">\n {containerHeight && containerHeight > 0 ? (\n <DiffEditor\n height={containerHeight}\n language={language}\n original={original}\n modified={modified}\n theme={monacoTheme}\n onMount={(editor) => {\n diffEditorRef.current = editor;\n setEditorReady(true);\n }}\n options={{\n fontSize: isMobile ? 11 : 13,\n fontFamily: \"Menlo, Monaco, Consolas, monospace\",\n diffWordWrap: isMobile ? \"on\" : wordWrap ? \"on\" : \"off\",\n renderSideBySide,\n useInlineViewWhenSpaceIsLimited: false,\n readOnly: true,\n automaticLayout: true,\n scrollBeyondLastLine: false,\n }}\n loading={<Loader2 className=\"size-5 animate-spin text-muted-foreground\" />}\n />\n ) : (\n <div className=\"flex items-center justify-center h-full\">\n <Loader2 className=\"size-5 animate-spin text-muted-foreground\" />\n </div>\n )}\n </div>\n </div>\n );\n}\n\nfunction parseDiff(diff: string): { original: string; modified: string } {\n const lines = diff.split(\"\\n\");\n const originalLines: string[] = [];\n const modifiedLines: string[] = [];\n let inHunk = false;\n\n for (const line of lines) {\n if (\n line.startsWith(\"diff --git\") || line.startsWith(\"diff --no-index\") ||\n line.startsWith(\"index \") || line.startsWith(\"new file\") ||\n line.startsWith(\"deleted file\") || line.startsWith(\"old mode\") ||\n line.startsWith(\"new mode\") || line.startsWith(\"---\") ||\n line.startsWith(\"+++\") || line.startsWith(\"Binary files\") ||\n line.startsWith(\"\\\\ No newline\")\n ) continue;\n\n if (line.startsWith(\"@@\")) { inHunk = true; continue; }\n if (!inHunk) continue;\n\n if (line.startsWith(\"-\")) {\n originalLines.push(line.slice(1));\n } else if (line.startsWith(\"+\")) {\n modifiedLines.push(line.slice(1));\n } else {\n const content = line.startsWith(\" \") ? line.slice(1) : line;\n originalLines.push(content);\n modifiedLines.push(content);\n }\n }\n\n return { original: originalLines.join(\"\\n\"), modified: modifiedLines.join(\"\\n\") };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"diff-viewer-BfatMgWw.js","names":[],"sources":["../../../src/web/components/editor/diff-viewer.tsx"],"sourcesContent":["import { useEffect, useState, useMemo, useRef } from \"react\";\nimport { DiffEditor } from \"@monaco-editor/react\";\nimport { api, projectUrl } from \"@/lib/api-client\";\nimport { useShallow } from \"zustand/react/shallow\";\nimport { useSettingsStore } from \"@/stores/settings-store\";\nimport { useMonacoTheme } from \"@/lib/use-monaco-theme\";\nimport { Loader2, FileCode, WrapText } from \"lucide-react\";\n\nfunction getMonacoLanguage(filename: string): string {\n const ext = filename.split(\".\").pop()?.toLowerCase() ?? \"\";\n const map: Record<string, string> = {\n js: \"javascript\", jsx: \"javascript\",\n ts: \"typescript\", tsx: \"typescript\",\n py: \"python\", html: \"html\",\n css: \"css\", scss: \"scss\",\n json: \"json\", md: \"markdown\", mdx: \"markdown\",\n yaml: \"yaml\", yml: \"yaml\",\n sh: \"shell\", bash: \"shell\",\n };\n return map[ext] ?? \"plaintext\";\n}\n\ninterface DiffViewerProps {\n metadata?: Record<string, unknown>;\n}\n\nexport function DiffViewer({ metadata }: DiffViewerProps) {\n const filePath = metadata?.filePath as string | undefined;\n const projectName = metadata?.projectName as string | undefined;\n const ref1 = metadata?.ref1 as string | undefined;\n const ref2 = metadata?.ref2 as string | undefined;\n const file1 = metadata?.file1 as string | undefined;\n const file2 = metadata?.file2 as string | undefined;\n const inlineOriginal = metadata?.original as string | undefined;\n const inlineModified = metadata?.modified as string | undefined;\n const isInline = inlineOriginal != null || inlineModified != null;\n const isFileCompare = Boolean(file1 && file2);\n\n const [diffText, setDiffText] = useState<string | null>(null);\n const [fileContents, setFileContents] = useState<{ original: string; modified: string } | null>(null);\n const [fullFileDiff, setFullFileDiff] = useState<{ original: string; modified: string } | null>(null);\n const [loading, setLoading] = useState(!isInline);\n const [error, setError] = useState<string | null>(null);\n const { wordWrap, toggleWordWrap } = useSettingsStore(useShallow((s) => ({ wordWrap: s.wordWrap, toggleWordWrap: s.toggleWordWrap })));\n const monacoTheme = useMonacoTheme();\n\n // Measure container height — Monaco needs explicit pixel height on mobile\n const containerRef = useRef<HTMLDivElement>(null);\n const diffEditorRef = useRef<import(\"monaco-editor\").editor.IStandaloneDiffEditor | null>(null);\n const [editorReady, setEditorReady] = useState(false);\n const [containerHeight, setContainerHeight] = useState<number | undefined>();\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n const ro = new ResizeObserver(([entry]) => {\n if (entry) setContainerHeight(Math.floor(entry.contentRect.height));\n });\n ro.observe(el);\n return () => ro.disconnect();\n }, [loading, error]);\n\n useEffect(() => {\n if (isInline) return;\n if (!projectName) return;\n setLoading(true);\n setError(null);\n setFullFileDiff(null);\n setFileContents(null);\n setDiffText(null);\n\n if (file1 && file2) {\n const params = new URLSearchParams({ file1, file2 });\n api\n .get<{ original: string; modified: string }>(\n `${projectUrl(projectName)}/files/compare?${params}`,\n )\n .then((data) => { setFileContents(data); setLoading(false); })\n .catch((err) => { setError(err instanceof Error ? err.message : \"Failed to compare files\"); setLoading(false); });\n return;\n }\n\n // Single-file diff → fetch FULL file contents on both sides (VSCode-style).\n // Monaco DiffEditor computes the diff itself, giving full-file view instead\n // of just the changed hunks + 3 lines of context that `git diff` returns.\n if (filePath) {\n const params = new URLSearchParams({ file: filePath });\n if (ref1) params.set(\"ref\", ref1);\n api\n .get<{ original: string; modified: string }>(\n `${projectUrl(projectName)}/git/file-full-diff?${params}`,\n )\n .then((data) => { setFullFileDiff(data); setLoading(false); })\n .catch((err) => { setError(err instanceof Error ? err.message : \"Failed to load diff\"); setLoading(false); });\n return;\n }\n\n let url: string;\n if (ref1 || ref2) {\n const params = new URLSearchParams();\n if (ref1) params.set(\"ref1\", ref1);\n if (ref2) params.set(\"ref2\", ref2);\n url = `${projectUrl(projectName)}/git/diff?${params}`;\n } else {\n url = `${projectUrl(projectName)}/git/diff`;\n }\n\n api\n .get<{ diff: string }>(url)\n .then((data) => { setDiffText(data.diff); setLoading(false); })\n .catch((err) => { setError(err instanceof Error ? err.message : \"Failed to load diff\"); setLoading(false); });\n }, [filePath, projectName, ref1, ref2, file1, file2, isInline]);\n\n const { original, modified } = useMemo(() => {\n if (isInline) return { original: inlineOriginal ?? \"\", modified: inlineModified ?? \"\" };\n if (isFileCompare && fileContents) return fileContents;\n if (fullFileDiff) return fullFileDiff;\n if (!diffText) return { original: \"\", modified: \"\" };\n return parseDiff(diffText);\n }, [diffText, isInline, inlineOriginal, inlineModified, isFileCompare, fileContents, fullFileDiff]);\n\n const language = useMemo(() => {\n const langFile = filePath ?? file2 ?? file1;\n return langFile ? getMonacoLanguage(langFile) : \"plaintext\";\n }, [filePath, file1, file2]);\n\n // Force inline on mobile (<768px) since side-by-side is too narrow\n const isMobile = typeof window !== \"undefined\" && window.innerWidth < 768;\n const renderSideBySide = !isMobile;\n\n // Sync word wrap on both sub-editors.\n // Monaco DiffEditor has a bug: during init when container width is 0,\n // useInlineViewWhenSpaceIsLimited briefly triggers inline mode which sets\n // wordWrapOverride2='off' on the original editor. When side-by-side resumes,\n // wordWrapOverride2 is never cleared, permanently blocking word wrap on the\n // left side. We disable that option and also force wordWrapOverride2 to clear it.\n useEffect(() => {\n const editor = diffEditorRef.current;\n if (!editor) return;\n const val: \"on\" | \"off\" = isMobile ? \"on\" : wordWrap ? \"on\" : \"off\";\n editor.updateOptions({ diffWordWrap: val });\n editor.getOriginalEditor().updateOptions({ wordWrapOverride2: val } as any);\n editor.getModifiedEditor().updateOptions({ wordWrapOverride2: val } as any);\n }, [wordWrap, isMobile, editorReady]);\n\n if (!projectName && !isInline) {\n return (\n <div className=\"flex items-center justify-center h-full text-muted-foreground text-sm\">\n No project selected.\n </div>\n );\n }\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center h-full gap-2 text-muted-foreground\">\n <Loader2 className=\"size-5 animate-spin\" />\n <span className=\"text-sm\">Loading diff...</span>\n </div>\n );\n }\n\n if (error) {\n return (\n <div className=\"flex items-center justify-center h-full text-destructive text-sm\">{error}</div>\n );\n }\n\n // Catch diffs with metadata-only changes (mode, rename) where parseDiff returns empty\n if (!isInline && !isFileCompare && !fullFileDiff && !original && !modified) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full gap-2 text-muted-foreground\">\n <FileCode className=\"size-8\" />\n <p className=\"text-sm\">No content changes</p>\n {filePath && <p className=\"text-xs font-mono\">{filePath}</p>}\n </div>\n );\n }\n\n return (\n <div className=\"flex flex-col h-full\">\n {/* Toolbar */}\n {!isMobile && (\n <div className=\"flex items-center justify-end gap-0.5 px-2 py-0.5 border-b border-border shrink-0\">\n <button type=\"button\" onClick={toggleWordWrap} title=\"Toggle word wrap\"\n className={`p-1 rounded hover:bg-muted transition-colors ${wordWrap ? \"bg-muted text-foreground\" : \"\"}`}\n >\n <WrapText className=\"size-3.5\" />\n </button>\n </div>\n )}\n {/* Monaco DiffEditor */}\n <div ref={containerRef} className=\"flex-1 overflow-hidden\">\n {containerHeight && containerHeight > 0 ? (\n <DiffEditor\n height={containerHeight}\n language={language}\n original={original}\n modified={modified}\n theme={monacoTheme}\n onMount={(editor) => {\n diffEditorRef.current = editor;\n setEditorReady(true);\n }}\n options={{\n fontSize: isMobile ? 11 : 13,\n fontFamily: \"Menlo, Monaco, Consolas, monospace\",\n diffWordWrap: isMobile ? \"on\" : wordWrap ? \"on\" : \"off\",\n renderSideBySide,\n useInlineViewWhenSpaceIsLimited: false,\n readOnly: true,\n automaticLayout: true,\n scrollBeyondLastLine: false,\n }}\n loading={<Loader2 className=\"size-5 animate-spin text-muted-foreground\" />}\n />\n ) : (\n <div className=\"flex items-center justify-center h-full\">\n <Loader2 className=\"size-5 animate-spin text-muted-foreground\" />\n </div>\n )}\n </div>\n </div>\n );\n}\n\nfunction parseDiff(diff: string): { original: string; modified: string } {\n const lines = diff.split(\"\\n\");\n const originalLines: string[] = [];\n const modifiedLines: string[] = [];\n let inHunk = false;\n\n for (const line of lines) {\n if (\n line.startsWith(\"diff --git\") || line.startsWith(\"diff --no-index\") ||\n line.startsWith(\"index \") || line.startsWith(\"new file\") ||\n line.startsWith(\"deleted file\") || line.startsWith(\"old mode\") ||\n line.startsWith(\"new mode\") || line.startsWith(\"---\") ||\n line.startsWith(\"+++\") || line.startsWith(\"Binary files\") ||\n line.startsWith(\"\\\\ No newline\")\n ) continue;\n\n if (line.startsWith(\"@@\")) { inHunk = true; continue; }\n if (!inHunk) continue;\n\n if (line.startsWith(\"-\")) {\n originalLines.push(line.slice(1));\n } else if (line.startsWith(\"+\")) {\n modifiedLines.push(line.slice(1));\n } else {\n const content = line.startsWith(\" \") ? line.slice(1) : line;\n originalLines.push(content);\n modifiedLines.push(content);\n }\n }\n\n return { original: originalLines.join(\"\\n\"), modified: modifiedLines.join(\"\\n\") };\n}\n"],"mappings":"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"}
@@ -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{r}from"./api-client-DIhJ5qVW.js";import{q as i,y as a}from"./index-hxAGD2rx.js";var o=e(n(),1),s=t(),c=`<script>
1
+ import{o as e}from"./rolldown-runtime-FhOqtrmT.js";import{b as t,x as n}from"./vendor-markdown-0Mxgxy0L.js";import{r}from"./api-client-DIhJ5qVW.js";import{q as i,y as a}from"./index-DkQ6jVSH.js";var o=e(n(),1),s=t(),c=`<script>
2
2
  function acquireVsCodeApi(){return{postMessage:function(m){window.parent.postMessage(m,"*")},getState:function(){try{return JSON.parse(sessionStorage.getItem("vscode-state")||"null")}catch{return null}},setState:function(s){sessionStorage.setItem("vscode-state",JSON.stringify(s));return s}}}
3
3
  <\/script>`;function l(e){if(!e)return e;let t=e.indexOf(`<head>`);return t===-1?c+e:e.slice(0,t+6)+c+e.slice(t+6)}function u({metadata:e}){let t=e?.panelId,n=e?.viewType,c=e?.projectName||void 0,[u,d]=(0,o.useState)(!1),f=a(e=>e.contributions!==null),p=a(e=>{if(t&&e.webviewPanels[t])return e.webviewPanels[t];if(n){let t=n.includes(`.`)?n:`${n}.view`;return Object.values(e.webviewPanels).find(e=>e.viewType===n||e.viewType===t)}}),m=p?.id??t,h=(0,o.useRef)(null),g=p?.html??``,_=l(g),v=(0,o.useRef)(null);(0,o.useEffect)(()=>{if(p||!n||!f||c&&c===v.current)return;c&&(v.current=c);let e=n.includes(`.`)?n:`${n}.view`,t=!1;async function i(){let n=[];if(c)try{let e=r(),t=(await(await fetch(`/api/projects`,e?{headers:{Authorization:`Bearer ${e}`}}:{})).json()).data?.find(e=>e.name===c);t&&(n=[t.path])}catch{}t||window.dispatchEvent(new CustomEvent(`ext:command:execute`,{detail:{command:e,args:n}}))}return i(),()=>{t=!0}},[p,n,c,f]);let y=e?.extensionId,b=a(e=>{if(y&&e.activationErrors[y])return e.activationErrors[y];if(n){for(let[t,r]of Object.entries(e.activationErrors))if(t===`ext-${n}`)return r}}),x=(0,o.useCallback)(()=>{if(d(!1),!n)return;let e=n.includes(`.`)?n:`${n}.view`;(async()=>{try{let t=r(),n=(await(await fetch(`/api/projects`,t?{headers:{Authorization:`Bearer ${t}`}}:{})).json()).data?.find(e=>e.name===c),i=n?[n.path]:[];window.dispatchEvent(new CustomEvent(`ext:command:execute`,{detail:{command:e,args:i}}))}catch{}})()},[n,c]),S=(0,o.useRef)(null),C=(0,o.useRef)(n);return(0,o.useEffect)(()=>{S.current=m??null},[m]),(0,o.useEffect)(()=>{C.current=n},[n]),(0,o.useEffect)(()=>()=>{let e=S.current;if(e){let t=C.current;a.getState().removeWebviewPanel(e),window.dispatchEvent(new CustomEvent(`ext:webview:close`,{detail:{panelId:e,viewType:t}}))}},[]),(0,o.useEffect)(()=>{if(p){d(!1);return}if(!f||!n)return;let e=0,t=setInterval(()=>{if(e++,e>3){clearInterval(t),d(!0);return}x()},2e3);return()=>clearInterval(t)},[p,f,n,x]),(0,o.useEffect)(()=>{if(!m)return;let e=e=>{h.current&&e.source===h.current.contentWindow&&window.dispatchEvent(new CustomEvent(`ext:webview:send`,{detail:{panelId:m,message:e.data}}))};return window.addEventListener(`message`,e),()=>window.removeEventListener(`message`,e)},[m]),(0,o.useEffect)(()=>{if(!m)return;let e=e=>{let{panelId:t,message:n}=e.detail;t===m&&h.current?.contentWindow?.postMessage(n,`*`)};return window.addEventListener(`ext:webview:message`,e),()=>window.removeEventListener(`ext:webview:message`,e)},[m]),!p||!g?(0,s.jsx)(`div`,{className:`flex flex-col items-center justify-center h-full gap-3 text-sm text-text-subtle`,children:u?(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(`span`,{className:`text-destructive font-medium`,children:`Extension failed to load`}),b&&(0,s.jsx)(`span`,{className:`text-xs text-muted-foreground max-w-md text-center`,children:b}),(0,s.jsx)(`button`,{onClick:x,className:`text-xs text-primary hover:underline`,children:`Retry`})]}):(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(i,{className:`size-5 animate-spin`}),(0,s.jsx)(`span`,{children:`Loading extension...`})]})}):(0,s.jsx)(`div`,{className:`h-full w-full relative`,children:(0,s.jsx)(`iframe`,{ref:h,srcDoc:_,sandbox:`allow-scripts`,className:`w-full h-full border-0 bg-white dark:bg-zinc-900`,title:p.title},m)})}export{u as ExtensionWebview};
4
- //# sourceMappingURL=extension-webview-CMBEb4FF.js.map
4
+ //# sourceMappingURL=extension-webview-DKSDoW_g.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension-webview-CMBEb4FF.js","names":[],"sources":["../../../src/web/components/extensions/extension-webview.tsx"],"sourcesContent":["import { useRef, useEffect, useState, useCallback } from \"react\";\nimport { useExtensionStore } from \"@/stores/extension-store\";\nimport { getAuthToken } from \"@/lib/api-client\";\nimport { Loader2 } from \"lucide-react\";\n\n/** Inject acquireVsCodeApi() shim so extension webviews can postMessage to parent */\nconst VSCODE_API_SHIM = `<script>\nfunction acquireVsCodeApi(){return{postMessage:function(m){window.parent.postMessage(m,\"*\")},getState:function(){try{return JSON.parse(sessionStorage.getItem(\"vscode-state\")||\"null\")}catch{return null}},setState:function(s){sessionStorage.setItem(\"vscode-state\",JSON.stringify(s));return s}}}\n</script>`;\n\nfunction injectVscodeApiShim(html: string): string {\n if (!html) return html;\n // Insert shim right after <head> tag (or at start if no <head>)\n const headIdx = html.indexOf(\"<head>\");\n if (headIdx !== -1) {\n return html.slice(0, headIdx + 6) + VSCODE_API_SHIM + html.slice(headIdx + 6);\n }\n return VSCODE_API_SHIM + html;\n}\n\ninterface ExtensionWebviewProps {\n metadata?: Record<string, unknown>;\n}\n\n/**\n * iframe-based webview container for extension-contributed webview panels.\n * Matches panel by panelId (direct) or viewType (reload recovery).\n */\nexport function ExtensionWebview({ metadata }: ExtensionWebviewProps) {\n const panelId = metadata?.panelId as string | undefined;\n const viewType = metadata?.viewType as string | undefined;\n // Use the tab's own project name (frozen at creation time) — NOT the global\n // currentProject. Old project's ExtensionWebview must not react to project\n // switches, which would dispatch commands for the wrong project.\n const projectName = (metadata?.projectName as string | undefined) || undefined;\n const [timedOut, setTimedOut] = useState(false);\n // Track whether extensions are activated (contributions received from WS)\n const extensionsReady = useExtensionStore((s) => s.contributions !== null);\n\n // Match panel: prefer panelId (exact), fallback to viewType match (reload recovery)\n const panel = useExtensionStore((s) => {\n if (panelId && s.webviewPanels[panelId]) return s.webviewPanels[panelId];\n if (viewType) {\n // Find panel whose viewType matches (with or without .view suffix)\n const fullViewType = viewType.includes(\".\") ? viewType : `${viewType}.view`;\n return Object.values(s.webviewPanels).find(\n (p) => p.viewType === viewType || p.viewType === fullViewType,\n );\n }\n return undefined;\n });\n\n const resolvedPanelId = panel?.id ?? panelId;\n const iframeRef = useRef<HTMLIFrameElement>(null);\n\n // Inject acquireVsCodeApi shim + write HTML into iframe via srcdoc\n const rawHtml = panel?.html ?? \"\";\n const html = injectVscodeApiShim(rawHtml);\n\n // Track which project was last dispatched to prevent duplicate dispatches\n const prevProjectRef = useRef<string | null>(null);\n\n // On reload: resolve project path and dispatch command once.\n // Wait for extensions to be activated (contributions received) before dispatching.\n useEffect(() => {\n if (panel || !viewType || !extensionsReady) return;\n // Already dispatched for this project — panel is just temporarily missing\n if (projectName && projectName === prevProjectRef.current) return;\n if (projectName) prevProjectRef.current = projectName;\n const command = viewType.includes(\".\") ? viewType : `${viewType}.view`;\n let cancelled = false;\n\n async function dispatch() {\n let args: unknown[] = [];\n if (projectName) {\n try {\n const token = getAuthToken();\n const res = await fetch(\"/api/projects\", token ? { headers: { Authorization: `Bearer ${token}` } } : {});\n const json = await res.json() as { ok: boolean; data?: { name: string; path: string }[] };\n const match = json.data?.find((p) => p.name === projectName);\n if (match) args = [match.path];\n } catch {}\n }\n if (cancelled) return;\n window.dispatchEvent(new CustomEvent(\"ext:command:execute\", {\n detail: { command, args },\n }));\n }\n\n dispatch();\n return () => { cancelled = true; };\n }, [panel, viewType, projectName, extensionsReady]);\n\n // Check activation errors for this extension\n const extensionId = metadata?.extensionId as string | undefined;\n const activationError = useExtensionStore((s) => {\n // Direct match by extensionId (most reliable)\n if (extensionId && s.activationErrors[extensionId]) return s.activationErrors[extensionId];\n // Fallback: check by viewType prefix (e.g. \"ext-git-graph\" for viewType \"git-graph\")\n if (!viewType) return undefined;\n for (const [extId, error] of Object.entries(s.activationErrors)) {\n if (extId === `ext-${viewType}`) return error;\n }\n return undefined;\n });\n\n // Retry handler — re-dispatches the command\n const handleRetry = useCallback(() => {\n setTimedOut(false);\n if (!viewType) return;\n const command = viewType.includes(\".\") ? viewType : `${viewType}.view`;\n (async () => {\n try {\n const token = getAuthToken();\n const res = await fetch(\"/api/projects\", token ? { headers: { Authorization: `Bearer ${token}` } } : {});\n const json = await res.json() as { ok: boolean; data?: { name: string; path: string }[] };\n const match = json.data?.find((p) => p.name === projectName);\n const args = match ? [match.path] : [];\n window.dispatchEvent(new CustomEvent(\"ext:command:execute\", {\n detail: { command, args },\n }));\n } catch {}\n })();\n }, [viewType, projectName]);\n\n // On unmount: notify server to dispose the panel so extension clears activePanel state\n const panelIdForCleanup = useRef<string | null>(null);\n const viewTypeForCleanup = useRef<string | undefined>(viewType);\n useEffect(() => {\n panelIdForCleanup.current = resolvedPanelId ?? null;\n }, [resolvedPanelId]);\n useEffect(() => {\n viewTypeForCleanup.current = viewType;\n }, [viewType]);\n useEffect(() => {\n return () => {\n const id = panelIdForCleanup.current;\n if (id) {\n const vt = viewTypeForCleanup.current;\n useExtensionStore.getState().removeWebviewPanel(id);\n window.dispatchEvent(new CustomEvent(\"ext:webview:close\", { detail: { panelId: id, viewType: vt } }));\n }\n };\n }, []);\n\n // Auto-retry: if panel doesn't appear after extensions are ready,\n // re-dispatch the command every 2s (up to 3 times) before showing error.\n // This handles transient WS instability during initial page load where the\n // first command dispatch may be lost due to connection cycling.\n useEffect(() => {\n if (panel) { setTimedOut(false); return; }\n if (!extensionsReady || !viewType) return;\n let retries = 0;\n const id = setInterval(() => {\n retries++;\n if (retries > 3) {\n clearInterval(id);\n setTimedOut(true);\n return;\n }\n handleRetry();\n }, 2_000);\n return () => clearInterval(id);\n }, [panel, extensionsReady, viewType, handleRetry]);\n\n // Listen for postMessage from iframe → forward to extension via WS bridge\n useEffect(() => {\n if (!resolvedPanelId) return;\n const handler = (event: MessageEvent) => {\n if (iframeRef.current && event.source === iframeRef.current.contentWindow) {\n window.dispatchEvent(new CustomEvent(\"ext:webview:send\", {\n detail: { panelId: resolvedPanelId, message: event.data },\n }));\n }\n };\n window.addEventListener(\"message\", handler);\n return () => window.removeEventListener(\"message\", handler);\n }, [resolvedPanelId]);\n\n // Listen for server→webview messages (dispatched by useExtensionWs)\n useEffect(() => {\n if (!resolvedPanelId) return;\n const handler = (e: Event) => {\n const { panelId: targetId, message } = (e as CustomEvent).detail;\n if (targetId === resolvedPanelId) {\n iframeRef.current?.contentWindow?.postMessage(message, \"*\");\n }\n };\n window.addEventListener(\"ext:webview:message\", handler);\n return () => window.removeEventListener(\"ext:webview:message\", handler);\n }, [resolvedPanelId]);\n\n // Loading state — waiting for extension to create the panel AND deliver HTML.\n // We must wait for HTML before mounting the iframe because browsers don't\n // re-execute scripts when React updates the srcDoc attribute from \"\" to content.\n if (!panel || !rawHtml) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full gap-3 text-sm text-text-subtle\">\n {timedOut ? (\n <>\n <span className=\"text-destructive font-medium\">Extension failed to load</span>\n {activationError && (\n <span className=\"text-xs text-muted-foreground max-w-md text-center\">{activationError}</span>\n )}\n <button\n onClick={handleRetry}\n className=\"text-xs text-primary hover:underline\"\n >\n Retry\n </button>\n </>\n ) : (\n <>\n <Loader2 className=\"size-5 animate-spin\" />\n <span>Loading extension...</span>\n </>\n )}\n </div>\n );\n }\n\n return (\n <div className=\"h-full w-full relative\">\n <iframe\n ref={iframeRef}\n key={resolvedPanelId}\n srcDoc={html}\n sandbox=\"allow-scripts\"\n className=\"w-full h-full border-0 bg-white dark:bg-zinc-900\"\n title={panel.title}\n />\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"extension-webview-DKSDoW_g.js","names":[],"sources":["../../../src/web/components/extensions/extension-webview.tsx"],"sourcesContent":["import { useRef, useEffect, useState, useCallback } from \"react\";\nimport { useExtensionStore } from \"@/stores/extension-store\";\nimport { getAuthToken } from \"@/lib/api-client\";\nimport { Loader2 } from \"lucide-react\";\n\n/** Inject acquireVsCodeApi() shim so extension webviews can postMessage to parent */\nconst VSCODE_API_SHIM = `<script>\nfunction acquireVsCodeApi(){return{postMessage:function(m){window.parent.postMessage(m,\"*\")},getState:function(){try{return JSON.parse(sessionStorage.getItem(\"vscode-state\")||\"null\")}catch{return null}},setState:function(s){sessionStorage.setItem(\"vscode-state\",JSON.stringify(s));return s}}}\n</script>`;\n\nfunction injectVscodeApiShim(html: string): string {\n if (!html) return html;\n // Insert shim right after <head> tag (or at start if no <head>)\n const headIdx = html.indexOf(\"<head>\");\n if (headIdx !== -1) {\n return html.slice(0, headIdx + 6) + VSCODE_API_SHIM + html.slice(headIdx + 6);\n }\n return VSCODE_API_SHIM + html;\n}\n\ninterface ExtensionWebviewProps {\n metadata?: Record<string, unknown>;\n}\n\n/**\n * iframe-based webview container for extension-contributed webview panels.\n * Matches panel by panelId (direct) or viewType (reload recovery).\n */\nexport function ExtensionWebview({ metadata }: ExtensionWebviewProps) {\n const panelId = metadata?.panelId as string | undefined;\n const viewType = metadata?.viewType as string | undefined;\n // Use the tab's own project name (frozen at creation time) — NOT the global\n // currentProject. Old project's ExtensionWebview must not react to project\n // switches, which would dispatch commands for the wrong project.\n const projectName = (metadata?.projectName as string | undefined) || undefined;\n const [timedOut, setTimedOut] = useState(false);\n // Track whether extensions are activated (contributions received from WS)\n const extensionsReady = useExtensionStore((s) => s.contributions !== null);\n\n // Match panel: prefer panelId (exact), fallback to viewType match (reload recovery)\n const panel = useExtensionStore((s) => {\n if (panelId && s.webviewPanels[panelId]) return s.webviewPanels[panelId];\n if (viewType) {\n // Find panel whose viewType matches (with or without .view suffix)\n const fullViewType = viewType.includes(\".\") ? viewType : `${viewType}.view`;\n return Object.values(s.webviewPanels).find(\n (p) => p.viewType === viewType || p.viewType === fullViewType,\n );\n }\n return undefined;\n });\n\n const resolvedPanelId = panel?.id ?? panelId;\n const iframeRef = useRef<HTMLIFrameElement>(null);\n\n // Inject acquireVsCodeApi shim + write HTML into iframe via srcdoc\n const rawHtml = panel?.html ?? \"\";\n const html = injectVscodeApiShim(rawHtml);\n\n // Track which project was last dispatched to prevent duplicate dispatches\n const prevProjectRef = useRef<string | null>(null);\n\n // On reload: resolve project path and dispatch command once.\n // Wait for extensions to be activated (contributions received) before dispatching.\n useEffect(() => {\n if (panel || !viewType || !extensionsReady) return;\n // Already dispatched for this project — panel is just temporarily missing\n if (projectName && projectName === prevProjectRef.current) return;\n if (projectName) prevProjectRef.current = projectName;\n const command = viewType.includes(\".\") ? viewType : `${viewType}.view`;\n let cancelled = false;\n\n async function dispatch() {\n let args: unknown[] = [];\n if (projectName) {\n try {\n const token = getAuthToken();\n const res = await fetch(\"/api/projects\", token ? { headers: { Authorization: `Bearer ${token}` } } : {});\n const json = await res.json() as { ok: boolean; data?: { name: string; path: string }[] };\n const match = json.data?.find((p) => p.name === projectName);\n if (match) args = [match.path];\n } catch {}\n }\n if (cancelled) return;\n window.dispatchEvent(new CustomEvent(\"ext:command:execute\", {\n detail: { command, args },\n }));\n }\n\n dispatch();\n return () => { cancelled = true; };\n }, [panel, viewType, projectName, extensionsReady]);\n\n // Check activation errors for this extension\n const extensionId = metadata?.extensionId as string | undefined;\n const activationError = useExtensionStore((s) => {\n // Direct match by extensionId (most reliable)\n if (extensionId && s.activationErrors[extensionId]) return s.activationErrors[extensionId];\n // Fallback: check by viewType prefix (e.g. \"ext-git-graph\" for viewType \"git-graph\")\n if (!viewType) return undefined;\n for (const [extId, error] of Object.entries(s.activationErrors)) {\n if (extId === `ext-${viewType}`) return error;\n }\n return undefined;\n });\n\n // Retry handler — re-dispatches the command\n const handleRetry = useCallback(() => {\n setTimedOut(false);\n if (!viewType) return;\n const command = viewType.includes(\".\") ? viewType : `${viewType}.view`;\n (async () => {\n try {\n const token = getAuthToken();\n const res = await fetch(\"/api/projects\", token ? { headers: { Authorization: `Bearer ${token}` } } : {});\n const json = await res.json() as { ok: boolean; data?: { name: string; path: string }[] };\n const match = json.data?.find((p) => p.name === projectName);\n const args = match ? [match.path] : [];\n window.dispatchEvent(new CustomEvent(\"ext:command:execute\", {\n detail: { command, args },\n }));\n } catch {}\n })();\n }, [viewType, projectName]);\n\n // On unmount: notify server to dispose the panel so extension clears activePanel state\n const panelIdForCleanup = useRef<string | null>(null);\n const viewTypeForCleanup = useRef<string | undefined>(viewType);\n useEffect(() => {\n panelIdForCleanup.current = resolvedPanelId ?? null;\n }, [resolvedPanelId]);\n useEffect(() => {\n viewTypeForCleanup.current = viewType;\n }, [viewType]);\n useEffect(() => {\n return () => {\n const id = panelIdForCleanup.current;\n if (id) {\n const vt = viewTypeForCleanup.current;\n useExtensionStore.getState().removeWebviewPanel(id);\n window.dispatchEvent(new CustomEvent(\"ext:webview:close\", { detail: { panelId: id, viewType: vt } }));\n }\n };\n }, []);\n\n // Auto-retry: if panel doesn't appear after extensions are ready,\n // re-dispatch the command every 2s (up to 3 times) before showing error.\n // This handles transient WS instability during initial page load where the\n // first command dispatch may be lost due to connection cycling.\n useEffect(() => {\n if (panel) { setTimedOut(false); return; }\n if (!extensionsReady || !viewType) return;\n let retries = 0;\n const id = setInterval(() => {\n retries++;\n if (retries > 3) {\n clearInterval(id);\n setTimedOut(true);\n return;\n }\n handleRetry();\n }, 2_000);\n return () => clearInterval(id);\n }, [panel, extensionsReady, viewType, handleRetry]);\n\n // Listen for postMessage from iframe → forward to extension via WS bridge\n useEffect(() => {\n if (!resolvedPanelId) return;\n const handler = (event: MessageEvent) => {\n if (iframeRef.current && event.source === iframeRef.current.contentWindow) {\n window.dispatchEvent(new CustomEvent(\"ext:webview:send\", {\n detail: { panelId: resolvedPanelId, message: event.data },\n }));\n }\n };\n window.addEventListener(\"message\", handler);\n return () => window.removeEventListener(\"message\", handler);\n }, [resolvedPanelId]);\n\n // Listen for server→webview messages (dispatched by useExtensionWs)\n useEffect(() => {\n if (!resolvedPanelId) return;\n const handler = (e: Event) => {\n const { panelId: targetId, message } = (e as CustomEvent).detail;\n if (targetId === resolvedPanelId) {\n iframeRef.current?.contentWindow?.postMessage(message, \"*\");\n }\n };\n window.addEventListener(\"ext:webview:message\", handler);\n return () => window.removeEventListener(\"ext:webview:message\", handler);\n }, [resolvedPanelId]);\n\n // Loading state — waiting for extension to create the panel AND deliver HTML.\n // We must wait for HTML before mounting the iframe because browsers don't\n // re-execute scripts when React updates the srcDoc attribute from \"\" to content.\n if (!panel || !rawHtml) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full gap-3 text-sm text-text-subtle\">\n {timedOut ? (\n <>\n <span className=\"text-destructive font-medium\">Extension failed to load</span>\n {activationError && (\n <span className=\"text-xs text-muted-foreground max-w-md text-center\">{activationError}</span>\n )}\n <button\n onClick={handleRetry}\n className=\"text-xs text-primary hover:underline\"\n >\n Retry\n </button>\n </>\n ) : (\n <>\n <Loader2 className=\"size-5 animate-spin\" />\n <span>Loading extension...</span>\n </>\n )}\n </div>\n );\n }\n\n return (\n <div className=\"h-full w-full relative\">\n <iframe\n ref={iframeRef}\n key={resolvedPanelId}\n srcDoc={html}\n sandbox=\"allow-scripts\"\n className=\"w-full h-full border-0 bg-white dark:bg-zinc-900\"\n title={panel.title}\n />\n </div>\n );\n}\n"],"mappings":"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"}