convex-mcp-visual 1.0.13 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/apps/apps/realtime-dashboard/index.html +2 -2
- package/dist/apps/apps/schema-browser/index.html +2 -2
- package/dist/apps/assets/realtime-dashboard-CZ-nhu4Y.js +140 -0
- package/dist/apps/assets/{schema-browser-QoseH50C.js → schema-browser-DhIjBXvL.js} +90 -50
- package/dist/apps/assets/{style-nhC7AW-4.css → style-l4K1L9Ub.css} +1 -1
- package/package.json +1 -1
- package/dist/apps/assets/realtime-dashboard-BKgb9FSM.js +0 -140
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<meta name="theme-color" content="#faf8f5">
|
|
7
7
|
<title>Realtime Dashboard - Convex MCP Apps</title>
|
|
8
|
-
<script type="module" crossorigin src="../../assets/realtime-dashboard-
|
|
8
|
+
<script type="module" crossorigin src="../../assets/realtime-dashboard-CZ-nhu4Y.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="../../assets/modulepreload-polyfill-B5Qt9EMX.js">
|
|
10
|
-
<link rel="stylesheet" crossorigin href="../../assets/style-
|
|
10
|
+
<link rel="stylesheet" crossorigin href="../../assets/style-l4K1L9Ub.css">
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
13
|
<div id="app"></div>
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<meta name="theme-color" content="#faf8f5">
|
|
7
7
|
<title>Schema Browser - Convex MCP Apps</title>
|
|
8
|
-
<script type="module" crossorigin src="../../assets/schema-browser-
|
|
8
|
+
<script type="module" crossorigin src="../../assets/schema-browser-DhIjBXvL.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="../../assets/modulepreload-polyfill-B5Qt9EMX.js">
|
|
10
|
-
<link rel="stylesheet" crossorigin href="../../assets/style-
|
|
10
|
+
<link rel="stylesheet" crossorigin href="../../assets/style-l4K1L9Ub.css">
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
13
|
<div id="app"></div>
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
var h=Object.defineProperty;var m=(c,e,t)=>e in c?h(c,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):c[e]=t;var l=(c,e,t)=>m(c,typeof e!="symbol"?e+"":e,t);import"./modulepreload-polyfill-B5Qt9EMX.js";class u{constructor(){l(this,"config",null);l(this,"refreshTimer",null);l(this,"isConnected",!0);l(this,"currentTheme","light");this.init()}init(){var t;this.initTheme();const e=window;if(e.__CONVEX_CONFIG__)this.config=e.__CONVEX_CONFIG__;else{const r=new URLSearchParams(window.location.search).get("config");if(r)try{this.config=JSON.parse(decodeURIComponent(r))}catch(s){console.error("Failed to parse config:",s)}}this.isConnected=!!((t=this.config)!=null&&t.deploymentUrl),this.render(),this.startAutoRefresh()}initTheme(){const e=localStorage.getItem("convex-dashboard-theme");e?this.currentTheme=e:window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches&&(this.currentTheme="light"),this.applyTheme()}applyTheme(){document.documentElement.setAttribute("data-theme",this.currentTheme),localStorage.setItem("convex-dashboard-theme",this.currentTheme)}toggleTheme(){this.currentTheme=this.currentTheme==="light"?"dark":"light",this.applyTheme();const e=document.getElementById("themeIcon");e&&(e.innerHTML=this.currentTheme==="light"?'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>':'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>')}render(){var r;const e=document.getElementById("app");if(!e)return;const t=((r=this.config)==null?void 0:r.deploymentUrl)||"Not connected";e.innerHTML=`
|
|
2
|
+
<div class="header">
|
|
3
|
+
<h1>
|
|
4
|
+
<span class="status-dot ${this.isConnected?"":"disconnected"}" id="statusDot" title="${this.isConnected?"Connected to Convex":"Not connected - check deploy key"}"></span>
|
|
5
|
+
Realtime Dashboard
|
|
6
|
+
</h1>
|
|
7
|
+
<div class="header-right">
|
|
8
|
+
<span class="deployment-url" title="Your Convex deployment URL">${t}</span>
|
|
9
|
+
<span class="last-update" id="lastUpdate" title="Time since last data refresh"></span>
|
|
10
|
+
<button class="theme-toggle-btn" id="themeToggle" title="Toggle dark/light mode">
|
|
11
|
+
<span id="themeIcon">${this.currentTheme==="light"?'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>':'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>'}</span>
|
|
12
|
+
</button>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<div class="metrics-grid" id="metricsGrid"></div>
|
|
17
|
+
<div class="charts-grid" id="chartsGrid"></div>
|
|
18
|
+
`;const i=document.getElementById("themeToggle");i&&i.addEventListener("click",()=>this.toggleTheme()),this.renderMetrics(),this.renderCharts(),this.updateTimestamp()}renderMetrics(){var i;const e=document.getElementById("metricsGrid");if(!e)return;const t=((i=this.config)==null?void 0:i.metrics)||[];if(t.length===0){e.innerHTML=`
|
|
19
|
+
<div class="metric-card">
|
|
20
|
+
<div class="metric-header">
|
|
21
|
+
<span class="metric-label">No Metrics Configured</span>
|
|
22
|
+
</div>
|
|
23
|
+
<div class="metric-value">--</div>
|
|
24
|
+
<div class="metric-change neutral">Add metrics via MCP tool parameters</div>
|
|
25
|
+
</div>
|
|
26
|
+
`;return}e.innerHTML=t.map(r=>`
|
|
27
|
+
<div class="metric-card" title="${this.getMetricTooltip(r)}">
|
|
28
|
+
<div class="metric-header">
|
|
29
|
+
<span class="metric-label">${r.name}</span>
|
|
30
|
+
<span class="metric-icon" title="${this.getAggregationTooltip(r.aggregation)}">${this.getMetricIcon(r.aggregation)}</span>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="metric-value" title="Exact value: ${(r.value||0).toLocaleString()}">${this.formatNumber(r.value||0)}</div>
|
|
33
|
+
<div class="metric-change neutral">
|
|
34
|
+
${r.documentCount!==void 0?`${r.documentCount.toLocaleString()} documents`:""}
|
|
35
|
+
</div>
|
|
36
|
+
<div class="metric-source" title="Data source: ${r.table} table">${r.table} / ${r.aggregation}${r.field?`(${r.field})`:""}</div>
|
|
37
|
+
</div>
|
|
38
|
+
`).join("")}getMetricIcon(e){return{count:"#",sum:"Σ",avg:"x̄",min:"↓",max:"↑"}[e]||"#"}getMetricTooltip(e){const t=this.getAggregationTooltip(e.aggregation);return`${e.name}: ${t} from ${e.table}${e.field?` on field "${e.field}"`:""}`}getAggregationTooltip(e){return{count:"Count - Total number of documents",sum:"Sum - Total of all values",avg:"Average - Mean of all values",min:"Minimum - Smallest value",max:"Maximum - Largest value"}[e]||e}formatNumber(e){return e>=1e6?(e/1e6).toFixed(1)+"M":e>=1e3?(e/1e3).toFixed(1)+"k":Number.isInteger(e)?e.toLocaleString():e.toFixed(2)}renderCharts(){var s,a;const e=document.getElementById("chartsGrid");if(!e)return;const t=((s=this.config)==null?void 0:s.charts)||[],i=((a=this.config)==null?void 0:a.allDocuments)||{},r=this.renderActivityTable(i);if(t.length===0){e.innerHTML=`
|
|
39
|
+
${this.renderTablesOverview()}
|
|
40
|
+
${r}
|
|
41
|
+
`;return}e.innerHTML=t.map((n,o)=>{const d=i[n.table]||[];return`
|
|
42
|
+
<div class="chart-card">
|
|
43
|
+
<div class="chart-header">
|
|
44
|
+
<span class="chart-title">${n.title}</span>
|
|
45
|
+
<span class="chart-subtitle">${d.length} documents</span>
|
|
46
|
+
</div>
|
|
47
|
+
<div class="chart-container" id="chart-${o}">
|
|
48
|
+
${this.renderChartContent(n,d)}
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
`}).join("")+r}renderTablesOverview(){var i;const e=((i=this.config)==null?void 0:i.tables)||[];if(e.length===0)return"";const t=Math.max(...e.map(r=>r.documentCount),1);return`
|
|
52
|
+
<div class="chart-card">
|
|
53
|
+
<div class="chart-header">
|
|
54
|
+
<span class="chart-title" title="Document count per table in your Convex database">Tables Overview</span>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="chart-container">
|
|
57
|
+
<div class="bar-chart">
|
|
58
|
+
${e.map(r=>`
|
|
59
|
+
<div class="bar" style="height: ${r.documentCount/t*100}%" data-value="${r.documentCount}" title="${r.name}: ${r.documentCount.toLocaleString()} documents">
|
|
60
|
+
<span class="bar-label">${r.name}</span>
|
|
61
|
+
</div>
|
|
62
|
+
`).join("")}
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
`}renderChartContent(e,t){switch(e.type){case"bar":return this.renderBarChart(t,e);case"line":return this.renderLineChart(t,e);case"pie":return this.renderPieChart(t,e);case"table":return this.renderDataTable(t);default:return`<div class="empty-state">Unknown chart type: ${e.type}</div>`}}renderBarChart(e,t){if(e.length===0)return'<div class="empty-state">No data available</div>';let i=[];if(t.groupBy){const s=new Map;for(const a of e){const n=String(a[t.groupBy]||"Unknown");s.set(n,(s.get(n)||0)+1)}i=Array.from(s.entries()).map(([a,n])=>({label:a,value:n})).sort((a,n)=>n.value-a.value).slice(0,7)}else{const s=new Map;for(const a of e){const n=new Date(a._creationTime).toLocaleDateString();s.set(n,(s.get(n)||0)+1)}i=Array.from(s.entries()).map(([a,n])=>({label:a,value:n})).slice(-7)}const r=Math.max(...i.map(s=>s.value),1);return`
|
|
67
|
+
<div class="bar-chart">
|
|
68
|
+
${i.map(s=>`
|
|
69
|
+
<div class="bar" style="height: ${s.value/r*100}%" data-value="${s.value}">
|
|
70
|
+
<span class="bar-label">${s.label.slice(0,8)}</span>
|
|
71
|
+
</div>
|
|
72
|
+
`).join("")}
|
|
73
|
+
</div>
|
|
74
|
+
`}renderLineChart(e,t){return e.length===0?'<div class="empty-state">No data available</div>':this.renderBarChart(e,t)}renderPieChart(e,t){if(e.length===0||!t.groupBy)return'<div class="empty-state">No data or groupBy field specified</div>';const i=new Map;for(const n of e){const o=String(n[t.groupBy]||"Unknown");i.set(o,(i.get(o)||0)+1)}const r=e.length,s=Array.from(i.entries()).map(([n,o])=>({label:n,count:o,percent:(o/r*100).toFixed(1)})).sort((n,o)=>o.count-n.count).slice(0,5),a=["#e94560","#0f3460","#16213e","#1a1a2e","#4caf50"];return`
|
|
75
|
+
<div class="pie-legend">
|
|
76
|
+
${s.map((n,o)=>`
|
|
77
|
+
<div class="pie-item">
|
|
78
|
+
<span class="pie-color" style="background: ${a[o%a.length]}"></span>
|
|
79
|
+
<span class="pie-label">${n.label}</span>
|
|
80
|
+
<span class="pie-value">${n.percent}%</span>
|
|
81
|
+
</div>
|
|
82
|
+
`).join("")}
|
|
83
|
+
</div>
|
|
84
|
+
`}renderDataTable(e){const t=e.slice(0,5);if(t.length===0)return'<div class="empty-state">No documents</div>';const i=Object.keys(t[0]).slice(0,4);return`
|
|
85
|
+
<div class="table-chart">
|
|
86
|
+
<table>
|
|
87
|
+
<thead>
|
|
88
|
+
<tr>
|
|
89
|
+
${i.map(r=>`<th>${r}</th>`).join("")}
|
|
90
|
+
</tr>
|
|
91
|
+
</thead>
|
|
92
|
+
<tbody>
|
|
93
|
+
${t.map(r=>`
|
|
94
|
+
<tr>
|
|
95
|
+
${i.map(s=>`<td>${this.formatValue(r[s])}</td>`).join("")}
|
|
96
|
+
</tr>
|
|
97
|
+
`).join("")}
|
|
98
|
+
</tbody>
|
|
99
|
+
</table>
|
|
100
|
+
</div>
|
|
101
|
+
`}renderActivityTable(e){const t=Object.entries(e).flatMap(([i,r])=>r.map(s=>({table:i,...s}))).sort((i,r)=>{const s=i._creationTime||0;return(r._creationTime||0)-s}).slice(0,10);return t.length===0?`
|
|
102
|
+
<div class="chart-card">
|
|
103
|
+
<div class="chart-header">
|
|
104
|
+
<span class="chart-title">Recent Activity</span>
|
|
105
|
+
</div>
|
|
106
|
+
<div class="chart-container">
|
|
107
|
+
<div class="empty-state">No documents found</div>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
`:`
|
|
111
|
+
<div class="chart-card">
|
|
112
|
+
<div class="chart-header">
|
|
113
|
+
<span class="chart-title">Recent Activity</span>
|
|
114
|
+
</div>
|
|
115
|
+
<div class="chart-container">
|
|
116
|
+
<div class="table-chart">
|
|
117
|
+
<table>
|
|
118
|
+
<thead>
|
|
119
|
+
<tr>
|
|
120
|
+
<th>Table</th>
|
|
121
|
+
<th>ID</th>
|
|
122
|
+
<th>Created</th>
|
|
123
|
+
</tr>
|
|
124
|
+
</thead>
|
|
125
|
+
<tbody>
|
|
126
|
+
${t.map(i=>`
|
|
127
|
+
<tr>
|
|
128
|
+
<td>${i.table}</td>
|
|
129
|
+
<td style="font-family: var(--font-mono); color: var(--accent);">
|
|
130
|
+
${String(i._id||"").slice(0,12)}...
|
|
131
|
+
</td>
|
|
132
|
+
<td>${this.formatTimeAgo(i._creationTime)}</td>
|
|
133
|
+
</tr>
|
|
134
|
+
`).join("")}
|
|
135
|
+
</tbody>
|
|
136
|
+
</table>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
`}formatValue(e){if(e==null)return'<span style="color: var(--text-secondary)">null</span>';if(typeof e=="object"){const t=JSON.stringify(e);return t.length>30?t.slice(0,30)+"...":t}return typeof e=="string"&&e.length>30?e.slice(0,30)+"...":String(e)}formatTimeAgo(e){if(!e)return"Unknown";const i=Date.now()-e,r=Math.floor(i/6e4),s=Math.floor(i/36e5),a=Math.floor(i/864e5);return r<1?"Just now":r<60?`${r}m ago`:s<24?`${s}h ago`:`${a}d ago`}updateTimestamp(){const e=document.getElementById("lastUpdate");e&&(e.textContent="Updated: "+new Date().toLocaleTimeString())}startAutoRefresh(){var t;const e=(((t=this.config)==null?void 0:t.refreshInterval)||5)*1e3;this.refreshTimer=window.setInterval(()=>{this.updateTimestamp()},e)}stopAutoRefresh(){this.refreshTimer&&(clearInterval(this.refreshTimer),this.refreshTimer=null)}destroy(){this.stopAutoRefresh()}}const g=new u;window.addEventListener("beforeunload",()=>{g.destroy()});
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
var V=Object.defineProperty;var X=(
|
|
1
|
+
var V=Object.defineProperty;var X=(E,e,t)=>e in E?V(E,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):E[e]=t;var m=(E,e,t)=>X(E,typeof e!="symbol"?e+"":e,t);import"./modulepreload-polyfill-B5Qt9EMX.js";class Y{constructor(){m(this,"config",null);m(this,"selectedTable",null);m(this,"currentPage",1);m(this,"pageSize",50);m(this,"searchQuery","");m(this,"queryModalOpen",!1);m(this,"viewMode","graph");m(this,"sidebarWidth",260);m(this,"codePanelWidth",360);m(this,"sidebarCollapsed",!1);m(this,"graphSidebarCollapsed",!1);m(this,"isResizingSidebar",!1);m(this,"graphNodes",[]);m(this,"graphEdges",[]);m(this,"canvas",null);m(this,"ctx",null);m(this,"panX",0);m(this,"panY",0);m(this,"zoom",1);m(this,"isDragging",!1);m(this,"dragStartX",0);m(this,"dragStartY",0);m(this,"selectedNode",null);m(this,"hoveredNode",null);m(this,"dpr",1);m(this,"showCodePanel",!0);m(this,"positionHistory",[]);m(this,"historyIndex",-1);m(this,"maxHistorySize",20);m(this,"showExportMenu",!1);m(this,"showFilterDropdown",!1);m(this,"sidebarSections",{tables:{collapsed:!1},convex:{collapsed:!0}});m(this,"tableSortBy","name");m(this,"tableSortOrder","asc");m(this,"filterState",{tableName:"",fieldName:"",fieldType:"",showEmpty:!0});m(this,"tooltip",null);m(this,"tooltipTimeout",null);m(this,"currentTheme","light");this.initTheme(),this.init()}initTheme(){const e=localStorage.getItem("convex-schema-theme");e==="dark"||e==="light"?this.currentTheme=e:window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches&&(this.currentTheme="light"),this.applyTheme()}applyTheme(){document.documentElement.setAttribute("data-theme",this.currentTheme)}showShortcutsModal(){const e=document.getElementById("shortcutsModal");e&&(e.style.display="flex")}hideShortcutsModal(){const e=document.getElementById("shortcutsModal");e&&(e.style.display="none")}toggleTheme(){this.currentTheme=this.currentTheme==="light"?"dark":"light",localStorage.setItem("convex-schema-theme",this.currentTheme),this.applyTheme(),this.viewMode==="graph"&&this.drawGraph()}getThemeColor(e){return getComputedStyle(document.documentElement).getPropertyValue(e).trim()}getThemeColors(){return{bgPrimary:this.getThemeColor("--bg-primary")||"#faf8f5",bgSecondary:this.getThemeColor("--bg-secondary")||"#f5f3f0",bgHover:this.getThemeColor("--bg-hover")||"#ebe9e6",textPrimary:this.getThemeColor("--text-primary")||"#1a1a1a",textSecondary:this.getThemeColor("--text-secondary")||"#6b6b6b",textMuted:this.getThemeColor("--text-muted")||"#999999",border:this.getThemeColor("--border")||"#e6e4e1",accent:this.getThemeColor("--accent")||"#8b7355",accentInteractive:this.getThemeColor("--accent-interactive")||"#EB5601",accentHover:this.getThemeColor("--accent-hover")||"#d14a01",warning:this.getThemeColor("--warning")||"#c4842d",nodeBg:this.getThemeColor("--node-bg")||"#ffffff",nodeHeader:this.getThemeColor("--node-header")||"#f8f7f5",nodeHeaderSelected:this.getThemeColor("--node-header-selected")||"#EB5601",nodeBorder:this.getThemeColor("--node-border")||"#e6e4e1",gridLine:this.getThemeColor("--grid-line")||"#e6e4e1"}}init(){var t,i,s;const e=window;if(e.__CONVEX_CONFIG__)this.config=e.__CONVEX_CONFIG__;else{const n=new URLSearchParams(window.location.search).get("config");if(n)try{this.config=JSON.parse(decodeURIComponent(n))}catch(a){console.error("Failed to parse config:",a)}}this.pageSize=((t=this.config)==null?void 0:t.pageSize)||50,this.render(),this.setupKeyboardShortcuts(),(i=this.config)!=null&&i.selectedTable?this.selectedTable=this.config.selectedTable:(s=this.config)!=null&&s.tables&&this.config.tables.length>0&&(this.selectedTable=this.config.tables[0].name),this.viewMode==="graph"&&this.initGraphView()}render(){var s,o;const e=document.getElementById("app");if(!e)return;const t=((s=this.config)==null?void 0:s.deploymentUrl)||"Not connected",i=!!((o=this.config)!=null&&o.deploymentUrl);e.innerHTML=`
|
|
2
2
|
<div class="app-container ${this.viewMode==="graph"?"graph-mode":"list-mode"}">
|
|
3
3
|
<div class="header">
|
|
4
4
|
<h1>
|
|
5
|
-
<span class="status-dot ${i?"":"error"}"></span>
|
|
5
|
+
<span class="status-dot ${i?"":"error"}" title="${i?"Connected to Convex":"Not connected"}"></span>
|
|
6
6
|
Schema Browser
|
|
7
7
|
</h1>
|
|
8
8
|
<div class="header-info">
|
|
9
|
-
<span class="deployment-url">${t}</span>
|
|
9
|
+
<span class="deployment-url" title="Your Convex deployment URL">${t}</span>
|
|
10
10
|
</div>
|
|
11
11
|
<div class="view-toggle">
|
|
12
|
-
<button class="view-btn ${this.viewMode==="list"?"active":""}" data-view="list" title="List View">
|
|
12
|
+
<button class="view-btn ${this.viewMode==="list"?"active":""}" data-view="list" title="List View - Browse tables and documents in a table format (G to toggle)">
|
|
13
13
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
14
14
|
<path d="M2 3h12v2H2V3zm0 4h12v2H2V7zm0 4h12v2H2v-2z"/>
|
|
15
15
|
</svg>
|
|
16
16
|
</button>
|
|
17
|
-
<button class="view-btn ${this.viewMode==="graph"?"active":""}" data-view="graph" title="Graph View">
|
|
17
|
+
<button class="view-btn ${this.viewMode==="graph"?"active":""}" data-view="graph" title="Graph View - Visualize table relationships as a diagram (G to toggle)">
|
|
18
18
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
19
19
|
<circle cx="4" cy="4" r="2"/>
|
|
20
20
|
<circle cx="12" cy="4" r="2"/>
|
|
@@ -24,7 +24,13 @@ var V=Object.defineProperty;var X=(x,e,t)=>e in x?V(x,e,{enumerable:!0,configura
|
|
|
24
24
|
</button>
|
|
25
25
|
</div>
|
|
26
26
|
<div class="header-actions">
|
|
27
|
-
<button class="
|
|
27
|
+
<button class="btn" id="shortcutsBtn" title="Keyboard shortcuts">
|
|
28
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
29
|
+
<rect x="2" y="6" width="20" height="12" rx="2"/>
|
|
30
|
+
<path d="M6 10h.01M10 10h.01M14 10h.01M18 10h.01M8 14h8"/>
|
|
31
|
+
</svg>
|
|
32
|
+
</button>
|
|
33
|
+
<button class="theme-toggle" id="themeToggle" title="Toggle dark/light mode">
|
|
28
34
|
<svg class="moon-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
29
35
|
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
|
|
30
36
|
</svg>
|
|
@@ -33,11 +39,43 @@ var V=Object.defineProperty;var X=(x,e,t)=>e in x?V(x,e,{enumerable:!0,configura
|
|
|
33
39
|
<path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/>
|
|
34
40
|
</svg>
|
|
35
41
|
</button>
|
|
36
|
-
<button class="btn" id="refreshBtn" title="Refresh">↻</button>
|
|
42
|
+
<button class="btn" id="refreshBtn" title="Refresh data (R)">↻</button>
|
|
37
43
|
</div>
|
|
38
44
|
</div>
|
|
39
45
|
|
|
40
46
|
${this.viewMode==="graph"?this.renderGraphView():this.renderListView()}
|
|
47
|
+
|
|
48
|
+
<!-- Keyboard shortcuts modal -->
|
|
49
|
+
<div class="shortcuts-modal" id="shortcutsModal" style="display: none;">
|
|
50
|
+
<div class="shortcuts-modal-content">
|
|
51
|
+
<div class="shortcuts-modal-header">
|
|
52
|
+
<h3>Keyboard Shortcuts</h3>
|
|
53
|
+
<button class="shortcuts-close" id="shortcutsClose">×</button>
|
|
54
|
+
</div>
|
|
55
|
+
<div class="shortcuts-modal-body">
|
|
56
|
+
<div class="shortcuts-section">
|
|
57
|
+
<h4>Navigation</h4>
|
|
58
|
+
<div class="shortcut-item"><kbd>G</kbd> Toggle Graph/List view</div>
|
|
59
|
+
<div class="shortcut-item"><kbd>R</kbd> Refresh data</div>
|
|
60
|
+
<div class="shortcut-item"><kbd>/</kbd> Focus search</div>
|
|
61
|
+
<div class="shortcut-item"><kbd>↑</kbd><kbd>↓</kbd> Navigate tables</div>
|
|
62
|
+
</div>
|
|
63
|
+
<div class="shortcuts-section">
|
|
64
|
+
<h4>Graph View</h4>
|
|
65
|
+
<div class="shortcut-item"><kbd>C</kbd> Toggle code panel</div>
|
|
66
|
+
<div class="shortcut-item"><kbd>A</kbd> Auto-arrange nodes</div>
|
|
67
|
+
<div class="shortcut-item"><kbd>F</kbd> Fit to view</div>
|
|
68
|
+
<div class="shortcut-item"><kbd>+</kbd><kbd>-</kbd> Zoom in/out</div>
|
|
69
|
+
<div class="shortcut-item"><kbd>Cmd/Ctrl</kbd>+<kbd>Z</kbd> Undo</div>
|
|
70
|
+
</div>
|
|
71
|
+
<div class="shortcuts-section">
|
|
72
|
+
<h4>List View</h4>
|
|
73
|
+
<div class="shortcut-item"><kbd>←</kbd><kbd>→</kbd> Previous/Next page</div>
|
|
74
|
+
<div class="shortcut-item"><kbd>B</kbd> Toggle sidebar</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
41
79
|
</div>
|
|
42
80
|
`,this.setupEventListeners(),this.viewMode==="list"?(this.renderTableList(),this.selectedTable&&this.selectTable(this.selectedTable),this.setupListViewEventListeners()):this.initGraphView()}renderListView(){var t,i;return`
|
|
43
81
|
<div class="main list-view">
|
|
@@ -77,20 +115,20 @@ var V=Object.defineProperty;var X=(x,e,t)=>e in x?V(x,e,{enumerable:!0,configura
|
|
|
77
115
|
<h2>Select a Table</h2>
|
|
78
116
|
<p>Choose a table from the sidebar to view its schema and documents.</p>
|
|
79
117
|
</div>
|
|
80
|
-
`;const e=(d=(l=this.config)==null?void 0:l.tables)==null?void 0:d.find(
|
|
118
|
+
`;const e=(d=(l=this.config)==null?void 0:l.tables)==null?void 0:d.find(g=>g.name===this.selectedTable),t=((c=(h=this.config)==null?void 0:h.allDocuments)==null?void 0:c[this.selectedTable])||(e==null?void 0:e.documents)||[],i=(e==null?void 0:e.declaredFields)||[],s=(e==null?void 0:e.inferredFields)||[],o=i.length>0?i:s,n=i.length>0?"declared":"inferred",a=(e==null?void 0:e.documentCount)||0,r=((p=this.config)==null?void 0:p.hasAdminAccess)??!0;return`
|
|
81
119
|
<div class="table-header">
|
|
82
120
|
<div class="table-header-title">${this.selectedTable}</div>
|
|
83
121
|
<div class="table-header-meta">
|
|
84
|
-
<span>${this.formatCount(a)} documents total</span>
|
|
85
|
-
${e!=null&&e.hasIndexes?'<span class="badge">Indexed</span>':""}
|
|
86
|
-
<span class="badge">${o.length} fields</span>
|
|
122
|
+
<span title="Total documents stored in this table">${this.formatCount(a)} documents total</span>
|
|
123
|
+
${e!=null&&e.hasIndexes?'<span class="badge" title="This table has database indexes for faster queries">Indexed</span>':""}
|
|
124
|
+
<span class="badge" title="Number of fields in this table schema">${o.length} fields</span>
|
|
87
125
|
</div>
|
|
88
126
|
</div>
|
|
89
127
|
<div class="content-split">
|
|
90
128
|
<div class="schema-sidebar">
|
|
91
129
|
<div class="schema-sidebar-header">
|
|
92
130
|
<span>Schema</span>
|
|
93
|
-
<span class="field-count-badge" title="${
|
|
131
|
+
<span class="field-count-badge" title="${n==="declared"?"Schema defined in convex/schema.ts":"Schema inferred from existing documents"}">${n}</span>
|
|
94
132
|
</div>
|
|
95
133
|
<div class="schema-fields-list">
|
|
96
134
|
${this.renderSchemaFieldsList(o)}
|
|
@@ -101,9 +139,9 @@ var V=Object.defineProperty;var X=(x,e,t)=>e in x?V(x,e,{enumerable:!0,configura
|
|
|
101
139
|
<div class="documents-toolbar">
|
|
102
140
|
<div class="documents-toolbar-title">
|
|
103
141
|
Documents
|
|
104
|
-
<span class="doc-count">${t.length>0?`(${this.formatCount(t.length)} loaded of ${this.formatCount(a)})`:"(none loaded)"}</span>
|
|
142
|
+
<span class="doc-count" title="Sample documents loaded from the database">${t.length>0?`(${this.formatCount(t.length)} loaded of ${this.formatCount(a)})`:"(none loaded)"}</span>
|
|
105
143
|
</div>
|
|
106
|
-
${r?"":'<span class="badge" style="background: var(--warning-bg); color: var(--warning-text);">Deploy key required for documents</span>'}
|
|
144
|
+
${r?"":'<span class="badge" style="background: var(--warning-bg); color: var(--warning-text);" title="A deploy key is required to fetch document data from Convex">Deploy key required for documents</span>'}
|
|
107
145
|
</div>
|
|
108
146
|
<div class="documents-table-wrapper" id="documentsTableWrapper">
|
|
109
147
|
${this.renderDocumentsTable(t,r)}
|
|
@@ -111,22 +149,22 @@ var V=Object.defineProperty;var X=(x,e,t)=>e in x?V(x,e,{enumerable:!0,configura
|
|
|
111
149
|
${this.renderPaginationBar(t)}
|
|
112
150
|
</div>
|
|
113
151
|
</div>
|
|
114
|
-
`}renderSchemaFieldsList(e){if(!e||e.length===0)return'<div style="padding: 16px; color: var(--text-secondary);">No schema data available</div>';const t=["_id","_creationTime"];return[...e].sort((
|
|
115
|
-
<div class="schema-field-item">
|
|
116
|
-
<span class="schema-field-name ${
|
|
117
|
-
${
|
|
118
|
-
${
|
|
152
|
+
`}renderSchemaFieldsList(e){if(!e||e.length===0)return'<div style="padding: 16px; color: var(--text-secondary);">No schema data available</div>';const t=["_id","_creationTime"];return[...e].sort((s,o)=>{const n=t.includes(s.name),a=t.includes(o.name);return n&&!a?-1:!n&&a?1:s.name.localeCompare(o.name)}).map(s=>{const o=t.includes(s.name);return`
|
|
153
|
+
<div class="schema-field-item" title="${this.getFieldTooltipText(s,o)}">
|
|
154
|
+
<span class="schema-field-name ${o?"system-field":""}">
|
|
155
|
+
${s.name}
|
|
156
|
+
${s.optional?'<span class="schema-field-optional" title="This field is optional">?</span>':""}
|
|
119
157
|
</span>
|
|
120
|
-
<span class="schema-field-type">${
|
|
158
|
+
<span class="schema-field-type" title="Field type: ${s.type}">${s.type}</span>
|
|
121
159
|
</div>
|
|
122
|
-
`).join("")}renderIndexesSection(e){return!e.indexes||e.indexes.length===0?"":`
|
|
160
|
+
`}).join("")}getFieldTooltipText(e,t){if(e.name==="_id")return"Document ID: Unique identifier auto-generated by Convex";if(e.name==="_creationTime")return"Creation timestamp: Unix time (ms) when this document was created";if(e.type.includes("Id<")){const s=e.type.match(/Id<["'](\w+)["']>/);return`Reference to ${s?s[1]:"another table"} table`}let i=`${e.name}: ${e.type}`;return e.optional&&(i+=" (optional)"),i}renderIndexesSection(e){return!e.indexes||e.indexes.length===0?"":`
|
|
123
161
|
<div class="schema-indexes">
|
|
124
|
-
<div class="schema-indexes-title">Indexes</div>
|
|
162
|
+
<div class="schema-indexes-title" title="Database indexes speed up queries on these fields">Indexes</div>
|
|
125
163
|
${e.indexes.map(t=>`
|
|
126
|
-
<div class="schema-index-item">${t}</div>
|
|
164
|
+
<div class="schema-index-item" title="Index: ${t} - Use withIndex('${t}', ...) in queries for faster lookups">${t}</div>
|
|
127
165
|
`).join("")}
|
|
128
166
|
</div>
|
|
129
|
-
`}renderDocumentsTable(e,t=!0){const i=(this.currentPage-1)*this.pageSize,
|
|
167
|
+
`}renderDocumentsTable(e,t=!0){const i=(this.currentPage-1)*this.pageSize,s=i+this.pageSize,o=e.slice(i,s);if(o.length===0)return t?`
|
|
130
168
|
<div class="documents-empty">
|
|
131
169
|
<div class="documents-empty-icon">
|
|
132
170
|
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
@@ -151,23 +189,23 @@ var V=Object.defineProperty;var X=(x,e,t)=>e in x?V(x,e,{enumerable:!0,configura
|
|
|
151
189
|
Get your key from Settings > Deploy Keys in the Convex dashboard.
|
|
152
190
|
</p>
|
|
153
191
|
</div>
|
|
154
|
-
`;const
|
|
192
|
+
`;const n=[...new Set(o.flatMap(a=>Object.keys(a)))];return n.sort((a,r)=>{const l=a.startsWith("_"),d=r.startsWith("_");return l&&!d?-1:!l&&d?1:a.localeCompare(r)}),`
|
|
155
193
|
<table>
|
|
156
194
|
<thead>
|
|
157
|
-
<tr>${
|
|
195
|
+
<tr>${n.map(a=>`<th>${a}</th>`).join("")}</tr>
|
|
158
196
|
</thead>
|
|
159
197
|
<tbody>
|
|
160
198
|
${o.map(a=>`
|
|
161
199
|
<tr>
|
|
162
|
-
${
|
|
200
|
+
${n.map(r=>{const l=a[r],d=r==="_id",h=l==null;return`<td class="${[d?"id-cell":"",h?"null-value":""].filter(Boolean).join(" ")}">${this.formatValue(l)}</td>`}).join("")}
|
|
163
201
|
</tr>
|
|
164
202
|
`).join("")}
|
|
165
203
|
</tbody>
|
|
166
204
|
</table>
|
|
167
|
-
`}renderPaginationBar(e){const t=Math.ceil(e.length/this.pageSize)||1,i=(this.currentPage-1)*this.pageSize+1,
|
|
205
|
+
`}renderPaginationBar(e){const t=Math.ceil(e.length/this.pageSize)||1,i=(this.currentPage-1)*this.pageSize+1,s=Math.min(this.currentPage*this.pageSize,e.length);return`
|
|
168
206
|
<div class="pagination-bar">
|
|
169
207
|
<div class="pagination-info">
|
|
170
|
-
${e.length>0?`Showing ${i}-${
|
|
208
|
+
${e.length>0?`Showing ${i}-${s} of ${e.length}`:"No documents"}
|
|
171
209
|
</div>
|
|
172
210
|
<div class="pagination-controls">
|
|
173
211
|
<button id="prevPage" ${this.currentPage<=1?"disabled":""}>‹</button>
|
|
@@ -219,7 +257,7 @@ var V=Object.defineProperty;var X=(x,e,t)=>e in x?V(x,e,{enumerable:!0,configura
|
|
|
219
257
|
${this.renderExportMenu()}
|
|
220
258
|
${this.renderFilterDropdown()}
|
|
221
259
|
</div>
|
|
222
|
-
`}renderEnhancedSidebar(){var
|
|
260
|
+
`}renderEnhancedSidebar(){var n,a;const e=((n=this.config)==null?void 0:n.deploymentUrl)||"Not connected",t=!!((a=this.config)!=null&&a.deploymentUrl),i=this.getSortedTables(),s=this.getFilteredTablesForSidebar(i);return`
|
|
223
261
|
<div class="enhanced-sidebar ${this.graphSidebarCollapsed?"collapsed":""}" id="enhancedSidebar">
|
|
224
262
|
<button class="graph-sidebar-toggle" id="graphSidebarToggle" title="${this.graphSidebarCollapsed?"Expand sidebar":"Collapse sidebar"}">
|
|
225
263
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="2">
|
|
@@ -252,7 +290,7 @@ var V=Object.defineProperty;var X=(x,e,t)=>e in x?V(x,e,{enumerable:!0,configura
|
|
|
252
290
|
</button>
|
|
253
291
|
</div>
|
|
254
292
|
<ul class="sidebar-table-list" id="graphTableList">
|
|
255
|
-
${
|
|
293
|
+
${s.map(r=>this.renderSidebarTableItem(r)).join("")}
|
|
256
294
|
</ul>
|
|
257
295
|
</div>
|
|
258
296
|
</div>
|
|
@@ -282,8 +320,8 @@ var V=Object.defineProperty;var X=(x,e,t)=>e in x?V(x,e,{enumerable:!0,configura
|
|
|
282
320
|
</div>
|
|
283
321
|
</div>
|
|
284
322
|
</div>
|
|
285
|
-
`}renderSidebarTableItem(e){var
|
|
286
|
-
<li class="sidebar-table-item ${t?"active":""} ${
|
|
323
|
+
`}renderSidebarTableItem(e){var n;const t=e.name===this.selectedTable,i=this.graphNodes.find(a=>a.id===e.name),s=(i==null?void 0:i.visible)!==!1,o=((n=e.inferredFields)==null?void 0:n.length)||0;return`
|
|
324
|
+
<li class="sidebar-table-item ${t?"active":""} ${s?"":"filtered-out"}" data-table="${e.name}">
|
|
287
325
|
<div class="table-item-main">
|
|
288
326
|
<svg class="table-icon" width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
289
327
|
<rect x="1" y="1" width="12" height="12" rx="2"/>
|
|
@@ -296,10 +334,10 @@ var V=Object.defineProperty;var X=(x,e,t)=>e in x?V(x,e,{enumerable:!0,configura
|
|
|
296
334
|
<span class="doc-count" title="${e.documentCount} documents">${this.formatCount(e.documentCount)}</span>
|
|
297
335
|
</div>
|
|
298
336
|
</li>
|
|
299
|
-
`}getSortedTables(){var t;const e=[...((t=this.config)==null?void 0:t.tables)||[]];return e.sort((i,
|
|
337
|
+
`}getSortedTables(){var t;const e=[...((t=this.config)==null?void 0:t.tables)||[]];return e.sort((i,s)=>{var n,a;let o=0;switch(this.tableSortBy){case"name":o=i.name.localeCompare(s.name);break;case"count":o=i.documentCount-s.documentCount;break;case"fields":o=(((n=i.inferredFields)==null?void 0:n.length)||0)-(((a=s.inferredFields)==null?void 0:a.length)||0);break}return this.tableSortOrder==="asc"?o:-o}),e}getFilteredTablesForSidebar(e){return this.searchQuery?e.filter(t=>t.name.toLowerCase().includes(this.searchQuery.toLowerCase())):e}truncateUrl(e){if(e.length<=35)return e;const t=e.match(/https?:\/\/([^\/]+)/);if(t){const i=t[1];return i.length>35?i.slice(0,32)+"...":i}return e.slice(0,32)+"..."}toggleSidebarSection(e){this.sidebarSections[e].collapsed=!this.sidebarSections[e].collapsed;const t=document.querySelector(`[data-section="${e}"]`);t&&t.classList.toggle("collapsed",this.sidebarSections[e].collapsed)}cycleSortOrder(){const e=[{by:"name",order:"asc"},{by:"name",order:"desc"},{by:"count",order:"desc"},{by:"count",order:"asc"},{by:"fields",order:"desc"},{by:"fields",order:"asc"}],i=(e.findIndex(s=>s.by===this.tableSortBy&&s.order===this.tableSortOrder)+1)%e.length;this.tableSortBy=e[i].by,this.tableSortOrder=e[i].order,this.updateSidebarTableList()}updateSidebarTableList(){const e=document.getElementById("graphTableList");if(e){const t=this.getSortedTables(),i=this.getFilteredTablesForSidebar(t);e.innerHTML=i.map(s=>this.renderSidebarTableItem(s)).join(""),this.setupSidebarTableEvents()}}setupSidebarTableEvents(){document.querySelectorAll(".sidebar-table-item").forEach(e=>{e.addEventListener("click",()=>{const t=e.dataset.table;if(t){this.selectTable(t);const i=this.graphNodes.find(s=>s.id===t);i&&this.centerOnNode(i)}})})}centerOnNode(e){if(!this.canvas)return;const t=this.canvas.width/this.dpr,i=this.canvas.height/this.dpr,s=e.x+e.width/2,o=e.y+e.height/2;this.panX=t/2-s*this.zoom,this.panY=i/2-o*this.zoom,this.drawGraph()}showTooltip(e,t,i,s,o){this.tooltipTimeout&&clearTimeout(this.tooltipTimeout),this.tooltipTimeout=window.setTimeout(()=>{this.tooltip={visible:!0,x:e,y:t,title:i,content:s,type:o},this.renderTooltip()},400)}hideTooltip(){var e;this.tooltipTimeout&&(clearTimeout(this.tooltipTimeout),this.tooltipTimeout=null),this.tooltip=null,(e=document.getElementById("tooltip"))==null||e.remove()}renderTooltip(){var a;if(!this.tooltip)return;(a=document.getElementById("tooltip"))==null||a.remove();const e=document.createElement("div");e.id="tooltip",e.className=`tooltip tooltip-${this.tooltip.type}`,e.innerHTML=`
|
|
300
338
|
<div class="tooltip-title">${this.tooltip.title}</div>
|
|
301
339
|
<div class="tooltip-content">${this.tooltip.content}</div>
|
|
302
|
-
`;let t=this.tooltip.x+12,i=this.tooltip.y+12;document.body.appendChild(e);const
|
|
340
|
+
`;let t=this.tooltip.x+12,i=this.tooltip.y+12;document.body.appendChild(e);const s=e.getBoundingClientRect(),o=window.innerWidth,n=window.innerHeight;t+s.width>o-10&&(t=this.tooltip.x-s.width-12),i+s.height>n-10&&(i=this.tooltip.y-s.height-12),e.style.left=`${t}px`,e.style.top=`${i}px`}getFieldTooltipContent(e,t){const i={_id:{title:"Document ID",content:`Unique identifier auto-generated by Convex.<br><code>Id<"${t}"></code>`},_creationTime:{title:"Creation Timestamp",content:"Unix timestamp (ms) when this document was created.<br>Auto-set by Convex on insert."}};if(i[e.name])return i[e.name];if(e.type.includes("Id<")){const s=e.type.match(/Id<["'](\w+)["']>/);return{title:"Reference Field",content:`Links to the <strong>${s?s[1]:"unknown"}</strong> table.<br>Type: <code>${e.type}</code>`}}return{title:e.name,content:`Type: <code>${e.type}</code>${e.optional?"<br><em>Optional field</em>":""}`}}getFieldAtPosition(e,t){const i=this.getSortedFields(e.table.inferredFields||[]),s=44,o=24,n=t-e.y-s;if(n<0)return null;const a=Math.floor(n/o);return a>=0&&a<Math.min(i.length,12)?i[a]:null}renderToolbar(){const e=this.historyIndex>0,t=this.historyIndex<this.positionHistory.length-1;return`
|
|
303
341
|
<div class="toolbar">
|
|
304
342
|
<div class="toolbar-group">
|
|
305
343
|
<button class="toolbar-btn ${this.showCodePanel?"active":""}" id="viewCodeBtn" title="Toggle Code Panel (C)">
|
|
@@ -477,14 +515,16 @@ var V=Object.defineProperty;var X=(x,e,t)=>e in x?V(x,e,{enumerable:!0,configura
|
|
|
477
515
|
<button class="btn btn-primary" id="filterApply">Apply</button>
|
|
478
516
|
</div>
|
|
479
517
|
</div>
|
|
480
|
-
`:""}generateSchemaCode(){var n,o,s;const e=((n=this.config)==null?void 0:n.tables)||[],t=((o=this.config)==null?void 0:o.allDocuments)||{},i={};for(const a of e){const r={};if(a.inferredFields)for(const l of a.inferredFields)r[l.name]=l.type+(l.optional?"?":"");i[a.name]={fields:r,documentCount:a.documentCount,sample:((s=t[a.name])==null?void 0:s[0])||null}}return this.syntaxHighlight(JSON.stringify(i,null,2))}syntaxHighlight(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?)/g,t=>{let i="json-string";return/:$/.test(t)&&(i="json-key"),`<span class="${i}">${t}</span>`}).replace(/\b(true|false)\b/g,'<span class="json-boolean">$1</span>').replace(/\b(null)\b/g,'<span class="json-null">$1</span>').replace(/\b(-?\d+\.?\d*)\b/g,'<span class="json-number">$1</span>')}initGraphView(){if(this.canvas=document.getElementById("graphCanvas"),!this.canvas)return;this.dpr=window.devicePixelRatio||1;const e=this.canvas.parentElement;if(e){const t=e.clientWidth,i=e.clientHeight;this.canvas.width=t*this.dpr,this.canvas.height=i*this.dpr,this.canvas.style.width=t+"px",this.canvas.style.height=i+"px"}this.ctx=this.canvas.getContext("2d"),this.ctx&&(this.ctx.scale(this.dpr,this.dpr),this.calculateGraphLayout(),this.setupGraphEvents(),this.drawGraph(),this.savePositionState(),window.addEventListener("resize",()=>{if(this.canvas&&this.canvas.parentElement&&this.ctx){const t=this.canvas.parentElement.clientWidth,i=this.canvas.parentElement.clientHeight;this.dpr=window.devicePixelRatio||1,this.canvas.width=t*this.dpr,this.canvas.height=i*this.dpr,this.canvas.style.width=t+"px",this.canvas.style.height=i+"px",this.ctx.scale(this.dpr,this.dpr),this.drawGraph()}}))}calculateGraphLayout(){var a,r;const e=((a=this.config)==null?void 0:a.tables)||[];(r=this.config)!=null&&r.allDocuments,this.graphNodes=[],this.graphEdges=[];const t=220,i=160,n=80,o=60,s=Math.ceil(Math.sqrt(e.length));e.forEach((l,d)=>{const h=d%s,c=Math.floor(d/s);this.graphNodes.push({id:l.name,x:100+h*(t+n),y:100+c*(i+o),width:t,height:i,table:l})});for(const l of this.graphNodes){const d=l.table.inferredFields||[];for(const h of d){const c=h.type.match(/Id<["'](\w+)["']>/);if(c){const u=c[1],g=this.graphNodes.find(b=>b.id===u);if(g&&g.id!==l.id){this.graphEdges.push({from:l.id,to:g.id,fromField:h.name,toField:"_id",inferred:!1});continue}}const p=h.name.toLowerCase();for(const u of this.graphNodes){if(u.id===l.id)continue;const g=u.id.toLowerCase(),b=g.endsWith("s")?g.slice(0,-1):g;(p===g+"id"||p===b+"id"||p===b+"_id"||p===g+"_id")&&(this.graphEdges.some(w=>w.from===l.id&&w.to===u.id&&w.fromField===h.name)||this.graphEdges.push({from:l.id,to:u.id,fromField:h.name,toField:"_id",inferred:!0}))}}}if(this.canvas&&this.graphNodes.length>0){const l=Math.min(...this.graphNodes.map(f=>f.x)),d=Math.max(...this.graphNodes.map(f=>f.x+f.width)),h=Math.min(...this.graphNodes.map(f=>f.y)),c=Math.max(...this.graphNodes.map(f=>f.y+f.height)),p=d-l,u=c-h,g=this.canvas.width/this.dpr,b=this.canvas.height/this.dpr;this.panX=(g-p)/2-l,this.panY=(b-u)/2-h}}setupGraphEvents(){var i,n,o,s;if(!this.canvas)return;let e=0,t=0;this.canvas.addEventListener("mousedown",a=>{const r=this.canvas.getBoundingClientRect(),l=(a.clientX-r.left-this.panX)/this.zoom,d=(a.clientY-r.top-this.panY)/this.zoom;this.selectedNode=this.graphNodes.find(h=>l>=h.x&&l<=h.x+h.width&&d>=h.y&&d<=h.y+h.height)||null,this.selectedNode&&(this.selectTable(this.selectedNode.id),this.updateCodePanel()),this.isDragging=!0,e=a.clientX,t=a.clientY,this.dragStartX=a.clientX,this.dragStartY=a.clientY}),this.canvas.addEventListener("mousemove",a=>{const r=this.canvas.getBoundingClientRect(),l=(a.clientX-r.left-this.panX)/this.zoom,d=(a.clientY-r.top-this.panY)/this.zoom,h=this.graphNodes.find(c=>c.visible!==!1&&l>=c.x&&l<=c.x+c.width&&d>=c.y&&d<=c.y+c.height)||null;if(h!==this.hoveredNode&&(this.hoveredNode=h,this.canvas.style.cursor=h?"pointer":"grab",this.hideTooltip(),this.drawGraph()),h&&!this.isDragging){const c=this.getFieldAtPosition(h,d);if(c){const p=this.getFieldTooltipContent(c,h.table.name);this.showTooltip(a.clientX,a.clientY,p.title,p.content,"field")}else this.hideTooltip()}else h||this.hideTooltip();if(this.isDragging){if(this.hideTooltip(),this.selectedNode){const c=(a.clientX-e)/this.zoom,p=(a.clientY-t)/this.zoom;this.selectedNode.x+=c,this.selectedNode.y+=p}else this.panX+=a.clientX-e,this.panY+=a.clientY-t;e=a.clientX,t=a.clientY,this.drawGraph()}}),this.canvas.addEventListener("mouseup",a=>{this.isDragging&&(this.selectedNode||Math.abs(a.clientX-this.dragStartX)>5||Math.abs(a.clientY-this.dragStartY)>5)&&this.savePositionState(),this.isDragging=!1,this.selectedNode=null}),this.canvas.addEventListener("mouseleave",()=>{this.isDragging=!1,this.selectedNode=null,this.hoveredNode=null,this.hideTooltip(),this.drawGraph()}),this.canvas.addEventListener("wheel",a=>{a.preventDefault();const r=this.canvas.getBoundingClientRect(),l=a.clientX-r.left,d=a.clientY-r.top,h=this.zoom,c=a.deltaY>0?.9:1.1;this.zoom=Math.max(.25,Math.min(2,this.zoom*c)),this.panX=l-(l-this.panX)*(this.zoom/h),this.panY=d-(d-this.panY)*(this.zoom/h),this.drawGraph()}),(i=document.getElementById("zoomIn"))==null||i.addEventListener("click",()=>{this.zoom=Math.min(2,this.zoom*1.2),this.updateZoomDisplay(),this.drawGraph()}),(n=document.getElementById("zoomOut"))==null||n.addEventListener("click",()=>{this.zoom=Math.max(.25,this.zoom/1.2),this.updateZoomDisplay(),this.drawGraph()}),(o=document.getElementById("fitView"))==null||o.addEventListener("click",()=>{this.fitToView()}),(s=document.getElementById("resetView"))==null||s.addEventListener("click",()=>{this.zoom=1,this.calculateGraphLayout(),this.updateZoomDisplay(),this.drawGraph()})}drawGraph(){if(!this.ctx||!this.canvas)return;const e=this.ctx,t=this.getThemeColors();this.canvas.width/this.dpr,this.canvas.height/this.dpr,e.save(),e.setTransform(1,0,0,1,0,0),e.fillStyle=t.bgPrimary,e.fillRect(0,0,this.canvas.width,this.canvas.height),e.restore(),e.save(),e.translate(this.panX,this.panY),e.scale(this.zoom,this.zoom),this.drawGridPattern(e);for(const i of this.graphEdges){const n=this.graphNodes.find(s=>s.id===i.from),o=this.graphNodes.find(s=>s.id===i.to);n&&o&&n.visible!==!1&&o.visible!==!1&&this.drawEdge(e,n,o,i)}for(const i of this.graphNodes)i.visible!==!1&&this.drawNode(e,i);e.restore()}drawGridPattern(e){const t=this.getThemeColors(),i=40,n=-this.panX/this.zoom-1e3,o=-this.panY/this.zoom-1e3,s=n+3e3,a=o+3e3;e.strokeStyle=t.gridLine,e.lineWidth=.5;for(let r=Math.floor(n/i)*i;r<s;r+=i)e.beginPath(),e.moveTo(r,o),e.lineTo(r,a),e.stroke();for(let r=Math.floor(o/i)*i;r<a;r+=i)e.beginPath(),e.moveTo(n,r),e.lineTo(s,r),e.stroke()}drawNode(e,t){const i=this.getThemeColors(),n=this.currentTheme==="dark",o=this.hoveredNode===t,s=this.selectedTable===t.id,a=t.x,r=t.y,l=t.width,d=10,h=this.getSortedFields(t.table.inferredFields||[]),c=12,p=h.slice(0,c),u=h.length>c,g=44,b=24,f=12,w=u?28:0,y=g+p.length*b+f+w;t.height=y;const v=n?.4:.08,C=n?.5:.15;e.shadowColor=o||s?`rgba(235, 86, 1, ${C})`:`rgba(0, 0, 0, ${v})`,e.shadowBlur=o?16:8,e.shadowOffsetX=0,e.shadowOffsetY=o?6:3,e.fillStyle=i.nodeBg,e.beginPath(),e.roundRect(a,r,l,y,d),e.fill(),e.shadowColor="transparent",e.strokeStyle=s?i.accentInteractive:o?i.accent:i.nodeBorder,e.lineWidth=s?2:1,e.stroke();const S=e.createLinearGradient(a,r,a,r+g);s?(S.addColorStop(0,i.accentInteractive),S.addColorStop(1,i.accentHover)):(S.addColorStop(0,i.nodeHeader),S.addColorStop(1,n?i.bgSecondary:i.bgHover)),e.fillStyle=S,e.beginPath(),e.roundRect(a,r,l,g,[d,d,0,0]),e.fill(),e.strokeStyle=s?"rgba(255,255,255,0.1)":i.border,e.lineWidth=1,e.beginPath(),e.moveTo(a,r+g),e.lineTo(a+l,r+g),e.stroke(),e.fillStyle=s?"#ffffff":i.textPrimary,e.font='bold 14px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',e.textBaseline="middle";const k=t.table.name,$=l-100;let L=k;if(e.measureText(k).width>$){for(;e.measureText(L+"...").width>$&&L.length>0;)L=L.slice(0,-1);L+="..."}e.fillText(L,a+14,r+g/2);const I=`${h.length} fields`;e.font="11px -apple-system, BlinkMacSystemFont, sans-serif";const B=e.measureText(I).width+14;e.fillStyle=s?"rgba(255,255,255,0.25)":n?"rgba(255,255,255,0.1)":"rgba(0,0,0,0.06)",e.beginPath(),e.roundRect(a+l-B-10,r+(g-20)/2,B,20,10),e.fill(),e.fillStyle=s?"rgba(255,255,255,0.9)":i.textSecondary,e.textAlign="center",e.fillText(I,a+l-B/2-10,r+g/2),e.textAlign="left";let M=r+g+8;for(const T of p){const z=T.name.startsWith("_"),P=T.name==="_id",N=T.type.includes("Id<");o&&(e.fillStyle=n?"rgba(255, 255, 255, 0.03)":"rgba(0, 0, 0, 0.02)",e.fillRect(a+4,M-6,l-8,b)),P&&this.drawKeyIcon(e,a+12,M+2,s,i);const F=P?a+28:a+14;if(e.font='12px "SF Mono", Monaco, "Cascadia Code", monospace',z?e.fillStyle=i.textMuted:N?e.fillStyle=i.accent:e.fillStyle=i.accentInteractive,e.fillText(T.name,F,M+4),T.optional){const D=e.measureText(T.name).width;e.fillStyle=i.warning,e.font="10px -apple-system, sans-serif",e.fillText("?",F+D+2,M+3)}e.font='11px "SF Mono", Monaco, "Cascadia Code", monospace',e.fillStyle=z?i.textMuted:i.textSecondary,e.textAlign="right";let E=T.type;const H=80;if(e.measureText(E).width>H){for(;e.measureText(E+"...").width>H&&E.length>0;)E=E.slice(0,-1);E+="..."}e.fillText(E,a+l-12,M+4),e.textAlign="left",M+=b}if(u){const T=h.length-c;e.fillStyle=i.bgSecondary,e.beginPath(),e.roundRect(a+4,M,l-8,24,[0,0,d-4,d-4]),e.fill(),e.fillStyle=i.textSecondary,e.font="11px -apple-system, BlinkMacSystemFont, sans-serif",e.textAlign="center",e.fillText(`+ ${T} more field${T>1?"s":""}`,a+l/2,M+14),e.textAlign="left"}}getSortedFields(e){const t=["_id","_creationTime"];return[...e].sort((i,n)=>{const o=t.includes(i.name),s=t.includes(n.name);return o&&!s?-1:!o&&s?1:o&&s?t.indexOf(i.name)-t.indexOf(n.name):i.name.localeCompare(n.name)})}drawKeyIcon(e,t,i,n,o){e.save(),e.fillStyle=n?o.accentInteractive:o.warning,e.beginPath(),e.arc(t+4,i,4,0,Math.PI*2),e.fill(),e.fillRect(t+7,i-1,6,2),e.fillRect(t+10,i-1,1,3),e.fillRect(t+12,i-1,1,2),e.restore()}drawEdge(e,t,i,n){const o=this.hoveredNode&&(this.hoveredNode.id===n.from||this.hoveredNode.id===n.to),s=this.calculateAttachmentPoints(t,i),a=Math.sqrt(Math.pow(s.toX-s.fromX,2)+Math.pow(s.toY-s.fromY,2)),r=Math.min(a*.4,100);let l,d,h,c;s.fromSide==="right"?(l=s.fromX+r,d=s.fromY):s.fromSide==="left"?(l=s.fromX-r,d=s.fromY):s.fromSide==="bottom"?(l=s.fromX,d=s.fromY+r):(l=s.fromX,d=s.fromY-r),s.toSide==="left"?(h=s.toX-r,c=s.toY):s.toSide==="right"?(h=s.toX+r,c=s.toY):s.toSide==="top"?(h=s.toX,c=s.toY-r):(h=s.toX,c=s.toY+r),o&&(e.save(),e.shadowColor="rgba(235, 86, 1, 0.3)",e.shadowBlur=8,e.shadowOffsetX=0,e.shadowOffsetY=0),e.strokeStyle=o?"#EB5601":"#8b7355",e.lineWidth=o?2.5:1.5,n.inferred?e.setLineDash([6,4]):e.setLineDash([]),e.beginPath(),e.moveTo(s.fromX,s.fromY),e.bezierCurveTo(l,d,h,c,s.toX,s.toY),e.stroke(),e.setLineDash([]),o&&e.restore(),this.drawArrowHead(e,h,c,s.toX,s.toY,o);const p=.5,u=this.bezierPoint(s.fromX,l,h,s.toX,p),g=this.bezierPoint(s.fromY,d,c,s.toY,p)-12;e.font="10px -apple-system, BlinkMacSystemFont, sans-serif";const b=n.fromField,f=e.measureText(b).width+10,w=16;e.fillStyle=o?"rgba(235, 86, 1, 0.1)":"rgba(255, 255, 255, 0.95)",e.beginPath(),e.roundRect(u-f/2,g-w/2,f,w,4),e.fill(),e.strokeStyle=o?"rgba(235, 86, 1, 0.3)":"#e6e4e1",e.lineWidth=1,e.stroke(),e.fillStyle=o?"#EB5601":"#6b6b6b",e.textAlign="center",e.textBaseline="middle",e.fillText(b,u,g),e.textAlign="left",e.textBaseline="alphabetic"}calculateAttachmentPoints(e,t){const i=e.x+e.width/2,n=e.y+e.height/2,o=t.x+t.width/2,s=t.y+t.height/2,a=o-i,r=s-n;let l,d,h,c,p,u;return Math.abs(a)>Math.abs(r)?a>0?(l="right",d="left",h=e.x+e.width,c=n,p=t.x,u=s):(l="left",d="right",h=e.x,c=n,p=t.x+t.width,u=s):r>0?(l="bottom",d="top",h=i,c=e.y+e.height,p=o,u=t.y):(l="top",d="bottom",h=i,c=e.y,p=o,u=t.y+t.height),{fromX:h,fromY:c,fromSide:l,toX:p,toY:u,toSide:d}}drawArrowHead(e,t,i,n,o,s){const a=Math.atan2(o-i,n-t),r=s?10:8;e.fillStyle=s?"#EB5601":"#8b7355",e.beginPath(),e.moveTo(n,o),e.lineTo(n-r*Math.cos(a-Math.PI/6),o-r*Math.sin(a-Math.PI/6)),e.lineTo(n-r*Math.cos(a+Math.PI/6),o-r*Math.sin(a+Math.PI/6)),e.closePath(),e.fill()}bezierPoint(e,t,i,n,o){const s=1-o;return s*s*s*e+3*s*s*o*t+3*s*o*o*i+o*o*o*n}updateCodePanel(){var i,n,o,s;const e=document.getElementById("codeBlock"),t=document.querySelector(".code-filename");if(e&&this.selectedTable){const a=(n=(i=this.config)==null?void 0:i.tables)==null?void 0:n.find(l=>l.name===this.selectedTable),r=((s=(o=this.config)==null?void 0:o.allDocuments)==null?void 0:s[this.selectedTable])||[];if(a){const l={table:this.selectedTable,documentCount:a.documentCount,fields:{}};if(a.inferredFields)for(const d of a.inferredFields)l.fields[d.name]={type:d.type,required:!d.optional};r.length>0&&(l.sample=r[0]),e.innerHTML=this.syntaxHighlight(JSON.stringify(l,null,2))}}t&&this.selectedTable&&(t.textContent=`${this.selectedTable}.json`)}savePositionState(){const e={nodes:this.graphNodes.map(t=>({id:t.id,x:t.x,y:t.y})),panX:this.panX,panY:this.panY,zoom:this.zoom};this.historyIndex<this.positionHistory.length-1&&(this.positionHistory=this.positionHistory.slice(0,this.historyIndex+1)),this.positionHistory.push(e),this.positionHistory.length>this.maxHistorySize?this.positionHistory.shift():this.historyIndex++,this.updateUndoRedoButtons()}undo(){this.historyIndex<=0||(this.historyIndex--,this.restorePositionState(this.positionHistory[this.historyIndex]),this.updateUndoRedoButtons())}redo(){this.historyIndex>=this.positionHistory.length-1||(this.historyIndex++,this.restorePositionState(this.positionHistory[this.historyIndex]),this.updateUndoRedoButtons())}restorePositionState(e){for(const t of e.nodes){const i=this.graphNodes.find(n=>n.id===t.id);i&&(i.x=t.x,i.y=t.y)}this.panX=e.panX,this.panY=e.panY,this.zoom=e.zoom,this.updateZoomDisplay(),this.drawGraph()}updateUndoRedoButtons(){const e=document.getElementById("undoBtn"),t=document.getElementById("redoBtn");e&&(e.disabled=this.historyIndex<=0),t&&(t.disabled=this.historyIndex>=this.positionHistory.length-1)}fitToView(){if(this.graphNodes.length===0)return;const e=this.graphNodes.filter(p=>p.visible!==!1);if(e.length===0)return;const t=60,i=Math.min(...e.map(p=>p.x))-t,n=Math.max(...e.map(p=>p.x+p.width))+t,o=Math.min(...e.map(p=>p.y))-t,s=Math.max(...e.map(p=>p.y+p.height))+t,a=n-i,r=s-o;if(!this.canvas)return;const l=this.canvas.width/this.dpr,d=this.canvas.height/this.dpr,h=l/a,c=d/r;this.zoom=Math.min(h,c,1.5),this.zoom=Math.max(this.zoom,.25),this.panX=(l-a*this.zoom)/2-i*this.zoom,this.panY=(d-r*this.zoom)/2-o*this.zoom,this.updateZoomDisplay(),this.drawGraph()}autoArrange(){this.calculateGraphLayout(),this.drawGraph(),this.savePositionState()}updateZoomDisplay(){const e=document.getElementById("zoomLevel"),t=document.getElementById("zoomDisplay"),i=`${Math.round(this.zoom*100)}%`;e&&(e.textContent=i),t&&(t.textContent=i)}toggleCodePanel(){this.showCodePanel=!this.showCodePanel;const e=document.getElementById("sidebarContainer"),t=document.getElementById("viewCodeBtn");e&&e.classList.toggle("hidden",!this.showCodePanel),t&&t.classList.toggle("active",this.showCodePanel),setTimeout(()=>this.resizeCanvas(),50)}toggleExportMenu(){this.showExportMenu=!this.showExportMenu,this.showFilterDropdown=!1,this.renderDropdowns()}toggleFilterDropdown(){this.showFilterDropdown=!this.showFilterDropdown,this.showExportMenu=!1,this.renderDropdowns()}renderDropdowns(){var t,i;(t=document.getElementById("exportMenu"))==null||t.remove(),(i=document.getElementById("filterMenu"))==null||i.remove();const e=document.querySelector(".graph-view");e&&(this.showExportMenu&&(e.insertAdjacentHTML("beforeend",this.renderExportMenu()),this.setupExportMenuEvents()),this.showFilterDropdown&&(e.insertAdjacentHTML("beforeend",this.renderFilterDropdown()),this.setupFilterMenuEvents()))}setupExportMenuEvents(){var e,t,i;(e=document.getElementById("exportJson"))==null||e.addEventListener("click",()=>{this.exportSchema("json"),this.showExportMenu=!1,this.renderDropdowns()}),(t=document.getElementById("exportTs"))==null||t.addEventListener("click",()=>{this.exportSchema("typescript"),this.showExportMenu=!1,this.renderDropdowns()}),(i=document.getElementById("exportPng"))==null||i.addEventListener("click",()=>{this.exportAsPng(),this.showExportMenu=!1,this.renderDropdowns()}),setTimeout(()=>{document.addEventListener("click",this.handleOutsideClick.bind(this),{once:!0})},0)}setupFilterMenuEvents(){var e,t,i;(e=document.getElementById("filterClose"))==null||e.addEventListener("click",()=>{this.showFilterDropdown=!1,this.renderDropdowns()}),(t=document.getElementById("filterApply"))==null||t.addEventListener("click",()=>{this.applyFilters()}),(i=document.getElementById("filterClear"))==null||i.addEventListener("click",()=>{this.clearFilters()})}handleOutsideClick(e){const t=document.getElementById("exportMenu"),i=document.getElementById("exportBtn"),n=document.getElementById("filterMenu"),o=document.getElementById("filterBtn");t&&!t.contains(e.target)&&!(i!=null&&i.contains(e.target))&&(this.showExportMenu=!1,this.renderDropdowns()),n&&!n.contains(e.target)&&!(o!=null&&o.contains(e.target))&&(this.showFilterDropdown=!1,this.renderDropdowns())}exportSchema(e){var s;const t=((s=this.config)==null?void 0:s.tables)||[];let i,n,o;if(e==="json"){const a={};for(const r of t)a[r.name]={documentCount:r.documentCount,fields:(r.inferredFields||[]).reduce((l,d)=>(l[d.name]={type:d.type,required:!d.optional},l),{})};i=JSON.stringify(a,null,2),n="convex-schema.json",o="application/json"}else{const a=['import { defineSchema, defineTable } from "convex/server";','import { v } from "convex/values";',"","export default defineSchema({"];for(const r of t){const l=(r.inferredFields||[]).filter(d=>!d.name.startsWith("_")).map(d=>` ${d.name}: ${this.toConvexValidator(d.type)}${d.optional?".optional()":""},`).join(`
|
|
518
|
+
`:""}generateSchemaCode(){var s,o,n;const e=((s=this.config)==null?void 0:s.tables)||[],t=((o=this.config)==null?void 0:o.allDocuments)||{},i={};for(const a of e){const r={};if(a.inferredFields)for(const l of a.inferredFields)r[l.name]=l.type+(l.optional?"?":"");i[a.name]={fields:r,documentCount:a.documentCount,sample:((n=t[a.name])==null?void 0:n[0])||null}}return this.syntaxHighlight(JSON.stringify(i,null,2))}syntaxHighlight(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?)/g,t=>{let i="json-string";return/:$/.test(t)&&(i="json-key"),`<span class="${i}">${t}</span>`}).replace(/\b(true|false)\b/g,'<span class="json-boolean">$1</span>').replace(/\b(null)\b/g,'<span class="json-null">$1</span>').replace(/\b(-?\d+\.?\d*)\b/g,'<span class="json-number">$1</span>')}initGraphView(){if(this.canvas=document.getElementById("graphCanvas"),!this.canvas)return;this.dpr=window.devicePixelRatio||1;const e=this.canvas.parentElement;if(e){const t=e.clientWidth,i=e.clientHeight;this.canvas.width=t*this.dpr,this.canvas.height=i*this.dpr,this.canvas.style.width=t+"px",this.canvas.style.height=i+"px"}this.ctx=this.canvas.getContext("2d"),this.ctx&&(this.ctx.scale(this.dpr,this.dpr),this.calculateGraphLayout(),this.setupGraphEvents(),this.drawGraph(),this.savePositionState(),window.addEventListener("resize",()=>{if(this.canvas&&this.canvas.parentElement&&this.ctx){const t=this.canvas.parentElement.clientWidth,i=this.canvas.parentElement.clientHeight;this.dpr=window.devicePixelRatio||1,this.canvas.width=t*this.dpr,this.canvas.height=i*this.dpr,this.canvas.style.width=t+"px",this.canvas.style.height=i+"px",this.ctx.scale(this.dpr,this.dpr),this.drawGraph()}}))}calculateGraphLayout(){var a,r;const e=((a=this.config)==null?void 0:a.tables)||[];(r=this.config)!=null&&r.allDocuments,this.graphNodes=[],this.graphEdges=[];const t=220,i=160,s=80,o=60,n=Math.ceil(Math.sqrt(e.length));e.forEach((l,d)=>{const h=d%n,c=Math.floor(d/n);this.graphNodes.push({id:l.name,x:100+h*(t+s),y:100+c*(i+o),width:t,height:i,table:l})});for(const l of this.graphNodes){const d=l.table.inferredFields||[];for(const h of d){const c=h.type.match(/Id<["'](\w+)["']>/);if(c){const g=c[1],u=this.graphNodes.find(y=>y.id===g);if(u&&u.id!==l.id){this.graphEdges.push({from:l.id,to:u.id,fromField:h.name,toField:"_id",inferred:!1});continue}}const p=h.name.toLowerCase();for(const g of this.graphNodes){if(g.id===l.id)continue;const u=g.id.toLowerCase(),y=u.endsWith("s")?u.slice(0,-1):u;(p===u+"id"||p===y+"id"||p===y+"_id"||p===u+"_id")&&(this.graphEdges.some(w=>w.from===l.id&&w.to===g.id&&w.fromField===h.name)||this.graphEdges.push({from:l.id,to:g.id,fromField:h.name,toField:"_id",inferred:!0}))}}}if(this.canvas&&this.graphNodes.length>0){const l=Math.min(...this.graphNodes.map(f=>f.x)),d=Math.max(...this.graphNodes.map(f=>f.x+f.width)),h=Math.min(...this.graphNodes.map(f=>f.y)),c=Math.max(...this.graphNodes.map(f=>f.y+f.height)),p=d-l,g=c-h,u=this.canvas.width/this.dpr,y=this.canvas.height/this.dpr;this.panX=(u-p)/2-l,this.panY=(y-g)/2-h}}setupGraphEvents(){var i,s,o,n;if(!this.canvas)return;let e=0,t=0;this.canvas.addEventListener("mousedown",a=>{const r=this.canvas.getBoundingClientRect(),l=(a.clientX-r.left-this.panX)/this.zoom,d=(a.clientY-r.top-this.panY)/this.zoom;this.selectedNode=this.graphNodes.find(h=>l>=h.x&&l<=h.x+h.width&&d>=h.y&&d<=h.y+h.height)||null,this.selectedNode&&(this.selectTable(this.selectedNode.id),this.updateCodePanel()),this.isDragging=!0,e=a.clientX,t=a.clientY,this.dragStartX=a.clientX,this.dragStartY=a.clientY}),this.canvas.addEventListener("mousemove",a=>{const r=this.canvas.getBoundingClientRect(),l=(a.clientX-r.left-this.panX)/this.zoom,d=(a.clientY-r.top-this.panY)/this.zoom,h=this.graphNodes.find(c=>c.visible!==!1&&l>=c.x&&l<=c.x+c.width&&d>=c.y&&d<=c.y+c.height)||null;if(h!==this.hoveredNode&&(this.hoveredNode=h,this.canvas.style.cursor=h?"pointer":"grab",this.hideTooltip(),this.drawGraph()),h&&!this.isDragging){const c=this.getFieldAtPosition(h,d);if(c){const p=this.getFieldTooltipContent(c,h.table.name);this.showTooltip(a.clientX,a.clientY,p.title,p.content,"field")}else this.hideTooltip()}else h||this.hideTooltip();if(this.isDragging){if(this.hideTooltip(),this.selectedNode){const c=(a.clientX-e)/this.zoom,p=(a.clientY-t)/this.zoom;this.selectedNode.x+=c,this.selectedNode.y+=p}else this.panX+=a.clientX-e,this.panY+=a.clientY-t;e=a.clientX,t=a.clientY,this.drawGraph()}}),this.canvas.addEventListener("mouseup",a=>{this.isDragging&&(this.selectedNode||Math.abs(a.clientX-this.dragStartX)>5||Math.abs(a.clientY-this.dragStartY)>5)&&this.savePositionState(),this.isDragging=!1,this.selectedNode=null}),this.canvas.addEventListener("mouseleave",()=>{this.isDragging=!1,this.selectedNode=null,this.hoveredNode=null,this.hideTooltip(),this.drawGraph()}),this.canvas.addEventListener("wheel",a=>{a.preventDefault();const r=this.canvas.getBoundingClientRect(),l=a.clientX-r.left,d=a.clientY-r.top,h=this.zoom,c=a.deltaY>0?.9:1.1;this.zoom=Math.max(.25,Math.min(2,this.zoom*c)),this.panX=l-(l-this.panX)*(this.zoom/h),this.panY=d-(d-this.panY)*(this.zoom/h),this.drawGraph()}),(i=document.getElementById("zoomIn"))==null||i.addEventListener("click",()=>{this.zoom=Math.min(2,this.zoom*1.2),this.updateZoomDisplay(),this.drawGraph()}),(s=document.getElementById("zoomOut"))==null||s.addEventListener("click",()=>{this.zoom=Math.max(.25,this.zoom/1.2),this.updateZoomDisplay(),this.drawGraph()}),(o=document.getElementById("fitView"))==null||o.addEventListener("click",()=>{this.fitToView()}),(n=document.getElementById("resetView"))==null||n.addEventListener("click",()=>{this.zoom=1,this.calculateGraphLayout(),this.updateZoomDisplay(),this.drawGraph()})}drawGraph(){if(!this.ctx||!this.canvas)return;const e=this.ctx,t=this.getThemeColors();this.canvas.width/this.dpr,this.canvas.height/this.dpr,e.save(),e.setTransform(1,0,0,1,0,0),e.fillStyle=t.bgPrimary,e.fillRect(0,0,this.canvas.width,this.canvas.height),e.restore(),e.save(),e.translate(this.panX,this.panY),e.scale(this.zoom,this.zoom),this.drawGridPattern(e);for(const i of this.graphEdges){const s=this.graphNodes.find(n=>n.id===i.from),o=this.graphNodes.find(n=>n.id===i.to);s&&o&&s.visible!==!1&&o.visible!==!1&&this.drawEdge(e,s,o,i)}for(const i of this.graphNodes)i.visible!==!1&&this.drawNode(e,i);e.restore()}drawGridPattern(e){const t=this.getThemeColors(),i=40,s=-this.panX/this.zoom-1e3,o=-this.panY/this.zoom-1e3,n=s+3e3,a=o+3e3;e.strokeStyle=t.gridLine,e.lineWidth=.5;for(let r=Math.floor(s/i)*i;r<n;r+=i)e.beginPath(),e.moveTo(r,o),e.lineTo(r,a),e.stroke();for(let r=Math.floor(o/i)*i;r<a;r+=i)e.beginPath(),e.moveTo(s,r),e.lineTo(n,r),e.stroke()}drawNode(e,t){const i=this.getThemeColors(),s=this.currentTheme==="dark",o=this.hoveredNode===t,n=this.selectedTable===t.id,a=t.x,r=t.y,l=t.width,d=10,h=this.getSortedFields(t.table.inferredFields||[]),c=12,p=h.slice(0,c),g=h.length>c,u=44,y=24,f=12,w=g?28:0,L=u+p.length*y+f+w;t.height=L;const k=s?.4:.08,B=s?.5:.15;e.shadowColor=o||n?`rgba(235, 86, 1, ${B})`:`rgba(0, 0, 0, ${k})`,e.shadowBlur=o?16:8,e.shadowOffsetX=0,e.shadowOffsetY=o?6:3,e.fillStyle=i.nodeBg,e.beginPath(),e.roundRect(a,r,l,L,d),e.fill(),e.shadowColor="transparent",e.strokeStyle=n?i.accentInteractive:o?i.accent:i.nodeBorder,e.lineWidth=n?2:1,e.stroke();const v=e.createLinearGradient(a,r,a,r+u);n?(v.addColorStop(0,i.accentInteractive),v.addColorStop(1,i.accentHover)):(v.addColorStop(0,i.nodeHeader),v.addColorStop(1,s?i.bgSecondary:i.bgHover)),e.fillStyle=v,e.beginPath(),e.roundRect(a,r,l,u,[d,d,0,0]),e.fill(),e.strokeStyle=n?"rgba(255,255,255,0.1)":i.border,e.lineWidth=1,e.beginPath(),e.moveTo(a,r+u),e.lineTo(a+l,r+u),e.stroke(),e.fillStyle=n?"#ffffff":i.textPrimary,e.font='bold 14px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',e.textBaseline="middle";const b=t.table.name,T=l-100;let S=b;if(e.measureText(b).width>T){for(;e.measureText(S+"...").width>T&&S.length>0;)S=S.slice(0,-1);S+="..."}e.fillText(S,a+14,r+u/2);const I=`${h.length} fields`;e.font="11px -apple-system, BlinkMacSystemFont, sans-serif";const $=e.measureText(I).width+14;e.fillStyle=n?"rgba(255,255,255,0.25)":s?"rgba(255,255,255,0.1)":"rgba(0,0,0,0.06)",e.beginPath(),e.roundRect(a+l-$-10,r+(u-20)/2,$,20,10),e.fill(),e.fillStyle=n?"rgba(255,255,255,0.9)":i.textSecondary,e.textAlign="center",e.fillText(I,a+l-$/2-10,r+u/2),e.textAlign="left";let M=r+u+8;for(const C of p){const z=C.name.startsWith("_"),P=C.name==="_id",H=C.type.includes("Id<");o&&(e.fillStyle=s?"rgba(255, 255, 255, 0.03)":"rgba(0, 0, 0, 0.02)",e.fillRect(a+4,M-6,l-8,y)),P&&this.drawKeyIcon(e,a+12,M+2,n,i);const F=P?a+28:a+14;if(e.font='12px "SF Mono", Monaco, "Cascadia Code", monospace',z?e.fillStyle=i.textMuted:H?e.fillStyle=i.accent:e.fillStyle=i.accentInteractive,e.fillText(C.name,F,M+4),C.optional){const D=e.measureText(C.name).width;e.fillStyle=i.warning,e.font="10px -apple-system, sans-serif",e.fillText("?",F+D+2,M+3)}e.font='11px "SF Mono", Monaco, "Cascadia Code", monospace',e.fillStyle=z?i.textMuted:i.textSecondary,e.textAlign="right";let x=C.type;const N=80;if(e.measureText(x).width>N){for(;e.measureText(x+"...").width>N&&x.length>0;)x=x.slice(0,-1);x+="..."}e.fillText(x,a+l-12,M+4),e.textAlign="left",M+=y}if(g){const C=h.length-c;e.fillStyle=i.bgSecondary,e.beginPath(),e.roundRect(a+4,M,l-8,24,[0,0,d-4,d-4]),e.fill(),e.fillStyle=i.textSecondary,e.font="11px -apple-system, BlinkMacSystemFont, sans-serif",e.textAlign="center",e.fillText(`+ ${C} more field${C>1?"s":""}`,a+l/2,M+14),e.textAlign="left"}}getSortedFields(e){const t=["_id","_creationTime"];return[...e].sort((i,s)=>{const o=t.includes(i.name),n=t.includes(s.name);return o&&!n?-1:!o&&n?1:o&&n?t.indexOf(i.name)-t.indexOf(s.name):i.name.localeCompare(s.name)})}drawKeyIcon(e,t,i,s,o){e.save(),e.fillStyle=s?o.accentInteractive:o.warning,e.beginPath(),e.arc(t+4,i,4,0,Math.PI*2),e.fill(),e.fillRect(t+7,i-1,6,2),e.fillRect(t+10,i-1,1,3),e.fillRect(t+12,i-1,1,2),e.restore()}drawEdge(e,t,i,s){const o=this.hoveredNode!==null&&(this.hoveredNode.id===s.from||this.hoveredNode.id===s.to),n=this.calculateAttachmentPoints(t,i),a=Math.sqrt(Math.pow(n.toX-n.fromX,2)+Math.pow(n.toY-n.fromY,2)),r=Math.min(a*.4,100);let l,d,h,c;n.fromSide==="right"?(l=n.fromX+r,d=n.fromY):n.fromSide==="left"?(l=n.fromX-r,d=n.fromY):n.fromSide==="bottom"?(l=n.fromX,d=n.fromY+r):(l=n.fromX,d=n.fromY-r),n.toSide==="left"?(h=n.toX-r,c=n.toY):n.toSide==="right"?(h=n.toX+r,c=n.toY):n.toSide==="top"?(h=n.toX,c=n.toY-r):(h=n.toX,c=n.toY+r),o&&(e.save(),e.shadowColor="rgba(235, 86, 1, 0.3)",e.shadowBlur=8,e.shadowOffsetX=0,e.shadowOffsetY=0),e.strokeStyle=o?"#EB5601":"#8b7355",e.lineWidth=o?2.5:1.5,s.inferred?e.setLineDash([6,4]):e.setLineDash([]),e.beginPath(),e.moveTo(n.fromX,n.fromY),e.bezierCurveTo(l,d,h,c,n.toX,n.toY),e.stroke(),e.setLineDash([]),o&&e.restore(),this.drawArrowHead(e,h,c,n.toX,n.toY,o);const p=.5,g=this.bezierPoint(n.fromX,l,h,n.toX,p),u=this.bezierPoint(n.fromY,d,c,n.toY,p)-12;e.font="10px -apple-system, BlinkMacSystemFont, sans-serif";const y=s.fromField,f=e.measureText(y).width+10,w=16;e.fillStyle=o?"rgba(235, 86, 1, 0.1)":"rgba(255, 255, 255, 0.95)",e.beginPath(),e.roundRect(g-f/2,u-w/2,f,w,4),e.fill(),e.strokeStyle=o?"rgba(235, 86, 1, 0.3)":"#e6e4e1",e.lineWidth=1,e.stroke(),e.fillStyle=o?"#EB5601":"#6b6b6b",e.textAlign="center",e.textBaseline="middle",e.fillText(y,g,u),e.textAlign="left",e.textBaseline="alphabetic"}calculateAttachmentPoints(e,t){const i=e.x+e.width/2,s=e.y+e.height/2,o=t.x+t.width/2,n=t.y+t.height/2,a=o-i,r=n-s;let l,d,h,c,p,g;return Math.abs(a)>Math.abs(r)?a>0?(l="right",d="left",h=e.x+e.width,c=s,p=t.x,g=n):(l="left",d="right",h=e.x,c=s,p=t.x+t.width,g=n):r>0?(l="bottom",d="top",h=i,c=e.y+e.height,p=o,g=t.y):(l="top",d="bottom",h=i,c=e.y,p=o,g=t.y+t.height),{fromX:h,fromY:c,fromSide:l,toX:p,toY:g,toSide:d}}drawArrowHead(e,t,i,s,o,n){const a=Math.atan2(o-i,s-t),r=n?10:8;e.fillStyle=n?"#EB5601":"#8b7355",e.beginPath(),e.moveTo(s,o),e.lineTo(s-r*Math.cos(a-Math.PI/6),o-r*Math.sin(a-Math.PI/6)),e.lineTo(s-r*Math.cos(a+Math.PI/6),o-r*Math.sin(a+Math.PI/6)),e.closePath(),e.fill()}bezierPoint(e,t,i,s,o){const n=1-o;return n*n*n*e+3*n*n*o*t+3*n*o*o*i+o*o*o*s}updateCodePanel(){var i,s,o,n;const e=document.getElementById("codeBlock"),t=document.querySelector(".code-filename");if(e&&this.selectedTable){const a=(s=(i=this.config)==null?void 0:i.tables)==null?void 0:s.find(l=>l.name===this.selectedTable),r=((n=(o=this.config)==null?void 0:o.allDocuments)==null?void 0:n[this.selectedTable])||[];if(a){const l={table:this.selectedTable,documentCount:a.documentCount,fields:{}};if(a.inferredFields)for(const d of a.inferredFields)l.fields[d.name]={type:d.type,required:!d.optional};r.length>0&&(l.sample=r[0]),e.innerHTML=this.syntaxHighlight(JSON.stringify(l,null,2))}}t&&this.selectedTable&&(t.textContent=`${this.selectedTable}.json`)}savePositionState(){const e={nodes:this.graphNodes.map(t=>({id:t.id,x:t.x,y:t.y})),panX:this.panX,panY:this.panY,zoom:this.zoom};this.historyIndex<this.positionHistory.length-1&&(this.positionHistory=this.positionHistory.slice(0,this.historyIndex+1)),this.positionHistory.push(e),this.positionHistory.length>this.maxHistorySize?this.positionHistory.shift():this.historyIndex++,this.updateUndoRedoButtons()}undo(){this.historyIndex<=0||(this.historyIndex--,this.restorePositionState(this.positionHistory[this.historyIndex]),this.updateUndoRedoButtons())}redo(){this.historyIndex>=this.positionHistory.length-1||(this.historyIndex++,this.restorePositionState(this.positionHistory[this.historyIndex]),this.updateUndoRedoButtons())}restorePositionState(e){for(const t of e.nodes){const i=this.graphNodes.find(s=>s.id===t.id);i&&(i.x=t.x,i.y=t.y)}this.panX=e.panX,this.panY=e.panY,this.zoom=e.zoom,this.updateZoomDisplay(),this.drawGraph()}updateUndoRedoButtons(){const e=document.getElementById("undoBtn"),t=document.getElementById("redoBtn");e&&(e.disabled=this.historyIndex<=0),t&&(t.disabled=this.historyIndex>=this.positionHistory.length-1)}fitToView(){if(this.graphNodes.length===0)return;const e=this.graphNodes.filter(p=>p.visible!==!1);if(e.length===0)return;const t=60,i=Math.min(...e.map(p=>p.x))-t,s=Math.max(...e.map(p=>p.x+p.width))+t,o=Math.min(...e.map(p=>p.y))-t,n=Math.max(...e.map(p=>p.y+p.height))+t,a=s-i,r=n-o;if(!this.canvas)return;const l=this.canvas.width/this.dpr,d=this.canvas.height/this.dpr,h=l/a,c=d/r;this.zoom=Math.min(h,c,1.5),this.zoom=Math.max(this.zoom,.25),this.panX=(l-a*this.zoom)/2-i*this.zoom,this.panY=(d-r*this.zoom)/2-o*this.zoom,this.updateZoomDisplay(),this.drawGraph()}autoArrange(){this.calculateGraphLayout(),this.drawGraph(),this.savePositionState()}updateZoomDisplay(){const e=document.getElementById("zoomLevel"),t=document.getElementById("zoomDisplay"),i=`${Math.round(this.zoom*100)}%`;e&&(e.textContent=i),t&&(t.textContent=i)}toggleCodePanel(){this.showCodePanel=!this.showCodePanel;const e=document.getElementById("sidebarContainer"),t=document.getElementById("viewCodeBtn");e&&e.classList.toggle("hidden",!this.showCodePanel),t&&t.classList.toggle("active",this.showCodePanel),setTimeout(()=>this.resizeCanvas(),50)}toggleExportMenu(){this.showExportMenu=!this.showExportMenu,this.showFilterDropdown=!1,this.renderDropdowns()}toggleFilterDropdown(){this.showFilterDropdown=!this.showFilterDropdown,this.showExportMenu=!1,this.renderDropdowns()}renderDropdowns(){var t,i;(t=document.getElementById("exportMenu"))==null||t.remove(),(i=document.getElementById("filterMenu"))==null||i.remove();const e=document.querySelector(".graph-view");e&&(this.showExportMenu&&(e.insertAdjacentHTML("beforeend",this.renderExportMenu()),this.setupExportMenuEvents()),this.showFilterDropdown&&(e.insertAdjacentHTML("beforeend",this.renderFilterDropdown()),this.setupFilterMenuEvents()))}setupExportMenuEvents(){var e,t,i;(e=document.getElementById("exportJson"))==null||e.addEventListener("click",()=>{this.exportSchema("json"),this.showExportMenu=!1,this.renderDropdowns()}),(t=document.getElementById("exportTs"))==null||t.addEventListener("click",()=>{this.exportSchema("typescript"),this.showExportMenu=!1,this.renderDropdowns()}),(i=document.getElementById("exportPng"))==null||i.addEventListener("click",()=>{this.exportAsPng(),this.showExportMenu=!1,this.renderDropdowns()}),setTimeout(()=>{document.addEventListener("click",this.handleOutsideClick.bind(this),{once:!0})},0)}setupFilterMenuEvents(){var e,t,i;(e=document.getElementById("filterClose"))==null||e.addEventListener("click",()=>{this.showFilterDropdown=!1,this.renderDropdowns()}),(t=document.getElementById("filterApply"))==null||t.addEventListener("click",()=>{this.applyFilters()}),(i=document.getElementById("filterClear"))==null||i.addEventListener("click",()=>{this.clearFilters()})}handleOutsideClick(e){const t=document.getElementById("exportMenu"),i=document.getElementById("exportBtn"),s=document.getElementById("filterMenu"),o=document.getElementById("filterBtn");t&&!t.contains(e.target)&&!(i!=null&&i.contains(e.target))&&(this.showExportMenu=!1,this.renderDropdowns()),s&&!s.contains(e.target)&&!(o!=null&&o.contains(e.target))&&(this.showFilterDropdown=!1,this.renderDropdowns())}exportSchema(e){var n;const t=((n=this.config)==null?void 0:n.tables)||[];let i,s,o;if(e==="json"){const a={};for(const r of t)a[r.name]={documentCount:r.documentCount,fields:(r.inferredFields||[]).reduce((l,d)=>(l[d.name]={type:d.type,required:!d.optional},l),{})};i=JSON.stringify(a,null,2),s="convex-schema.json",o="application/json"}else{const a=['import { defineSchema, defineTable } from "convex/server";','import { v } from "convex/values";',"","export default defineSchema({"];for(const r of t){const l=(r.inferredFields||[]).filter(d=>!d.name.startsWith("_")).map(d=>` ${d.name}: ${this.toConvexValidator(d.type)}${d.optional?".optional()":""},`).join(`
|
|
481
519
|
`);a.push(` ${r.name}: defineTable({`),l&&a.push(l),a.push(" }),")}a.push("});"),i=a.join(`
|
|
482
|
-
`),
|
|
520
|
+
`),s="schema.ts",o="text/typescript"}this.downloadFile(i,s,o)}toConvexValidator(e){return e.startsWith("Id<")?`v.id(${e.slice(3,-1)})`:e==="string"?"v.string()":e==="number"?"v.number()":e==="boolean"?"v.boolean()":e==="null"?"v.null()":e.startsWith("array")?"v.array(v.any())":e==="object"?"v.object({})":"v.any()"}exportAsPng(){if(!this.canvas)return;const e=document.createElement("a");e.download="convex-schema-graph.png",e.href=this.canvas.toDataURL("image/png"),e.click()}downloadFile(e,t,i){const s=new Blob([e],{type:i}),o=URL.createObjectURL(s),n=document.createElement("a");n.href=o,n.download=t,n.click(),URL.revokeObjectURL(o)}applyFilters(){var o,n,a,r;const e=((o=document.getElementById("filterTableName"))==null?void 0:o.value)||"",t=((n=document.getElementById("filterFieldName"))==null?void 0:n.value)||"",i=((a=document.getElementById("filterFieldType"))==null?void 0:a.value)||"",s=((r=document.getElementById("filterShowEmpty"))==null?void 0:r.checked)??!0;this.filterState={tableName:e,fieldName:t,fieldType:i,showEmpty:s};for(const l of this.graphNodes){let d=!0;e&&!l.table.name.toLowerCase().includes(e.toLowerCase())&&(d=!1),!s&&l.table.documentCount===0&&(d=!1),d&&(t||i)&&((l.table.inferredFields||[]).some(p=>{const g=!t||p.name.toLowerCase().includes(t.toLowerCase()),u=!i||p.type.toLowerCase().includes(i.toLowerCase());return g&&u})||(d=!1)),l.visible=d}this.drawGraph(),this.showFilterDropdown=!1,this.renderDropdowns()}clearFilters(){this.filterState={tableName:"",fieldName:"",fieldType:"",showEmpty:!0};const e=document.getElementById("filterTableName"),t=document.getElementById("filterFieldName"),i=document.getElementById("filterFieldType"),s=document.getElementById("filterShowEmpty");e&&(e.value=""),t&&(t.value=""),i&&(i.value=""),s&&(s.checked=!0);for(const o of this.graphNodes)o.visible=!0;this.drawGraph()}setupEventListeners(){var e,t,i,s,o,n,a,r,l,d,h,c,p,g,u,y,f,w,L,k,B;if(document.querySelectorAll(".view-btn").forEach(v=>{v.addEventListener("click",b=>{const T=b.currentTarget.dataset.view;T&&T!==this.viewMode&&(this.viewMode=T,this.render())})}),(e=document.getElementById("refreshBtn"))==null||e.addEventListener("click",()=>this.refresh()),(t=document.getElementById("themeToggle"))==null||t.addEventListener("click",()=>this.toggleTheme()),(i=document.getElementById("shortcutsBtn"))==null||i.addEventListener("click",()=>this.showShortcutsModal()),(s=document.getElementById("shortcutsClose"))==null||s.addEventListener("click",()=>this.hideShortcutsModal()),(o=document.getElementById("shortcutsModal"))==null||o.addEventListener("click",v=>{v.target.id==="shortcutsModal"&&this.hideShortcutsModal()}),this.viewMode==="graph"){(n=document.getElementById("viewCodeBtn"))==null||n.addEventListener("click",()=>this.toggleCodePanel()),(a=document.getElementById("exportBtn"))==null||a.addEventListener("click",b=>{b.stopPropagation(),this.toggleExportMenu()}),(r=document.getElementById("autoArrangeBtn"))==null||r.addEventListener("click",()=>this.autoArrange()),(l=document.getElementById("undoBtn"))==null||l.addEventListener("click",()=>this.undo()),(d=document.getElementById("redoBtn"))==null||d.addEventListener("click",()=>this.redo()),(h=document.getElementById("filterBtn"))==null||h.addEventListener("click",b=>{b.stopPropagation(),this.toggleFilterDropdown()}),(c=document.getElementById("zoomInToolbar"))==null||c.addEventListener("click",()=>{this.zoom=Math.min(2,this.zoom*1.2),this.updateZoomDisplay(),this.drawGraph()}),(p=document.getElementById("zoomOutToolbar"))==null||p.addEventListener("click",()=>{this.zoom=Math.max(.25,this.zoom/1.2),this.updateZoomDisplay(),this.drawGraph()}),(g=document.getElementById("fitViewBtn"))==null||g.addEventListener("click",()=>this.fitToView()),(u=document.getElementById("sectionHeaderTables"))==null||u.addEventListener("click",()=>{this.toggleSidebarSection("tables")}),(y=document.getElementById("sectionHeaderConvex"))==null||y.addEventListener("click",()=>{this.toggleSidebarSection("convex")}),(f=document.getElementById("sortBtn"))==null||f.addEventListener("click",()=>{this.cycleSortOrder()});const v=document.getElementById("graphSidebarSearch");v==null||v.addEventListener("input",b=>{this.searchQuery=b.target.value.toLowerCase(),this.updateSidebarTableList()}),(w=document.getElementById("graphSidebarToggle"))==null||w.addEventListener("click",()=>{this.graphSidebarCollapsed=!this.graphSidebarCollapsed;const b=document.getElementById("enhancedSidebar"),T=document.getElementById("graphSidebarToggle");if(b&&b.classList.toggle("collapsed",this.graphSidebarCollapsed),T){T.title=this.graphSidebarCollapsed?"Expand sidebar":"Collapse sidebar";const S=T.querySelector("svg path");S&&S.setAttribute("d",this.graphSidebarCollapsed?"M5 2L10 7L5 12":"M9 2L4 7L9 12")}setTimeout(()=>this.resizeCanvas(),50)}),this.setupSidebarTableEvents()}if((L=document.getElementById("sidebarToggle"))==null||L.addEventListener("click",()=>{this.sidebarCollapsed=!this.sidebarCollapsed;const v=document.getElementById("sidebarContainer"),b=document.getElementById("sidebarToggle");v&&v.classList.toggle("collapsed",this.sidebarCollapsed),b&&(b.title=this.sidebarCollapsed?"Show panel":"Hide panel"),this.viewMode==="graph"&&setTimeout(()=>this.resizeCanvas(),50)}),this.setupSidebarResize(),this.viewMode==="list"){(k=document.getElementById("prevPage"))==null||k.addEventListener("click",()=>this.changePage(-1)),(B=document.getElementById("nextPage"))==null||B.addEventListener("click",()=>this.changePage(1));const v=document.getElementById("searchInput");v==null||v.addEventListener("input",b=>{this.searchQuery=b.target.value.toLowerCase(),this.renderTableList()})}}setupSidebarResize(){const e=document.getElementById("resizeHandle");if(!e)return;let t=0,i=0;const s=n=>{if(!this.isResizingSidebar)return;const a=n.clientX-t,r=Math.max(180,Math.min(600,i+a));if(this.viewMode==="list"){this.sidebarWidth=r;const l=document.querySelector(".sidebar");l&&(l.style.width=r+"px")}else{this.codePanelWidth=r;const l=document.getElementById("codePanel");l&&(l.style.width=r+"px")}},o=()=>{this.isResizingSidebar=!1,e.classList.remove("dragging"),document.body.style.cursor="",document.body.style.userSelect="",document.removeEventListener("mousemove",s),document.removeEventListener("mouseup",o),this.viewMode==="graph"&&this.resizeCanvas()};e.addEventListener("mousedown",n=>{n.preventDefault(),this.isResizingSidebar=!0,t=n.clientX,i=this.viewMode==="list"?this.sidebarWidth:this.codePanelWidth,e.classList.add("dragging"),document.body.style.cursor="col-resize",document.body.style.userSelect="none",document.addEventListener("mousemove",s),document.addEventListener("mouseup",o)})}resizeCanvas(){if(!this.canvas||!this.canvas.parentElement||!this.ctx)return;const e=this.canvas.parentElement.clientWidth,t=this.canvas.parentElement.clientHeight;this.dpr=window.devicePixelRatio||1,this.canvas.width=e*this.dpr,this.canvas.height=t*this.dpr,this.canvas.style.width=e+"px",this.canvas.style.height=t+"px",this.ctx.setTransform(1,0,0,1,0,0),this.ctx.scale(this.dpr,this.dpr),this.drawGraph()}setupKeyboardShortcuts(){document.addEventListener("keydown",e=>{var t,i;if(e.target instanceof HTMLInputElement||e.target instanceof HTMLTextAreaElement){e.key==="Escape"&&e.target.blur();return}switch(e.key){case"r":this.refresh();break;case"g":this.viewMode=this.viewMode==="graph"?"list":"graph",this.render();break;case"b":(t=document.getElementById("sidebarToggle"))==null||t.click();break;case"c":this.viewMode==="graph"&&this.toggleCodePanel();break;case"a":this.viewMode==="graph"&&this.autoArrange();break;case"f":this.viewMode==="graph"&&this.fitToView();break;case"z":(e.metaKey||e.ctrlKey)&&this.viewMode==="graph"&&(e.preventDefault(),e.shiftKey?this.redo():this.undo());break;case"+":case"=":this.viewMode==="graph"&&(this.zoom=Math.min(2,this.zoom*1.2),this.updateZoomDisplay(),this.drawGraph());break;case"-":this.viewMode==="graph"&&(this.zoom=Math.max(.25,this.zoom/1.2),this.updateZoomDisplay(),this.drawGraph());break;case"ArrowLeft":this.viewMode==="list"&&this.changePage(-1);break;case"ArrowRight":this.viewMode==="list"&&this.changePage(1);break;case"ArrowUp":e.preventDefault(),this.viewMode==="list"&&this.navigateTable(-1);break;case"ArrowDown":e.preventDefault(),this.viewMode==="list"&&this.navigateTable(1);break;case"/":e.preventDefault(),(i=document.getElementById("searchInput"))==null||i.focus();break}})}renderTableList(){var s;const e=document.getElementById("tableList");if(!e)return;const t=((s=this.config)==null?void 0:s.tables)||[],i=t.filter(o=>o.name.toLowerCase().includes(this.searchQuery));if(i.length===0){e.innerHTML=`
|
|
483
521
|
<li class="empty-state" style="padding: 30px 20px;">
|
|
484
522
|
<p>${t.length===0?"No tables found":"No matching tables"}</p>
|
|
485
523
|
</li>
|
|
486
524
|
`;return}e.innerHTML=i.map(o=>`
|
|
487
|
-
<li class="table-item ${o.name===this.selectedTable?"active":""}"
|
|
525
|
+
<li class="table-item ${o.name===this.selectedTable?"active":""}"
|
|
526
|
+
data-table="${o.name}"
|
|
527
|
+
title="Click to view ${o.name} schema and documents${o.hasIndexes?" (indexed)":""}">
|
|
488
528
|
<span class="table-name">
|
|
489
529
|
<svg class="table-icon" width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
490
530
|
<rect x="1" y="1" width="12" height="12" rx="2"/>
|
|
@@ -492,9 +532,9 @@ var V=Object.defineProperty;var X=(x,e,t)=>e in x?V(x,e,{enumerable:!0,configura
|
|
|
492
532
|
</svg>
|
|
493
533
|
${o.name}
|
|
494
534
|
</span>
|
|
495
|
-
<span class="table-count">${this.formatCount(o.documentCount)}</span>
|
|
535
|
+
<span class="table-count" title="${o.documentCount.toLocaleString()} documents">${this.formatCount(o.documentCount)}</span>
|
|
496
536
|
</li>
|
|
497
|
-
`).join(""),e.querySelectorAll(".table-item").forEach(o=>{o.addEventListener("click",()=>{const
|
|
537
|
+
`).join(""),e.querySelectorAll(".table-item").forEach(o=>{o.addEventListener("click",()=>{const n=o.dataset.table;n&&this.selectTable(n)})})}formatCount(e){return e?e>=1e6?(e/1e6).toFixed(1)+"M":e>=1e3?(e/1e3).toFixed(1)+"k":e.toString():"0"}navigateTable(e){var o;const t=((o=this.config)==null?void 0:o.tables)||[];if(t.length===0)return;let s=t.findIndex(n=>n.name===this.selectedTable)+e;s<0&&(s=t.length-1),s>=t.length&&(s=0),this.selectTable(t[s].name)}selectTable(e){if(this.selectedTable=e,this.currentPage=1,this.viewMode==="list"){this.renderTableList();const t=document.getElementById("listViewContent");t&&(t.innerHTML=this.renderListViewContent(),this.setupListViewEventListeners())}else this.drawGraph(),this.updateCodePanel()}setupListViewEventListeners(){const e=document.getElementById("prevPage"),t=document.getElementById("nextPage");e&&e.addEventListener("click",()=>this.changePage(-1)),t&&t.addEventListener("click",()=>this.changePage(1))}renderSchema(e,t){const i=document.getElementById("schemaPanel");if(!i)return;const s=(t==null?void 0:t.inferredFields)||[],o=(t==null?void 0:t.documentCount)||0;i.innerHTML=`
|
|
498
538
|
<div class="schema-title">${e}</div>
|
|
499
539
|
<div class="schema-subtitle">
|
|
500
540
|
${this.formatCount(o)} documents
|
|
@@ -505,23 +545,23 @@ var V=Object.defineProperty;var X=(x,e,t)=>e in x?V(x,e,{enumerable:!0,configura
|
|
|
505
545
|
<div class="schema-card">
|
|
506
546
|
<div class="schema-card-header">
|
|
507
547
|
<span>Inferred Schema</span>
|
|
508
|
-
<span class="count">${
|
|
548
|
+
<span class="count">${s.length} fields</span>
|
|
509
549
|
</div>
|
|
510
550
|
<div class="schema-card-body">
|
|
511
|
-
${this.renderFields(
|
|
551
|
+
${this.renderFields(s)}
|
|
512
552
|
</div>
|
|
513
553
|
</div>
|
|
514
554
|
</div>
|
|
515
|
-
`}renderFields(e){if(!e||e.length===0)return'<p style="color: var(--text-secondary); padding: 12px;">No schema data available</p>';const t=["_id","_creationTime"];return[...e].sort((
|
|
516
|
-
<div class="field-row ${t.includes(
|
|
555
|
+
`}renderFields(e){if(!e||e.length===0)return'<p style="color: var(--text-secondary); padding: 12px;">No schema data available</p>';const t=["_id","_creationTime"];return[...e].sort((s,o)=>{const n=t.includes(s.name),a=t.includes(o.name);return n&&!a?-1:!n&&a?1:s.name.localeCompare(o.name)}).map(s=>`
|
|
556
|
+
<div class="field-row ${t.includes(s.name)?"field-system":""}">
|
|
517
557
|
<span>
|
|
518
|
-
<span class="field-name">${
|
|
519
|
-
${
|
|
558
|
+
<span class="field-name">${s.name}</span>
|
|
559
|
+
${s.optional?'<span class="field-optional">?</span>':""}
|
|
520
560
|
</span>
|
|
521
|
-
<span class="field-type">${
|
|
561
|
+
<span class="field-type">${s.type}</span>
|
|
522
562
|
</div>
|
|
523
|
-
`).join("")}renderDocuments(e){const t=document.getElementById("docTableHead"),i=document.getElementById("docTableBody");if(!t||!i)return;const
|
|
563
|
+
`).join("")}renderDocuments(e){const t=document.getElementById("docTableHead"),i=document.getElementById("docTableBody");if(!t||!i)return;const s=(this.currentPage-1)*this.pageSize,o=s+this.pageSize,n=e.slice(s,o),a=Math.ceil(e.length/this.pageSize);if(n.length===0){t.innerHTML="",i.innerHTML='<tr><td colspan="100" class="empty-state">No documents found</td></tr>';return}const r=[...new Set(n.flatMap(d=>Object.keys(d)))];r.sort((d,h)=>{const c=d.startsWith("_"),p=h.startsWith("_");return c&&!p?-1:!c&&p?1:d.localeCompare(h)}),t.innerHTML=`<tr>${r.map(d=>`<th>${d}</th>`).join("")}</tr>`,i.innerHTML=n.map(d=>`
|
|
524
564
|
<tr>
|
|
525
|
-
${r.map(h=>{const c=d[h],p=h==="_id",
|
|
565
|
+
${r.map(h=>{const c=d[h],p=h==="_id",g=c==null;return`<td class="${[p?"id-cell":"",g?"null-value":""].filter(Boolean).join(" ")}">${this.formatValue(c)}</td>`}).join("")}
|
|
526
566
|
</tr>
|
|
527
|
-
`).join("");const l=document.getElementById("pageInfo");l&&(l.textContent=`Page ${this.currentPage} of ${a||1}`),document.getElementById("prevPage").disabled=this.currentPage<=1,document.getElementById("nextPage").disabled=this.currentPage>=a}formatValue(e){if(e==null)return'<span class="null-value">null</span>';if(typeof e=="object"){const t=JSON.stringify(e);return t.length>50?t.slice(0,50)+"...":t}return typeof e=="string"&&e.length>50?e.slice(0,50)+"...":String(e)}changePage(e){var r,l,d,h;const t=(l=(r=this.config)==null?void 0:r.tables)==null?void 0:l.find(c=>c.name===this.selectedTable),i=((h=(d=this.config)==null?void 0:d.allDocuments)==null?void 0:h[this.selectedTable||""])||(t==null?void 0:t.documents)||[],
|
|
567
|
+
`).join("");const l=document.getElementById("pageInfo");l&&(l.textContent=`Page ${this.currentPage} of ${a||1}`),document.getElementById("prevPage").disabled=this.currentPage<=1,document.getElementById("nextPage").disabled=this.currentPage>=a}formatValue(e){if(e==null)return'<span class="null-value">null</span>';if(typeof e=="object"){const t=JSON.stringify(e);return t.length>50?t.slice(0,50)+"...":t}return typeof e=="string"&&e.length>50?e.slice(0,50)+"...":String(e)}changePage(e){var r,l,d,h;const t=(l=(r=this.config)==null?void 0:r.tables)==null?void 0:l.find(c=>c.name===this.selectedTable),i=((h=(d=this.config)==null?void 0:d.allDocuments)==null?void 0:h[this.selectedTable||""])||(t==null?void 0:t.documents)||[],s=Math.ceil(i.length/this.pageSize),o=this.currentPage+e;if(o<1||o>s)return;this.currentPage=o;const n=document.getElementById("documentsTableWrapper");n&&(n.innerHTML=this.renderDocumentsTable(i));const a=document.querySelector(".pagination-bar");a&&(a.outerHTML=this.renderPaginationBar(i),this.setupListViewEventListeners())}refresh(){if(this.viewMode==="graph")this.calculateGraphLayout(),this.drawGraph();else if(this.selectedTable){const e=document.getElementById("listViewContent");e&&(e.innerHTML=this.renderListViewContent(),this.setupListViewEventListeners())}}}new Y;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
html,body{width:100%;height:100%;overflow:hidden}:root{--bg-primary: #faf8f5;--bg-secondary: #f5f3f0;--bg-tertiary: #ebe9e6;--bg-hover: #ebe9e6;--bg-canvas: #faf8f5;--text-primary: #1a1a1a;--text-secondary: #6b6b6b;--text-muted: #999999;--border: #e6e4e1;--border-strong: #d4d2cf;--accent: #8b7355;--accent-interactive: #eb5601;--accent-hover: #d14a01;--success: #4a8c5c;--warning: #c4842d;--warning-bg: #fef3e2;--warning-text: #8a5a00;--error: #dc3545;--info: #4a7c9b;--font-mono: "SF Mono", Monaco, "Cascadia Code", "Courier New", monospace;--code-bg: #1e1e1e;--code-text: #d4d4d4;--code-key: #9cdcfe;--code-string: #ce9178;--code-number: #b5cea8;--code-boolean: #569cd6;--shadow-sm: 0 1px 2px rgba(0, 0, 0, .05);--shadow-md: 0 4px 12px rgba(0, 0, 0, .08);--shadow-lg: 0 8px 24px rgba(0, 0, 0, .12);--node-bg: #ffffff;--node-header: #f8f7f5;--node-header-selected: #eb5601;--node-border: #e6e4e1;--grid-line: #e6e4e1}[data-theme=dark]{--bg-primary: #1e1e1e;--bg-secondary: #252526;--bg-tertiary: #2d2d2d;--bg-hover: #37373d;--bg-canvas: #1e1e1e;--text-primary: #cccccc;--text-secondary: #8b8b8b;--text-muted: #6b6b6b;--border: #3c3c3c;--border-strong: #4a4a4a;--accent: #c9a87c;--accent-interactive: #ff6b35;--accent-hover: #ff8555;--success: #4ec9b0;--warning: #dcdcaa;--warning-bg: #3d3520;--warning-text: #dcdcaa;--error: #f14c4c;--info: #4fc1ff;--code-bg: #1e1e1e;--code-text: #d4d4d4;--code-key: #9cdcfe;--code-string: #ce9178;--code-number: #b5cea8;--code-boolean: #569cd6;--shadow-sm: 0 1px 2px rgba(0, 0, 0, .3);--shadow-md: 0 4px 12px rgba(0, 0, 0, .4);--shadow-lg: 0 8px 24px rgba(0, 0, 0, .5);--node-bg: #2d2d2d;--node-header: #37373d;--node-header-selected: #ff6b35;--node-border: #4a4a4a;--grid-line: #2d2d2d}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,sans-serif;background:var(--bg-primary);color:var(--text-primary);min-height:100vh;display:flex;flex-direction:column;margin:0;padding:0}.header{background:var(--bg-secondary);padding:10px 16px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center}.header h1{font-size:18px;font-weight:600;display:flex;align-items:center;gap:8px;color:var(--text-primary)}.header-info{flex:1;margin:0 16px}.deployment-url{font-size:12px;color:var(--text-secondary);font-family:var(--font-mono)}.header-actions{display:flex;gap:8px;align-items:center}.theme-toggle{width:36px;height:36px;background:var(--bg-hover);border:1px solid var(--border);border-radius:8px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--text-secondary);transition:all .2s ease}.theme-toggle:hover{background:var(--accent-interactive);border-color:var(--accent-interactive);color:#fff}.theme-toggle svg{width:18px;height:18px}.theme-toggle .sun-icon{display:none}.theme-toggle .moon-icon,[data-theme=dark] .theme-toggle .sun-icon{display:block}[data-theme=dark] .theme-toggle .moon-icon{display:none}.btn{background:var(--bg-primary);color:var(--text-primary);border:1px solid var(--border);padding:6px 12px;border-radius:6px;cursor:pointer;font-size:13px;transition:all .2s}.btn:hover{background:var(--bg-hover);border-color:var(--accent)}.btn-primary{background:var(--accent-interactive);border-color:var(--accent-interactive);color:#fff}.btn-primary:hover{background:var(--accent-hover);border-color:var(--accent-hover)}.main{display:flex;flex:1;overflow:hidden}.sidebar{width:260px;background:var(--bg-secondary);border-right:1px solid var(--border);overflow-y:auto;flex-shrink:0;display:flex;flex-direction:column;margin:0;padding:0}.sidebar-header{padding:12px 16px;font-size:11px;text-transform:uppercase;color:var(--text-secondary);border-bottom:1px solid var(--border);display:flex;align-items:center;font-weight:600;letter-spacing:.5px;gap:8px;position:relative}.sidebar-header #tableCount{background:var(--bg-hover);padding:2px 8px;border-radius:10px;font-size:11px}.sidebar-header .sidebar-collapse-btn{margin-left:auto;width:24px;height:24px;background:var(--bg-hover);border:1px solid transparent;border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--text-secondary);transition:all .15s;flex-shrink:0}.sidebar-header .sidebar-collapse-btn:hover{background:var(--accent-interactive);color:#fff}.sidebar-header .sidebar-collapse-btn svg{transition:transform .2s}.sidebar-search{padding:8px 12px;border-bottom:1px solid var(--border)}.sidebar-search input{width:100%;background:var(--bg-primary);border:1px solid var(--border);border-radius:6px;padding:8px 10px;color:var(--text-primary);font-size:13px}.sidebar-search input:focus{outline:none;border-color:var(--accent-interactive);box-shadow:0 0 0 2px #eb56011a}.table-list{list-style:none;flex:1;overflow-y:auto}.table-item{padding:10px 16px;cursor:pointer;display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--border);transition:background .2s}.table-item:hover{background:var(--bg-hover)}.table-item.active{background:var(--accent-interactive);color:#fff}.table-item.active .table-count{background:#fff3;color:#fff}.table-item.active .table-icon{opacity:1}.table-name{font-size:14px;font-weight:500;display:flex;align-items:center;gap:6px}.table-icon{opacity:.5}.table-count{font-size:12px;color:var(--text-secondary);background:var(--bg-hover);padding:2px 8px;border-radius:10px}.content{flex:1;display:flex;flex-direction:column;overflow:hidden;background:var(--bg-primary)}.schema-panel{padding:20px;flex:1;overflow-y:auto}.schema-title{font-size:22px;margin-bottom:8px;display:flex;align-items:center;gap:12px;color:var(--text-primary)}.schema-subtitle{color:var(--text-secondary);font-size:13px;margin-bottom:20px}.schema-grid{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-bottom:20px}@media(max-width:900px){.schema-grid{grid-template-columns:1fr}}.schema-card{background:var(--bg-secondary);border:1px solid var(--border);border-radius:8px;overflow:hidden}.schema-card-header{background:var(--bg-hover);padding:10px 16px;font-weight:600;font-size:14px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center;color:var(--text-primary)}.schema-card-header .count{color:var(--text-secondary);font-weight:400;font-size:12px}.schema-card-body{padding:8px;max-height:400px;overflow-y:auto}.field-row{display:flex;justify-content:space-between;padding:8px 10px;border-radius:4px;font-size:13px;font-family:var(--font-mono)}.field-row:hover{background:var(--bg-hover)}.field-name{color:var(--accent-interactive)}.field-type{color:var(--text-secondary)}.field-optional{color:var(--warning);font-size:11px;margin-left:2px}.field-system{opacity:.6}.indexes-section{margin-top:20px}.indexes-title{font-size:14px;font-weight:600;margin-bottom:12px;color:var(--text-secondary)}.index-item{background:var(--bg-secondary);border:1px solid var(--border);border-radius:6px;padding:12px 16px;margin-bottom:8px}.index-name{font-family:var(--font-mono);font-size:13px;color:var(--accent-interactive);margin-bottom:4px}.index-fields{font-size:12px;color:var(--text-secondary)}.documents-panel{background:var(--bg-secondary);border-top:1px solid var(--border);max-height:320px;overflow:hidden;display:flex;flex-direction:column}.documents-header{padding:10px 16px;display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--border);background:var(--bg-hover);flex-shrink:0}.documents-title{font-weight:600;font-size:14px;color:var(--text-primary)}.pagination{display:flex;align-items:center;gap:8px}.pagination button{background:var(--bg-primary);color:var(--text-primary);border:1px solid var(--border);width:28px;height:28px;border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:12px}.pagination button:hover:not(:disabled){background:var(--accent-interactive);border-color:var(--accent-interactive);color:#fff}.pagination button:disabled{opacity:.5;cursor:not-allowed}.pagination span{font-size:13px;color:var(--text-secondary)}.documents-table{overflow:auto;flex:1}table{width:100%;border-collapse:collapse;font-size:13px}th,td{padding:10px 14px;text-align:left;border-bottom:1px solid var(--border);white-space:nowrap}th{background:var(--bg-hover);font-weight:600;position:sticky;top:0;z-index:1;color:var(--text-primary)}td{font-family:var(--font-mono);font-size:12px}tr:hover td{background:var(--bg-hover)}td.id-cell{color:var(--accent-interactive);cursor:pointer}td.id-cell:hover{text-decoration:underline}td.null-value{color:var(--text-secondary);font-style:italic}td.truncated{max-width:200px;overflow:hidden;text-overflow:ellipsis}.empty-state{text-align:center;padding:60px 20px;color:var(--text-secondary)}.empty-state h2{margin-bottom:8px;color:var(--text-primary);font-size:18px}.empty-state p{font-size:14px}.warning-badge{background:#c4842d1a;border:1px solid var(--warning);color:var(--warning);padding:10px 14px;border-radius:6px;font-size:13px;margin-top:16px;display:flex;align-items:center;gap:8px}.warning-badge:before{content:"⚠"}.loading{display:flex;align-items:center;justify-content:center;padding:40px;gap:12px;color:var(--text-secondary)}.spinner{width:24px;height:24px;border:2px solid var(--border);border-top-color:var(--accent-interactive);border-radius:50%;animation:spin .8s linear infinite}.status-dot{width:8px;height:8px;border-radius:50%;background:var(--success)}.status-dot.error{background:var(--accent-interactive)}.status-dot.warning{background:var(--warning)}.modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:#1a1a1a80;display:flex;align-items:center;justify-content:center;z-index:100}.modal{background:var(--bg-primary);border:1px solid var(--border);border-radius:12px;width:90%;max-width:600px;max-height:80vh;overflow:hidden;display:flex;flex-direction:column;box-shadow:0 20px 40px #00000026}.modal-header{padding:16px 20px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center;background:var(--bg-secondary)}.modal-header h2{font-size:16px;color:var(--text-primary)}.modal-close{background:none;border:none;color:var(--text-secondary);font-size:20px;cursor:pointer;padding:4px}.modal-close:hover{color:var(--text-primary)}.modal-body{padding:20px;flex:1;overflow-y:auto}.query-editor{width:100%;min-height:150px;background:var(--bg-secondary);border:1px solid var(--border);border-radius:6px;padding:12px;color:var(--text-primary);font-family:var(--font-mono);font-size:13px;resize:vertical}.query-editor:focus{outline:none;border-color:var(--accent-interactive);box-shadow:0 0 0 2px #eb56011a}.modal-footer{padding:16px 20px;border-top:1px solid var(--border);display:flex;justify-content:flex-end;gap:8px;background:var(--bg-secondary)}kbd{background:var(--bg-secondary);border:1px solid var(--border);border-radius:3px;padding:2px 6px;font-size:11px;font-family:var(--font-mono)}.app-container{display:flex;flex-direction:column;height:100vh;width:100vw;overflow:hidden;margin:0;padding:0;position:absolute;top:0;left:0}.view-toggle{display:flex;gap:4px;background:var(--bg-hover);padding:4px;border-radius:8px;margin-right:12px}.view-btn{display:flex;align-items:center;justify-content:center;width:32px;height:28px;border:none;background:transparent;color:var(--text-secondary);border-radius:6px;cursor:pointer;transition:all .2s}.view-btn:hover{color:var(--text-primary);background:var(--bg-secondary)}.view-btn.active{background:var(--accent-interactive);color:#fff}.graph-view{display:flex;flex-direction:column;flex:1;overflow:hidden}.graph-view .toolbar{order:-1}.code-panel{width:360px;background:#1e1e1e;border-right:1px solid #333;display:flex;flex-direction:column;flex-shrink:0}.code-header{padding:12px 16px;background:#252526;border-bottom:1px solid #333;display:flex;justify-content:space-between;align-items:center;color:#ccc;font-size:12px}.code-filename{color:#888;font-family:var(--font-mono)}.code-content{flex:1;overflow:auto;padding:0}.code-content pre{margin:0;padding:16px;font-family:var(--font-mono);font-size:12px;line-height:1.6;color:#d4d4d4}.code-content code{font-family:inherit}.json-key{color:#9cdcfe}.json-string{color:#ce9178}.json-number{color:#b5cea8}.json-boolean,.json-null{color:#569cd6}.graph-panel{flex:1;position:relative;background:var(--bg-primary);overflow:hidden;min-width:0}#graphCanvas{width:100%;height:100%;display:block}.graph-controls{position:absolute;top:16px;right:16px;display:flex;flex-direction:column;gap:8px}.graph-btn{width:36px;height:36px;border:1px solid var(--border);background:#fff;color:var(--text-primary);border-radius:8px;cursor:pointer;font-size:18px;display:flex;align-items:center;justify-content:center;transition:all .2s;box-shadow:0 2px 8px #00000014}.graph-btn:hover{background:var(--bg-hover);border-color:var(--accent)}.graph-legend{position:absolute;bottom:16px;right:16px;background:#fff;border:1px solid var(--border);border-radius:8px;padding:12px 16px;display:flex;flex-direction:column;gap:8px;font-size:12px;box-shadow:0 2px 8px #00000014}.legend-item{display:flex;align-items:center;gap:8px;color:var(--text-secondary)}.legend-dot{width:12px;height:12px;border-radius:4px;background:var(--accent-interactive)}.legend-dot.table{background:#fff;border:2px solid var(--border)}.legend-line{width:20px;height:2px;background:var(--accent)}.list-view{display:flex;flex:1;overflow:hidden}.list-view .content{display:flex;flex-direction:column;flex:1;overflow:hidden}.list-view .table-header{padding:16px 24px;border-bottom:1px solid var(--border);background:var(--bg-primary);flex-shrink:0}.list-view .table-header-title{font-size:24px;font-weight:600;color:var(--text-primary);margin-bottom:4px}.list-view .table-header-meta{font-size:13px;color:var(--text-secondary);display:flex;align-items:center;gap:12px}.list-view .table-header-meta .badge{background:var(--bg-hover);padding:2px 8px;border-radius:4px;font-size:12px}.list-view .content-split{display:flex;flex:1;overflow:hidden}.list-view .schema-sidebar{width:280px;min-width:240px;max-width:360px;background:var(--bg-secondary);border-right:1px solid var(--border);display:flex;flex-direction:column;overflow:hidden;flex-shrink:0}.list-view .schema-sidebar-header{padding:12px 16px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--text-secondary);border-bottom:1px solid var(--border);background:var(--bg-hover);display:flex;justify-content:space-between;align-items:center}.list-view .schema-sidebar-header .field-count-badge{font-size:11px;color:var(--text-secondary);background:var(--bg-primary);padding:2px 8px;border-radius:10px}.list-view .schema-fields-list{flex:1;overflow-y:auto;padding:8px 0}.list-view .schema-field-item{display:flex;justify-content:space-between;align-items:center;padding:10px 16px;border-bottom:1px solid var(--border);transition:background .15s}.list-view .schema-field-item:hover{background:var(--bg-hover)}.list-view .schema-field-item:last-child{border-bottom:none}.list-view .schema-field-name{font-family:var(--font-mono);font-size:13px;color:var(--accent-interactive);display:flex;align-items:center;gap:4px}.list-view .schema-field-name.system-field{color:var(--text-secondary)}.list-view .schema-field-optional{font-size:10px;color:var(--warning);font-weight:600}.list-view .schema-field-type{font-family:var(--font-mono);font-size:12px;color:var(--text-secondary);background:var(--bg-primary);padding:2px 8px;border-radius:4px}.list-view .schema-indexes{border-top:1px solid var(--border);padding:12px 16px;background:var(--bg-hover)}.list-view .schema-indexes-title{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--text-secondary);margin-bottom:8px}.list-view .schema-index-item{font-family:var(--font-mono);font-size:12px;color:var(--accent);padding:4px 0}.list-view .documents-main{flex:1;display:flex;flex-direction:column;overflow:hidden;background:var(--bg-primary);min-width:0}.list-view .documents-toolbar{padding:12px 20px;display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--border);background:var(--bg-secondary);flex-shrink:0}.list-view .documents-toolbar-title{font-size:13px;font-weight:600;color:var(--text-primary);display:flex;align-items:center;gap:8px}.list-view .documents-toolbar-title .doc-count{font-weight:400;color:var(--text-secondary)}.list-view .documents-table-wrapper{flex:1;overflow:auto;padding:0}.list-view .documents-table-wrapper table{width:100%;border-collapse:collapse;font-size:13px}.list-view .documents-table-wrapper th{position:sticky;top:0;background:var(--bg-hover);font-weight:600;text-align:left;padding:12px 16px;border-bottom:2px solid var(--border);white-space:nowrap;z-index:1}.list-view .documents-table-wrapper td{padding:10px 16px;border-bottom:1px solid var(--border);font-family:var(--font-mono);font-size:12px;vertical-align:top;max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.list-view .documents-table-wrapper tr:hover td{background:var(--bg-secondary)}.list-view .documents-table-wrapper td.id-cell{color:var(--accent-interactive);cursor:pointer}.list-view .documents-table-wrapper td.id-cell:hover{text-decoration:underline}.list-view .pagination-bar{padding:12px 20px;display:flex;justify-content:space-between;align-items:center;border-top:1px solid var(--border);background:var(--bg-secondary);flex-shrink:0}.list-view .pagination-info{font-size:13px;color:var(--text-secondary)}.list-view .pagination-controls{display:flex;align-items:center;gap:8px}.list-view .pagination-controls button{width:32px;height:32px;display:flex;align-items:center;justify-content:center;background:var(--bg-primary);border:1px solid var(--border);border-radius:6px;cursor:pointer;color:var(--text-primary);font-size:14px;transition:all .15s}.list-view .pagination-controls button:hover:not(:disabled){background:var(--accent-interactive);border-color:var(--accent-interactive);color:#fff}.list-view .pagination-controls button:disabled{opacity:.4;cursor:not-allowed}.list-view .pagination-controls .page-indicator{font-size:13px;color:var(--text-secondary);min-width:80px;text-align:center}.list-view .documents-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;color:var(--text-secondary);text-align:center}.list-view .documents-empty-icon{font-size:48px;margin-bottom:16px;opacity:.5}.list-view .documents-empty h3{font-size:16px;color:var(--text-primary);margin-bottom:4px}.list-view .documents-empty p{font-size:13px}.list-view .schema-panel,.list-view .documents-panel{display:none!important}.sidebar-container{display:flex;flex-shrink:0;position:relative}.sidebar-container.collapsed .sidebar{width:48px!important;min-width:48px!important;overflow:hidden}.sidebar-container.collapsed .sidebar .sidebar-header{padding:12px;justify-content:center}.sidebar-container.collapsed .sidebar .sidebar-header>span{display:none}.sidebar-container.collapsed .sidebar .sidebar-header .sidebar-collapse-btn{margin-left:0}.sidebar-container.collapsed .sidebar .sidebar-search,.sidebar-container.collapsed .sidebar .table-list{display:none}.sidebar-container.collapsed .code-panel{width:0!important;min-width:0!important;padding:0;overflow:hidden;border:none}.sidebar-container.collapsed .resize-handle{display:none}.resize-handle{width:4px;cursor:col-resize;background:transparent;transition:background .2s;position:relative;z-index:10}.resize-handle:hover,.resize-handle.dragging{background:var(--accent-interactive)}.resize-handle:after{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:4px;height:40px;border-radius:2px;background:var(--border);opacity:0;transition:opacity .2s}.resize-handle:hover:after{opacity:1;background:#fff}.sidebar-toggle{display:none}.list-view .sidebar-header{position:relative}.list-view .sidebar-header .sidebar-collapse-btn{position:absolute;right:8px;top:50%;transform:translateY(-50%);width:24px;height:24px;background:var(--bg-hover);border:1px solid var(--border);border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--text-secondary);transition:all .2s}.list-view .sidebar-header .sidebar-collapse-btn:hover{background:var(--accent-interactive);color:#fff;border-color:var(--accent-interactive)}.list-view .sidebar-header .sidebar-collapse-btn svg{width:12px;height:12px;transition:transform .2s}.sidebar-container.collapsed .sidebar-collapse-btn svg{transform:rotate(180deg)}.list-view .sidebar{min-width:180px;max-width:400px}.list-view .sidebar-container{height:100%}.toolbar{display:flex;align-items:center;padding:8px 16px;background:var(--bg-secondary);border-bottom:1px solid var(--border);gap:4px;flex-shrink:0;width:100%;z-index:10}.toolbar-group{display:flex;align-items:center;gap:2px}.toolbar-separator{width:1px;height:24px;background:var(--border);margin:0 8px}.toolbar-spacer{flex:1}.toolbar-btn{display:flex;align-items:center;gap:6px;padding:6px 10px;background:transparent;border:1px solid transparent;border-radius:6px;color:var(--text-secondary);font-size:13px;cursor:pointer;transition:all .15s ease}.toolbar-btn:hover:not(:disabled){background:var(--bg-hover);color:var(--text-primary);border-color:var(--border)}.toolbar-btn.active{background:var(--accent-interactive);color:#fff;border-color:var(--accent-interactive)}.toolbar-btn.active:hover{background:var(--accent-hover);border-color:var(--accent-hover)}.toolbar-btn:disabled{opacity:.4;cursor:not-allowed}.toolbar-btn.icon-only{padding:6px}.toolbar-btn svg{flex-shrink:0}.toolbar-btn .dropdown-arrow{margin-left:2px;opacity:.6}.zoom-group{display:flex;align-items:center;gap:4px;background:var(--bg-primary);border:1px solid var(--border);border-radius:6px;padding:2px}.zoom-group .toolbar-btn{border-radius:4px}.zoom-display{min-width:48px;text-align:center;font-size:12px;font-weight:500;color:var(--text-secondary);font-family:var(--font-mono)}.graph-content{display:flex;flex:1;overflow:hidden;flex-direction:row}.graph-content .enhanced-sidebar{order:0}.graph-content .graph-panel{order:1;flex:1}.graph-content .sidebar-container{order:2}.sidebar-container.hidden .code-panel{width:0!important;min-width:0;padding:0;overflow:hidden;border:none}.sidebar-container.hidden .resize-handle{display:none}.sidebar-container.hidden .sidebar-toggle{left:0;border-radius:0 6px 6px 0}.zoom-panel{position:absolute;bottom:20px;left:20px;display:flex;flex-direction:column;gap:4px;background:#fff;border:1px solid var(--border);border-radius:10px;padding:8px;box-shadow:0 4px 12px #0000001a;z-index:10}.zoom-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;background:transparent;border:none;border-radius:6px;color:var(--text-secondary);cursor:pointer;transition:all .15s ease}.zoom-btn:hover{background:var(--bg-hover);color:var(--text-primary)}.zoom-btn:active{background:var(--accent-interactive);color:#fff}.zoom-level{text-align:center;font-size:11px;font-weight:500;color:var(--text-secondary);font-family:var(--font-mono);padding:4px 0}.zoom-separator{height:1px;background:var(--border);margin:4px 0}.dropdown-menu{position:absolute;background:#fff;border:1px solid var(--border);border-radius:10px;box-shadow:0 8px 24px #0000001f;z-index:100;overflow:hidden;min-width:180px}.export-menu{top:52px;left:140px}.dropdown-item{display:flex;align-items:center;gap:10px;width:100%;padding:10px 14px;background:transparent;border:none;color:var(--text-primary);font-size:13px;cursor:pointer;transition:background .15s ease;text-align:left}.dropdown-item:hover{background:var(--bg-hover)}.dropdown-item svg{color:var(--text-secondary)}.filter-menu{position:absolute;top:52px;right:200px;width:280px}.filter-header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid var(--border);font-weight:600;font-size:14px}.filter-close{background:none;border:none;font-size:18px;color:var(--text-secondary);cursor:pointer;padding:0;line-height:1}.filter-close:hover{color:var(--text-primary)}.filter-body{padding:16px}.filter-group{margin-bottom:14px}.filter-group:last-child{margin-bottom:0}.filter-group label{display:block;font-size:12px;font-weight:500;color:var(--text-secondary);margin-bottom:6px}.filter-group input[type=text],.filter-group select{width:100%;padding:8px 10px;background:var(--bg-secondary);border:1px solid var(--border);border-radius:6px;font-size:13px;color:var(--text-primary)}.filter-group input[type=text]:focus,.filter-group select:focus{outline:none;border-color:var(--accent-interactive);box-shadow:0 0 0 2px #eb56011a}.filter-group.checkbox label{display:flex;align-items:center;gap:8px;cursor:pointer}.filter-group.checkbox input[type=checkbox]{width:16px;height:16px;accent-color:var(--accent-interactive)}.filter-footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid var(--border);background:var(--bg-secondary)}.filter-footer .btn{padding:6px 14px;font-size:13px}.btn-secondary{background:var(--bg-primary);border:1px solid var(--border);color:var(--text-primary)}.btn-secondary:hover{background:var(--bg-hover)}.enhanced-sidebar{width:260px;background:var(--bg-secondary);border-right:1px solid var(--border);display:flex;flex-direction:column;flex-shrink:0;overflow:hidden;height:100%;margin:0;padding:0;position:relative;transition:width .2s ease,min-width .2s ease}.graph-sidebar-toggle{position:absolute;top:50%;right:-14px;transform:translateY(-50%);width:28px;height:48px;background:var(--bg-secondary);border:1px solid var(--border);border-left:none;border-radius:0 8px 8px 0;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--text-secondary);z-index:20;transition:all .15s ease;box-shadow:2px 0 8px #0000000d}.graph-sidebar-toggle:hover{background:var(--accent-interactive);color:#fff;border-color:var(--accent-interactive)}.graph-sidebar-toggle svg{transition:transform .2s ease}.enhanced-sidebar.collapsed{width:0!important;min-width:0!important;border-right:none;overflow:visible}.enhanced-sidebar.collapsed .sidebar-deployment,.enhanced-sidebar.collapsed .sidebar-section{display:none}.enhanced-sidebar.collapsed .graph-sidebar-toggle{right:-28px;border-left:1px solid var(--border);border-radius:0 8px 8px 0}.sidebar-deployment{padding:14px 16px;border-bottom:1px solid var(--border);background:linear-gradient(to bottom,var(--bg-hover),var(--bg-secondary))}.deployment-status{display:flex;align-items:center;gap:8px;margin-bottom:4px}.status-indicator{width:8px;height:8px;border-radius:50%;background:var(--success)}.status-indicator.error{background:var(--accent-interactive)}.deployment-label{font-size:13px;font-weight:600;color:var(--text-primary)}.deployment-url-small{font-size:11px;font-family:var(--font-mono);color:var(--text-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sidebar-section{border-bottom:1px solid var(--border)}.section-header{display:flex;align-items:center;gap:8px;padding:12px 14px;cursor:pointer;-webkit-user-select:none;user-select:none;transition:background .15s ease}.section-header:hover{background:var(--bg-hover)}.section-chevron{transition:transform .2s ease;color:var(--text-secondary)}.sidebar-section:not(.collapsed) .section-chevron{transform:rotate(90deg)}.section-title{font-size:11px;font-weight:600;letter-spacing:.5px;color:var(--text-secondary);text-transform:uppercase}.section-count{margin-left:auto;font-size:11px;color:var(--text-secondary);background:var(--bg-hover);padding:2px 8px;border-radius:10px}.section-content{max-height:500px;overflow:hidden;transition:max-height .3s ease,opacity .2s ease}.sidebar-section.collapsed .section-content{max-height:0;opacity:0}.sidebar-toolbar{display:flex;gap:6px;padding:8px 12px;border-bottom:1px solid var(--border);background:var(--bg-primary)}.sidebar-filter{flex:1;padding:6px 10px;background:var(--bg-secondary);border:1px solid var(--border);border-radius:6px;font-size:12px;color:var(--text-primary)}.sidebar-filter:focus{outline:none;border-color:var(--accent-interactive);box-shadow:0 0 0 2px #eb56011a}.sort-btn{width:28px;height:28px;display:flex;align-items:center;justify-content:center;background:var(--bg-secondary);border:1px solid var(--border);border-radius:6px;color:var(--text-secondary);cursor:pointer;transition:all .15s ease}.sort-btn:hover{background:var(--bg-hover);border-color:var(--accent);color:var(--text-primary)}.sidebar-table-list{list-style:none;max-height:400px;overflow-y:auto;padding:4px 0}.sidebar-table-item{display:flex;align-items:center;justify-content:space-between;padding:8px 14px;cursor:pointer;transition:background .15s ease}.sidebar-table-item:hover{background:var(--bg-hover)}.sidebar-table-item.active{background:var(--accent-interactive)}.sidebar-table-item.active .table-name,.sidebar-table-item.active .table-icon{color:#fff}.sidebar-table-item.active .table-item-meta span{background:#fff3;color:#fff}.sidebar-table-item.filtered-out{opacity:.4}.table-item-main{display:flex;align-items:center;gap:8px;min-width:0}.table-icon{flex-shrink:0;color:var(--text-secondary)}.sidebar-table-item .table-name{font-size:13px;font-weight:500;color:var(--text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.table-item-meta{display:flex;align-items:center;gap:6px}.field-count,.doc-count{font-size:10px;padding:2px 6px;border-radius:8px;background:var(--bg-hover);color:var(--text-secondary)}.field-count{background:#eb56011a;color:var(--accent-interactive)}.convex-info-list{padding:8px 14px}.convex-info-item{display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid var(--border)}.convex-info-item:last-child{border-bottom:none}.info-label{font-size:12px;color:var(--text-secondary)}.info-value{font-size:12px;font-weight:600;color:var(--text-primary)}.legend-line.dashed{background:repeating-linear-gradient(to right,var(--accent) 0px,var(--accent) 4px,transparent 4px,transparent 8px)}.graph-view .sidebar-container{display:flex;flex-direction:row;flex-shrink:0;position:relative;border-left:1px solid var(--border);height:100%}.graph-view .sidebar-container .code-panel{border-right:none;border-left:none;min-width:200px;max-width:600px}.resize-handle-left{cursor:col-resize;width:4px;background:transparent;transition:background .2s}.resize-handle-left:hover,.resize-handle-left.dragging{background:var(--accent-interactive)}.sidebar-toggle-right{position:absolute;top:12px;left:-32px;right:auto}.sidebar-toggle-right svg{transform:rotate(180deg)}.sidebar-container.collapsed .sidebar-toggle-right{left:-24px}.sidebar-container.collapsed .sidebar-toggle-right svg{transform:rotate(0)}.sidebar-container.hidden{display:none}.tooltip{position:fixed;z-index:1000;max-width:280px;padding:10px 14px;background:#fff;border:1px solid var(--border);border-radius:8px;box-shadow:0 8px 24px #0000001f;font-size:12px;line-height:1.5;animation:tooltipFadeIn .2s ease;pointer-events:none}@keyframes tooltipFadeIn{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.tooltip-title{font-weight:600;color:var(--text-primary);margin-bottom:4px}.tooltip-content{color:var(--text-secondary)}.tooltip-content code{background:var(--bg-secondary);padding:1px 5px;border-radius:4px;font-family:var(--font-mono);font-size:11px;color:var(--accent-interactive)}.tooltip-content strong{color:var(--text-primary)}.tooltip-content em{color:var(--warning);font-style:normal}.tooltip-system{border-left:3px solid var(--text-secondary)}.tooltip-field{border-left:3px solid var(--accent-interactive)}.tooltip-relationship{border-left:3px solid var(--accent)}.loading-overlay{position:absolute;top:0;left:0;right:0;bottom:0;background:#faf8f5e6;display:flex;align-items:center;justify-content:center;z-index:50}.loading-content{text-align:center}.loading-spinner{width:40px;height:40px;border:3px solid var(--border);border-top-color:var(--accent-interactive);border-radius:50%;animation:spin .8s linear infinite;margin:0 auto 16px}.loading-text{color:var(--text-secondary);font-size:14px}.skeleton-sidebar{padding:16px}.skeleton-item{display:flex;align-items:center;gap:10px;padding:10px 0;border-bottom:1px solid var(--border)}.skeleton-icon{width:20px;height:20px;border-radius:4px;background:linear-gradient(90deg,#f0efed 25%,#e6e4e1,#f0efed 75%);background-size:200px 100%;animation:shimmer 1.5s infinite}.skeleton-text{flex:1;height:14px;border-radius:4px;background:linear-gradient(90deg,#f0efed 25%,#e6e4e1,#f0efed 75%);background-size:200px 100%;animation:shimmer 1.5s infinite}.skeleton-badge{width:32px;height:18px;border-radius:9px;background:linear-gradient(90deg,#f0efed 25%,#e6e4e1,#f0efed 75%);background-size:200px 100%;animation:shimmer 1.5s infinite}@keyframes shimmer{0%{background-position:-200px 0}to{background-position:200px 0}}.empty-state-enhanced{text-align:center;padding:60px 30px;color:var(--text-secondary)}.empty-icon{font-size:48px;margin-bottom:16px;opacity:.6}.empty-title{font-size:18px;font-weight:600;color:var(--text-primary);margin-bottom:8px}.empty-description{font-size:14px;margin-bottom:20px;max-width:320px;margin-left:auto;margin-right:auto}.empty-action .btn{display:inline-flex;align-items:center;gap:8px}.table-card,.sidebar-table-item{transition:transform .15s ease,box-shadow .15s ease}.graph-view,.list-view{animation:viewFadeIn .3s ease}@keyframes viewFadeIn{0%{opacity:0}to{opacity:1}}.toolbar-btn:active,.zoom-btn:active,.graph-btn:active{transform:scale(.95)}.toolbar-btn:focus-visible,.zoom-btn:focus-visible,.btn:focus-visible{outline:2px solid var(--accent-interactive);outline-offset:2px}@media(min-width:1600px){.enhanced-sidebar{width:300px}.graph-view .sidebar-container .code-panel{width:420px!important}.sidebar-table-list{max-height:600px}}@media(min-width:1920px){.enhanced-sidebar{width:320px}.graph-view .sidebar-container .code-panel{width:480px!important}}@media(max-width:1200px){.enhanced-sidebar{width:220px}.code-panel{width:300px!important}}@media(max-width:900px){.toolbar-btn span{display:none}.toolbar-btn,.toolbar-btn.icon-only{padding:6px}.zoom-display{min-width:40px;font-size:11px}.enhanced-sidebar{width:180px}.sidebar-deployment{padding:10px 12px}.deployment-label{font-size:12px}}@media print{.toolbar,.enhanced-sidebar,.sidebar-container,.zoom-panel,.graph-legend{display:none!important}.graph-panel{width:100%!important}}*{box-sizing:border-box;margin:0;padding:0}html,body{width:100%;height:100%;overflow-x:hidden}:root{--bg-primary: #faf8f5;--bg-secondary: #f5f3f0;--bg-hover: #ebe9e6;--text-primary: #1a1a1a;--text-secondary: #6b6b6b;--border: #e6e4e1;--accent: #8b7355;--accent-interactive: #EB5601;--accent-hover: #d14a01;--success: #4a8c5c;--warning: #c4842d;--info: #4a7c9b;--font-mono: "SF Mono", Monaco, "Cascadia Code", "Courier New", monospace;--chart-gradient-start: #EB5601;--chart-gradient-end: #d14a01;--chart-area-opacity: .1}[data-theme=dark]{--bg-primary: #1e1e1e;--bg-secondary: #252526;--bg-hover: #37373d;--text-primary: #cccccc;--text-secondary: #8b8b8b;--border: #3c3c3c;--accent: #c9a87c;--accent-interactive: #ff6b35;--accent-hover: #ff8555;--success: #4ec9b0;--warning: #dcdcaa;--info: #4fc1ff;--chart-gradient-start: #ff6b35;--chart-gradient-end: #ff8555;--chart-area-opacity: .15}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,sans-serif;background:var(--bg-primary);color:var(--text-primary);min-height:100vh;padding:16px 20px;margin:0}.header{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}.header h1{font-size:20px;display:flex;align-items:center;gap:10px;color:var(--text-primary)}.header-right{display:flex;align-items:center;gap:16px}.deployment-url{font-size:12px;color:var(--text-secondary);font-family:var(--font-mono);margin-right:16px}.last-update{color:var(--text-secondary);font-size:12px}.theme-toggle-btn{background:var(--bg-hover);border:1px solid var(--border);color:var(--text-primary);width:32px;height:32px;border-radius:6px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:16px;transition:all .2s ease}.theme-toggle-btn:hover{background:var(--accent-interactive);color:#fff;border-color:var(--accent-interactive)}.chart-subtitle{font-size:12px;color:var(--text-secondary)}.pie-legend{padding:20px}.pie-item{display:flex;align-items:center;gap:8px;padding:8px 0;border-bottom:1px solid var(--border)}.pie-item:last-child{border-bottom:none}.pie-color{width:12px;height:12px;border-radius:2px}.pie-label{flex:1;font-size:13px}.pie-value{font-family:var(--font-mono);font-size:12px;color:var(--text-secondary)}.status-dot{width:8px;height:8px;border-radius:50%;background:var(--success);animation:pulse 2s infinite}.status-dot.disconnected{background:var(--accent-interactive);animation:none}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.metrics-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;margin-bottom:24px}.metric-card{background:var(--bg-secondary);border:1px solid var(--border);border-radius:12px;padding:20px;transition:transform .2s,box-shadow .2s}.metric-card:hover{transform:translateY(-2px);box-shadow:0 4px 12px #00000014}.metric-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:12px}.metric-label{font-size:12px;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.5px;font-weight:600}.metric-icon{width:32px;height:32px;background:var(--bg-hover);border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:16px;color:var(--accent)}.metric-value{font-size:36px;font-weight:700;margin-bottom:8px;font-family:var(--font-mono);color:var(--text-primary)}.metric-change{font-size:13px;display:flex;align-items:center;gap:4px}.metric-change.positive{color:var(--success)}.metric-change.negative{color:var(--accent-interactive)}.metric-change.neutral{color:var(--text-secondary)}.metric-source{font-size:11px;color:var(--text-secondary);margin-top:8px;font-family:var(--font-mono)}.charts-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(400px,1fr));gap:16px}@media(max-width:900px){.charts-grid{grid-template-columns:1fr}}.chart-card{background:var(--bg-secondary);border:1px solid var(--border);border-radius:12px;padding:20px}.chart-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.chart-title{font-size:14px;font-weight:600;color:var(--text-primary)}.chart-actions{display:flex;gap:8px}.chart-btn{background:var(--bg-hover);border:1px solid var(--border);color:var(--text-secondary);padding:4px 8px;border-radius:4px;font-size:11px;cursor:pointer;transition:all .2s}.chart-btn:hover{color:var(--text-primary);border-color:var(--accent)}.chart-btn.active{background:var(--accent-interactive);color:#fff;border-color:var(--accent-interactive)}.chart-container{height:220px;background:var(--bg-hover);border-radius:8px;display:flex;align-items:center;justify-content:center;position:relative;overflow:hidden}.bar-chart{display:flex;align-items:flex-end;justify-content:space-around;height:100%;width:100%;padding:20px;gap:8px}.bar{flex:1;max-width:40px;background:linear-gradient(to top,var(--accent-interactive),var(--accent-hover));border-radius:4px 4px 0 0;transition:height .3s ease;position:relative;min-height:4px}.bar:after{content:attr(data-value);position:absolute;top:-20px;left:50%;transform:translate(-50%);font-size:11px;color:var(--text-secondary);font-family:var(--font-mono)}.bar-label{position:absolute;bottom:-20px;left:50%;transform:translate(-50%);font-size:10px;color:var(--text-secondary);white-space:nowrap}.line-chart{width:100%;height:100%;padding:20px}.line-chart svg{width:100%;height:100%}.line-chart path{fill:none;stroke:var(--accent-interactive);stroke-width:2}.line-chart .area{fill:var(--accent-interactive);opacity:.1}.table-chart{width:100%;height:100%;overflow:auto;padding:0}.table-chart table{width:100%;border-collapse:collapse;font-size:12px}.table-chart th,.table-chart td{padding:10px 12px;text-align:left;border-bottom:1px solid var(--border)}.table-chart th{background:var(--bg-secondary);font-weight:600;position:sticky;top:0;color:var(--text-primary)}.table-chart tr:hover td{background:#eb56010d}.empty-state{text-align:center;color:var(--text-secondary);padding:40px}.empty-state h3{color:var(--text-primary);margin-bottom:8px;font-size:16px}.empty-state p{font-size:13px}.loading{display:flex;align-items:center;justify-content:center;gap:12px;color:var(--text-secondary)}.spinner{width:20px;height:20px;border:2px solid var(--border);border-top-color:var(--accent-interactive);border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}@media(max-width:600px){body{padding:12px}.header{flex-direction:column;align-items:flex-start;gap:12px}.metrics-grid{grid-template-columns:1fr}.metric-value{font-size:28px}}
|
|
1
|
+
html,body{width:100%;height:100%;overflow:hidden}:root{--bg-primary: #faf8f5;--bg-secondary: #f5f3f0;--bg-tertiary: #ebe9e6;--bg-hover: #ebe9e6;--bg-canvas: #faf8f5;--text-primary: #1a1a1a;--text-secondary: #6b6b6b;--text-muted: #999999;--border: #e6e4e1;--border-strong: #d4d2cf;--accent: #8b7355;--accent-interactive: #eb5601;--accent-hover: #d14a01;--success: #4a8c5c;--warning: #c4842d;--warning-bg: #fef3e2;--warning-text: #8a5a00;--error: #dc3545;--info: #4a7c9b;--font-mono: "SF Mono", Monaco, "Cascadia Code", "Courier New", monospace;--code-bg: #1e1e1e;--code-text: #d4d4d4;--code-key: #9cdcfe;--code-string: #ce9178;--code-number: #b5cea8;--code-boolean: #569cd6;--shadow-sm: 0 1px 2px rgba(0, 0, 0, .05);--shadow-md: 0 4px 12px rgba(0, 0, 0, .08);--shadow-lg: 0 8px 24px rgba(0, 0, 0, .12);--node-bg: #ffffff;--node-header: #f8f7f5;--node-header-selected: #eb5601;--node-border: #e6e4e1;--grid-line: #e6e4e1}[data-theme=dark]{--bg-primary: #1e1e1e;--bg-secondary: #252526;--bg-tertiary: #2d2d2d;--bg-hover: #37373d;--bg-canvas: #1e1e1e;--text-primary: #cccccc;--text-secondary: #8b8b8b;--text-muted: #6b6b6b;--border: #3c3c3c;--border-strong: #4a4a4a;--accent: #c9a87c;--accent-interactive: #ff6b35;--accent-hover: #ff8555;--success: #4ec9b0;--warning: #dcdcaa;--warning-bg: #3d3520;--warning-text: #dcdcaa;--error: #f14c4c;--info: #4fc1ff;--code-bg: #1e1e1e;--code-text: #d4d4d4;--code-key: #9cdcfe;--code-string: #ce9178;--code-number: #b5cea8;--code-boolean: #569cd6;--shadow-sm: 0 1px 2px rgba(0, 0, 0, .3);--shadow-md: 0 4px 12px rgba(0, 0, 0, .4);--shadow-lg: 0 8px 24px rgba(0, 0, 0, .5);--node-bg: #2d2d2d;--node-header: #37373d;--node-header-selected: #ff6b35;--node-border: #4a4a4a;--grid-line: #2d2d2d}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,sans-serif;background:var(--bg-primary);color:var(--text-primary);min-height:100vh;display:flex;flex-direction:column;margin:0;padding:0}.header{background:var(--bg-secondary);padding:10px 16px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center}.header h1{font-size:18px;font-weight:600;display:flex;align-items:center;gap:8px;color:var(--text-primary)}.header-info{flex:1;margin:0 16px}.deployment-url{font-size:12px;color:var(--text-secondary);font-family:var(--font-mono)}.header-actions{display:flex;gap:8px;align-items:center}.theme-toggle{width:36px;height:36px;background:var(--bg-hover);border:1px solid var(--border);border-radius:8px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--text-secondary);transition:all .2s ease}.theme-toggle:hover{background:var(--accent-interactive);border-color:var(--accent-interactive);color:#fff}.theme-toggle svg{width:18px;height:18px}.theme-toggle .sun-icon{display:none}.theme-toggle .moon-icon,[data-theme=dark] .theme-toggle .sun-icon{display:block}[data-theme=dark] .theme-toggle .moon-icon{display:none}.btn{background:var(--bg-primary);color:var(--text-primary);border:1px solid var(--border);padding:6px 12px;border-radius:6px;cursor:pointer;font-size:13px;transition:all .2s}.btn:hover{background:var(--bg-hover);border-color:var(--accent)}.btn-primary{background:var(--accent-interactive);border-color:var(--accent-interactive);color:#fff}.btn-primary:hover{background:var(--accent-hover);border-color:var(--accent-hover)}.main{display:flex;flex:1;overflow:hidden}.sidebar{width:260px;background:var(--bg-secondary);border-right:1px solid var(--border);overflow-y:auto;flex-shrink:0;display:flex;flex-direction:column;margin:0;padding:0}.sidebar-header{padding:12px 16px;font-size:11px;text-transform:uppercase;color:var(--text-secondary);border-bottom:1px solid var(--border);display:flex;align-items:center;font-weight:600;letter-spacing:.5px;gap:8px;position:relative}.sidebar-header #tableCount{background:var(--bg-hover);padding:2px 8px;border-radius:10px;font-size:11px}.sidebar-header .sidebar-collapse-btn{margin-left:auto;width:24px;height:24px;background:var(--bg-hover);border:1px solid transparent;border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--text-secondary);transition:all .15s;flex-shrink:0}.sidebar-header .sidebar-collapse-btn:hover{background:var(--accent-interactive);color:#fff}.sidebar-header .sidebar-collapse-btn svg{transition:transform .2s}.sidebar-search{padding:8px 12px;border-bottom:1px solid var(--border)}.sidebar-search input{width:100%;background:var(--bg-primary);border:1px solid var(--border);border-radius:6px;padding:8px 10px;color:var(--text-primary);font-size:13px}.sidebar-search input:focus{outline:none;border-color:var(--accent-interactive);box-shadow:0 0 0 2px #eb56011a}.table-list{list-style:none;flex:1;overflow-y:auto}.table-item{padding:10px 16px;cursor:pointer;display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--border);transition:background .2s}.table-item:hover{background:var(--bg-hover)}.table-item.active{background:var(--accent-interactive);color:#fff}.table-item.active .table-count{background:#fff3;color:#fff}.table-item.active .table-icon{opacity:1}.table-name{font-size:14px;font-weight:500;display:flex;align-items:center;gap:6px}.table-icon{opacity:.5}.table-count{font-size:12px;color:var(--text-secondary);background:var(--bg-hover);padding:2px 8px;border-radius:10px}.content{flex:1;display:flex;flex-direction:column;overflow:hidden;background:var(--bg-primary)}.schema-panel{padding:20px;flex:1;overflow-y:auto}.schema-title{font-size:22px;margin-bottom:8px;display:flex;align-items:center;gap:12px;color:var(--text-primary)}.schema-subtitle{color:var(--text-secondary);font-size:13px;margin-bottom:20px}.schema-grid{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-bottom:20px}@media(max-width:900px){.schema-grid{grid-template-columns:1fr}}.schema-card{background:var(--bg-secondary);border:1px solid var(--border);border-radius:8px;overflow:hidden}.schema-card-header{background:var(--bg-hover);padding:10px 16px;font-weight:600;font-size:14px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center;color:var(--text-primary)}.schema-card-header .count{color:var(--text-secondary);font-weight:400;font-size:12px}.schema-card-body{padding:8px;max-height:400px;overflow-y:auto}.field-row{display:flex;justify-content:space-between;padding:8px 10px;border-radius:4px;font-size:13px;font-family:var(--font-mono)}.field-row:hover{background:var(--bg-hover)}.field-name{color:var(--accent-interactive)}.field-type{color:var(--text-secondary)}.field-optional{color:var(--warning);font-size:11px;margin-left:2px}.field-system{opacity:.6}.indexes-section{margin-top:20px}.indexes-title{font-size:14px;font-weight:600;margin-bottom:12px;color:var(--text-secondary)}.index-item{background:var(--bg-secondary);border:1px solid var(--border);border-radius:6px;padding:12px 16px;margin-bottom:8px}.index-name{font-family:var(--font-mono);font-size:13px;color:var(--accent-interactive);margin-bottom:4px}.index-fields{font-size:12px;color:var(--text-secondary)}.documents-panel{background:var(--bg-secondary);border-top:1px solid var(--border);max-height:320px;overflow:hidden;display:flex;flex-direction:column}.documents-header{padding:10px 16px;display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--border);background:var(--bg-hover);flex-shrink:0}.documents-title{font-weight:600;font-size:14px;color:var(--text-primary)}.pagination{display:flex;align-items:center;gap:8px}.pagination button{background:var(--bg-primary);color:var(--text-primary);border:1px solid var(--border);width:28px;height:28px;border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:12px}.pagination button:hover:not(:disabled){background:var(--accent-interactive);border-color:var(--accent-interactive);color:#fff}.pagination button:disabled{opacity:.5;cursor:not-allowed}.pagination span{font-size:13px;color:var(--text-secondary)}.documents-table{overflow:auto;flex:1}table{width:100%;border-collapse:collapse;font-size:13px}th,td{padding:10px 14px;text-align:left;border-bottom:1px solid var(--border);white-space:nowrap}th{background:var(--bg-hover);font-weight:600;position:sticky;top:0;z-index:1;color:var(--text-primary)}td{font-family:var(--font-mono);font-size:12px}tr:hover td{background:var(--bg-hover)}td.id-cell{color:var(--accent-interactive);cursor:pointer}td.id-cell:hover{text-decoration:underline}td.null-value{color:var(--text-secondary);font-style:italic}td.truncated{max-width:200px;overflow:hidden;text-overflow:ellipsis}.empty-state{text-align:center;padding:60px 20px;color:var(--text-secondary)}.empty-state h2{margin-bottom:8px;color:var(--text-primary);font-size:18px}.empty-state p{font-size:14px}.warning-badge{background:#c4842d1a;border:1px solid var(--warning);color:var(--warning);padding:10px 14px;border-radius:6px;font-size:13px;margin-top:16px;display:flex;align-items:center;gap:8px}.warning-badge:before{content:"⚠"}.loading{display:flex;align-items:center;justify-content:center;padding:40px;gap:12px;color:var(--text-secondary)}.spinner{width:24px;height:24px;border:2px solid var(--border);border-top-color:var(--accent-interactive);border-radius:50%;animation:spin .8s linear infinite}.status-dot{width:8px;height:8px;border-radius:50%;background:var(--success)}.status-dot.error{background:var(--accent-interactive)}.status-dot.warning{background:var(--warning)}.modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:#1a1a1a80;display:flex;align-items:center;justify-content:center;z-index:100}.modal{background:var(--bg-primary);border:1px solid var(--border);border-radius:12px;width:90%;max-width:600px;max-height:80vh;overflow:hidden;display:flex;flex-direction:column;box-shadow:0 20px 40px #00000026}.modal-header{padding:16px 20px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center;background:var(--bg-secondary)}.modal-header h2{font-size:16px;color:var(--text-primary)}.modal-close{background:none;border:none;color:var(--text-secondary);font-size:20px;cursor:pointer;padding:4px}.modal-close:hover{color:var(--text-primary)}.modal-body{padding:20px;flex:1;overflow-y:auto}.query-editor{width:100%;min-height:150px;background:var(--bg-secondary);border:1px solid var(--border);border-radius:6px;padding:12px;color:var(--text-primary);font-family:var(--font-mono);font-size:13px;resize:vertical}.query-editor:focus{outline:none;border-color:var(--accent-interactive);box-shadow:0 0 0 2px #eb56011a}.modal-footer{padding:16px 20px;border-top:1px solid var(--border);display:flex;justify-content:flex-end;gap:8px;background:var(--bg-secondary)}kbd{background:var(--bg-secondary);border:1px solid var(--border);border-radius:3px;padding:2px 6px;font-size:11px;font-family:var(--font-mono)}.app-container{display:flex;flex-direction:column;height:100vh;width:100vw;overflow:hidden;margin:0;padding:0;position:absolute;top:0;left:0}.view-toggle{display:flex;gap:4px;background:var(--bg-hover);padding:4px;border-radius:8px;margin-right:12px}.view-btn{display:flex;align-items:center;justify-content:center;width:32px;height:28px;border:none;background:transparent;color:var(--text-secondary);border-radius:6px;cursor:pointer;transition:all .2s}.view-btn:hover{color:var(--text-primary);background:var(--bg-secondary)}.view-btn.active{background:var(--accent-interactive);color:#fff}.graph-view{display:flex;flex-direction:column;flex:1;overflow:hidden}.graph-view .toolbar{order:-1}.code-panel{width:360px;background:#1e1e1e;border-right:1px solid #333;display:flex;flex-direction:column;flex-shrink:0}.code-header{padding:12px 16px;background:#252526;border-bottom:1px solid #333;display:flex;justify-content:space-between;align-items:center;color:#ccc;font-size:12px}.code-filename{color:#888;font-family:var(--font-mono)}.code-content{flex:1;overflow:auto;padding:0}.code-content pre{margin:0;padding:16px;font-family:var(--font-mono);font-size:12px;line-height:1.6;color:#d4d4d4}.code-content code{font-family:inherit}.json-key{color:#9cdcfe}.json-string{color:#ce9178}.json-number{color:#b5cea8}.json-boolean,.json-null{color:#569cd6}.graph-panel{flex:1;position:relative;background:var(--bg-primary);overflow:hidden;min-width:0}#graphCanvas{width:100%;height:100%;display:block}.graph-controls{position:absolute;top:16px;right:16px;display:flex;flex-direction:column;gap:8px}.graph-btn{width:36px;height:36px;border:1px solid var(--border);background:#fff;color:var(--text-primary);border-radius:8px;cursor:pointer;font-size:18px;display:flex;align-items:center;justify-content:center;transition:all .2s;box-shadow:0 2px 8px #00000014}.graph-btn:hover{background:var(--bg-hover);border-color:var(--accent)}.graph-legend{position:absolute;bottom:16px;right:16px;background:#fff;border:1px solid var(--border);border-radius:8px;padding:12px 16px;display:flex;flex-direction:column;gap:8px;font-size:12px;box-shadow:0 2px 8px #00000014}.legend-item{display:flex;align-items:center;gap:8px;color:var(--text-secondary)}.legend-dot{width:12px;height:12px;border-radius:4px;background:var(--accent-interactive)}.legend-dot.table{background:#fff;border:2px solid var(--border)}.legend-line{width:20px;height:2px;background:var(--accent)}.list-view{display:flex;flex:1;overflow:hidden}.list-view .content{display:flex;flex-direction:column;flex:1;overflow:hidden}.list-view .table-header{padding:16px 24px;border-bottom:1px solid var(--border);background:var(--bg-primary);flex-shrink:0}.list-view .table-header-title{font-size:24px;font-weight:600;color:var(--text-primary);margin-bottom:4px}.list-view .table-header-meta{font-size:13px;color:var(--text-secondary);display:flex;align-items:center;gap:12px}.list-view .table-header-meta .badge{background:var(--bg-hover);padding:2px 8px;border-radius:4px;font-size:12px}.list-view .content-split{display:flex;flex:1;overflow:hidden}.list-view .schema-sidebar{width:280px;min-width:240px;max-width:360px;background:var(--bg-secondary);border-right:1px solid var(--border);display:flex;flex-direction:column;overflow:hidden;flex-shrink:0}.list-view .schema-sidebar-header{padding:12px 16px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--text-secondary);border-bottom:1px solid var(--border);background:var(--bg-hover);display:flex;justify-content:space-between;align-items:center}.list-view .schema-sidebar-header .field-count-badge{font-size:11px;color:var(--text-secondary);background:var(--bg-primary);padding:2px 8px;border-radius:10px}.list-view .schema-fields-list{flex:1;overflow-y:auto;padding:8px 0}.list-view .schema-field-item{display:flex;justify-content:space-between;align-items:center;padding:10px 16px;border-bottom:1px solid var(--border);transition:background .15s}.list-view .schema-field-item:hover{background:var(--bg-hover)}.list-view .schema-field-item:last-child{border-bottom:none}.list-view .schema-field-name{font-family:var(--font-mono);font-size:13px;color:var(--accent-interactive);display:flex;align-items:center;gap:4px}.list-view .schema-field-name.system-field{color:var(--text-secondary)}.list-view .schema-field-optional{font-size:10px;color:var(--warning);font-weight:600}.list-view .schema-field-type{font-family:var(--font-mono);font-size:12px;color:var(--text-secondary);background:var(--bg-primary);padding:2px 8px;border-radius:4px}.list-view .schema-indexes{border-top:1px solid var(--border);padding:12px 16px;background:var(--bg-hover)}.list-view .schema-indexes-title{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--text-secondary);margin-bottom:8px}.list-view .schema-index-item{font-family:var(--font-mono);font-size:12px;color:var(--accent);padding:4px 0}.list-view .documents-main{flex:1;display:flex;flex-direction:column;overflow:hidden;background:var(--bg-primary);min-width:0}.list-view .documents-toolbar{padding:12px 20px;display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--border);background:var(--bg-secondary);flex-shrink:0}.list-view .documents-toolbar-title{font-size:13px;font-weight:600;color:var(--text-primary);display:flex;align-items:center;gap:8px}.list-view .documents-toolbar-title .doc-count{font-weight:400;color:var(--text-secondary)}.list-view .documents-table-wrapper{flex:1;overflow:auto;padding:0}.list-view .documents-table-wrapper table{width:100%;border-collapse:collapse;font-size:13px}.list-view .documents-table-wrapper th{position:sticky;top:0;background:var(--bg-hover);font-weight:600;text-align:left;padding:12px 16px;border-bottom:2px solid var(--border);white-space:nowrap;z-index:1}.list-view .documents-table-wrapper td{padding:10px 16px;border-bottom:1px solid var(--border);font-family:var(--font-mono);font-size:12px;vertical-align:top;max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.list-view .documents-table-wrapper tr:hover td{background:var(--bg-secondary)}.list-view .documents-table-wrapper td.id-cell{color:var(--accent-interactive);cursor:pointer}.list-view .documents-table-wrapper td.id-cell:hover{text-decoration:underline}.list-view .pagination-bar{padding:12px 20px;display:flex;justify-content:space-between;align-items:center;border-top:1px solid var(--border);background:var(--bg-secondary);flex-shrink:0}.list-view .pagination-info{font-size:13px;color:var(--text-secondary)}.list-view .pagination-controls{display:flex;align-items:center;gap:8px}.list-view .pagination-controls button{width:32px;height:32px;display:flex;align-items:center;justify-content:center;background:var(--bg-primary);border:1px solid var(--border);border-radius:6px;cursor:pointer;color:var(--text-primary);font-size:14px;transition:all .15s}.list-view .pagination-controls button:hover:not(:disabled){background:var(--accent-interactive);border-color:var(--accent-interactive);color:#fff}.list-view .pagination-controls button:disabled{opacity:.4;cursor:not-allowed}.list-view .pagination-controls .page-indicator{font-size:13px;color:var(--text-secondary);min-width:80px;text-align:center}.list-view .documents-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;color:var(--text-secondary);text-align:center}.list-view .documents-empty-icon{font-size:48px;margin-bottom:16px;opacity:.5}.list-view .documents-empty h3{font-size:16px;color:var(--text-primary);margin-bottom:4px}.list-view .documents-empty p{font-size:13px}.list-view .schema-panel,.list-view .documents-panel{display:none!important}.sidebar-container{display:flex;flex-shrink:0;position:relative}.sidebar-container.collapsed .sidebar{width:48px!important;min-width:48px!important;overflow:hidden}.sidebar-container.collapsed .sidebar .sidebar-header{padding:12px;justify-content:center}.sidebar-container.collapsed .sidebar .sidebar-header>span{display:none}.sidebar-container.collapsed .sidebar .sidebar-header .sidebar-collapse-btn{margin-left:0}.sidebar-container.collapsed .sidebar .sidebar-search,.sidebar-container.collapsed .sidebar .table-list{display:none}.sidebar-container.collapsed .code-panel{width:0!important;min-width:0!important;padding:0;overflow:hidden;border:none}.sidebar-container.collapsed .resize-handle{display:none}.resize-handle{width:4px;cursor:col-resize;background:transparent;transition:background .2s;position:relative;z-index:10}.resize-handle:hover,.resize-handle.dragging{background:var(--accent-interactive)}.resize-handle:after{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:4px;height:40px;border-radius:2px;background:var(--border);opacity:0;transition:opacity .2s}.resize-handle:hover:after{opacity:1;background:#fff}.sidebar-toggle{display:none}.list-view .sidebar-header{position:relative}.list-view .sidebar-header .sidebar-collapse-btn{position:absolute;right:8px;top:50%;transform:translateY(-50%);width:24px;height:24px;background:var(--bg-hover);border:1px solid var(--border);border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--text-secondary);transition:all .2s}.list-view .sidebar-header .sidebar-collapse-btn:hover{background:var(--accent-interactive);color:#fff;border-color:var(--accent-interactive)}.list-view .sidebar-header .sidebar-collapse-btn svg{width:12px;height:12px;transition:transform .2s}.sidebar-container.collapsed .sidebar-collapse-btn svg{transform:rotate(180deg)}.list-view .sidebar{min-width:180px;max-width:400px}.list-view .sidebar-container{height:100%}.toolbar{display:flex;align-items:center;padding:8px 16px;background:var(--bg-secondary);border-bottom:1px solid var(--border);gap:4px;flex-shrink:0;width:100%;z-index:10}.toolbar-group{display:flex;align-items:center;gap:2px}.toolbar-separator{width:1px;height:24px;background:var(--border);margin:0 8px}.toolbar-spacer{flex:1}.toolbar-btn{display:flex;align-items:center;gap:6px;padding:6px 10px;background:transparent;border:1px solid transparent;border-radius:6px;color:var(--text-secondary);font-size:13px;cursor:pointer;transition:all .15s ease}.toolbar-btn:hover:not(:disabled){background:var(--bg-hover);color:var(--text-primary);border-color:var(--border)}.toolbar-btn.active{background:var(--accent-interactive);color:#fff;border-color:var(--accent-interactive)}.toolbar-btn.active:hover{background:var(--accent-hover);border-color:var(--accent-hover)}.toolbar-btn:disabled{opacity:.4;cursor:not-allowed}.toolbar-btn.icon-only{padding:6px}.toolbar-btn svg{flex-shrink:0}.toolbar-btn .dropdown-arrow{margin-left:2px;opacity:.6}.zoom-group{display:flex;align-items:center;gap:4px;background:var(--bg-primary);border:1px solid var(--border);border-radius:6px;padding:2px}.zoom-group .toolbar-btn{border-radius:4px}.zoom-display{min-width:48px;text-align:center;font-size:12px;font-weight:500;color:var(--text-secondary);font-family:var(--font-mono)}.graph-content{display:flex;flex:1;overflow:hidden;flex-direction:row}.graph-content .enhanced-sidebar{order:0}.graph-content .graph-panel{order:1;flex:1}.graph-content .sidebar-container{order:2}.sidebar-container.hidden .code-panel{width:0!important;min-width:0;padding:0;overflow:hidden;border:none}.sidebar-container.hidden .resize-handle{display:none}.sidebar-container.hidden .sidebar-toggle{left:0;border-radius:0 6px 6px 0}.zoom-panel{position:absolute;bottom:20px;left:20px;display:flex;flex-direction:column;gap:4px;background:#fff;border:1px solid var(--border);border-radius:10px;padding:8px;box-shadow:0 4px 12px #0000001a;z-index:10}.zoom-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;background:transparent;border:none;border-radius:6px;color:var(--text-secondary);cursor:pointer;transition:all .15s ease}.zoom-btn:hover{background:var(--bg-hover);color:var(--text-primary)}.zoom-btn:active{background:var(--accent-interactive);color:#fff}.zoom-level{text-align:center;font-size:11px;font-weight:500;color:var(--text-secondary);font-family:var(--font-mono);padding:4px 0}.zoom-separator{height:1px;background:var(--border);margin:4px 0}.dropdown-menu{position:absolute;background:#fff;border:1px solid var(--border);border-radius:10px;box-shadow:0 8px 24px #0000001f;z-index:100;overflow:hidden;min-width:180px}.export-menu{top:52px;left:140px}.dropdown-item{display:flex;align-items:center;gap:10px;width:100%;padding:10px 14px;background:transparent;border:none;color:var(--text-primary);font-size:13px;cursor:pointer;transition:background .15s ease;text-align:left}.dropdown-item:hover{background:var(--bg-hover)}.dropdown-item svg{color:var(--text-secondary)}.filter-menu{position:absolute;top:52px;right:200px;width:280px}.filter-header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid var(--border);font-weight:600;font-size:14px}.filter-close{background:none;border:none;font-size:18px;color:var(--text-secondary);cursor:pointer;padding:0;line-height:1}.filter-close:hover{color:var(--text-primary)}.filter-body{padding:16px}.filter-group{margin-bottom:14px}.filter-group:last-child{margin-bottom:0}.filter-group label{display:block;font-size:12px;font-weight:500;color:var(--text-secondary);margin-bottom:6px}.filter-group input[type=text],.filter-group select{width:100%;padding:8px 10px;background:var(--bg-secondary);border:1px solid var(--border);border-radius:6px;font-size:13px;color:var(--text-primary)}.filter-group input[type=text]:focus,.filter-group select:focus{outline:none;border-color:var(--accent-interactive);box-shadow:0 0 0 2px #eb56011a}.filter-group.checkbox label{display:flex;align-items:center;gap:8px;cursor:pointer}.filter-group.checkbox input[type=checkbox]{width:16px;height:16px;accent-color:var(--accent-interactive)}.filter-footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid var(--border);background:var(--bg-secondary)}.filter-footer .btn{padding:6px 14px;font-size:13px}.btn-secondary{background:var(--bg-primary);border:1px solid var(--border);color:var(--text-primary)}.btn-secondary:hover{background:var(--bg-hover)}.enhanced-sidebar{width:260px;background:var(--bg-secondary);border-right:1px solid var(--border);display:flex;flex-direction:column;flex-shrink:0;overflow:hidden;height:100%;margin:0;padding:0;position:relative;transition:width .2s ease,min-width .2s ease}.graph-sidebar-toggle{position:absolute;top:50%;right:-14px;transform:translateY(-50%);width:28px;height:48px;background:var(--bg-secondary);border:1px solid var(--border);border-left:none;border-radius:0 8px 8px 0;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--text-secondary);z-index:20;transition:all .15s ease;box-shadow:2px 0 8px #0000000d}.graph-sidebar-toggle:hover{background:var(--accent-interactive);color:#fff;border-color:var(--accent-interactive)}.graph-sidebar-toggle svg{transition:transform .2s ease}.enhanced-sidebar.collapsed{width:0!important;min-width:0!important;border-right:none;overflow:visible}.enhanced-sidebar.collapsed .sidebar-deployment,.enhanced-sidebar.collapsed .sidebar-section{display:none}.enhanced-sidebar.collapsed .graph-sidebar-toggle{right:-28px;border-left:1px solid var(--border);border-radius:0 8px 8px 0}.sidebar-deployment{padding:14px 16px;border-bottom:1px solid var(--border);background:linear-gradient(to bottom,var(--bg-hover),var(--bg-secondary))}.deployment-status{display:flex;align-items:center;gap:8px;margin-bottom:4px}.status-indicator{width:8px;height:8px;border-radius:50%;background:var(--success)}.status-indicator.error{background:var(--accent-interactive)}.deployment-label{font-size:13px;font-weight:600;color:var(--text-primary)}.deployment-url-small{font-size:11px;font-family:var(--font-mono);color:var(--text-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sidebar-section{border-bottom:1px solid var(--border)}.section-header{display:flex;align-items:center;gap:8px;padding:12px 14px;cursor:pointer;-webkit-user-select:none;user-select:none;transition:background .15s ease}.section-header:hover{background:var(--bg-hover)}.section-chevron{transition:transform .2s ease;color:var(--text-secondary)}.sidebar-section:not(.collapsed) .section-chevron{transform:rotate(90deg)}.section-title{font-size:11px;font-weight:600;letter-spacing:.5px;color:var(--text-secondary);text-transform:uppercase}.section-count{margin-left:auto;font-size:11px;color:var(--text-secondary);background:var(--bg-hover);padding:2px 8px;border-radius:10px}.section-content{max-height:500px;overflow:hidden;transition:max-height .3s ease,opacity .2s ease}.sidebar-section.collapsed .section-content{max-height:0;opacity:0}.sidebar-toolbar{display:flex;gap:6px;padding:8px 12px;border-bottom:1px solid var(--border);background:var(--bg-primary)}.sidebar-filter{flex:1;padding:6px 10px;background:var(--bg-secondary);border:1px solid var(--border);border-radius:6px;font-size:12px;color:var(--text-primary)}.sidebar-filter:focus{outline:none;border-color:var(--accent-interactive);box-shadow:0 0 0 2px #eb56011a}.sort-btn{width:28px;height:28px;display:flex;align-items:center;justify-content:center;background:var(--bg-secondary);border:1px solid var(--border);border-radius:6px;color:var(--text-secondary);cursor:pointer;transition:all .15s ease}.sort-btn:hover{background:var(--bg-hover);border-color:var(--accent);color:var(--text-primary)}.sidebar-table-list{list-style:none;max-height:400px;overflow-y:auto;padding:4px 0}.sidebar-table-item{display:flex;align-items:center;justify-content:space-between;padding:8px 14px;cursor:pointer;transition:background .15s ease}.sidebar-table-item:hover{background:var(--bg-hover)}.sidebar-table-item.active{background:var(--accent-interactive)}.sidebar-table-item.active .table-name,.sidebar-table-item.active .table-icon{color:#fff}.sidebar-table-item.active .table-item-meta span{background:#fff3;color:#fff}.sidebar-table-item.filtered-out{opacity:.4}.table-item-main{display:flex;align-items:center;gap:8px;min-width:0}.table-icon{flex-shrink:0;color:var(--text-secondary)}.sidebar-table-item .table-name{font-size:13px;font-weight:500;color:var(--text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.table-item-meta{display:flex;align-items:center;gap:6px}.field-count,.doc-count{font-size:10px;padding:2px 6px;border-radius:8px;background:var(--bg-hover);color:var(--text-secondary)}.field-count{background:#eb56011a;color:var(--accent-interactive)}.convex-info-list{padding:8px 14px}.convex-info-item{display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid var(--border)}.convex-info-item:last-child{border-bottom:none}.info-label{font-size:12px;color:var(--text-secondary)}.info-value{font-size:12px;font-weight:600;color:var(--text-primary)}.legend-line.dashed{background:repeating-linear-gradient(to right,var(--accent) 0px,var(--accent) 4px,transparent 4px,transparent 8px)}.graph-view .sidebar-container{display:flex;flex-direction:row;flex-shrink:0;position:relative;border-left:1px solid var(--border);height:100%}.graph-view .sidebar-container .code-panel{border-right:none;border-left:none;min-width:200px;max-width:600px}.resize-handle-left{cursor:col-resize;width:4px;background:transparent;transition:background .2s}.resize-handle-left:hover,.resize-handle-left.dragging{background:var(--accent-interactive)}.sidebar-toggle-right{position:absolute;top:12px;left:-32px;right:auto}.sidebar-toggle-right svg{transform:rotate(180deg)}.sidebar-container.collapsed .sidebar-toggle-right{left:-24px}.sidebar-container.collapsed .sidebar-toggle-right svg{transform:rotate(0)}.sidebar-container.hidden{display:none}.tooltip{position:fixed;z-index:1000;max-width:280px;padding:10px 14px;background:#fff;border:1px solid var(--border);border-radius:8px;box-shadow:0 8px 24px #0000001f;font-size:12px;line-height:1.5;animation:tooltipFadeIn .2s ease;pointer-events:none}@keyframes tooltipFadeIn{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.tooltip-title{font-weight:600;color:var(--text-primary);margin-bottom:4px}.tooltip-content{color:var(--text-secondary)}.tooltip-content code{background:var(--bg-secondary);padding:1px 5px;border-radius:4px;font-family:var(--font-mono);font-size:11px;color:var(--accent-interactive)}.tooltip-content strong{color:var(--text-primary)}.tooltip-content em{color:var(--warning);font-style:normal}.tooltip-system{border-left:3px solid var(--text-secondary)}.tooltip-field{border-left:3px solid var(--accent-interactive)}.tooltip-relationship{border-left:3px solid var(--accent)}.loading-overlay{position:absolute;top:0;left:0;right:0;bottom:0;background:#faf8f5e6;display:flex;align-items:center;justify-content:center;z-index:50}.loading-content{text-align:center}.loading-spinner{width:40px;height:40px;border:3px solid var(--border);border-top-color:var(--accent-interactive);border-radius:50%;animation:spin .8s linear infinite;margin:0 auto 16px}.loading-text{color:var(--text-secondary);font-size:14px}.skeleton-sidebar{padding:16px}.skeleton-item{display:flex;align-items:center;gap:10px;padding:10px 0;border-bottom:1px solid var(--border)}.skeleton-icon{width:20px;height:20px;border-radius:4px;background:linear-gradient(90deg,#f0efed 25%,#e6e4e1,#f0efed 75%);background-size:200px 100%;animation:shimmer 1.5s infinite}.skeleton-text{flex:1;height:14px;border-radius:4px;background:linear-gradient(90deg,#f0efed 25%,#e6e4e1,#f0efed 75%);background-size:200px 100%;animation:shimmer 1.5s infinite}.skeleton-badge{width:32px;height:18px;border-radius:9px;background:linear-gradient(90deg,#f0efed 25%,#e6e4e1,#f0efed 75%);background-size:200px 100%;animation:shimmer 1.5s infinite}@keyframes shimmer{0%{background-position:-200px 0}to{background-position:200px 0}}.empty-state-enhanced{text-align:center;padding:60px 30px;color:var(--text-secondary)}.empty-icon{font-size:48px;margin-bottom:16px;opacity:.6}.empty-title{font-size:18px;font-weight:600;color:var(--text-primary);margin-bottom:8px}.empty-description{font-size:14px;margin-bottom:20px;max-width:320px;margin-left:auto;margin-right:auto}.empty-action .btn{display:inline-flex;align-items:center;gap:8px}.table-card,.sidebar-table-item{transition:transform .15s ease,box-shadow .15s ease}.graph-view,.list-view{animation:viewFadeIn .3s ease}@keyframes viewFadeIn{0%{opacity:0}to{opacity:1}}.toolbar-btn:active,.zoom-btn:active,.graph-btn:active{transform:scale(.95)}.toolbar-btn:focus-visible,.zoom-btn:focus-visible,.btn:focus-visible{outline:2px solid var(--accent-interactive);outline-offset:2px}@media(min-width:1600px){.enhanced-sidebar{width:300px}.graph-view .sidebar-container .code-panel{width:420px!important}.sidebar-table-list{max-height:600px}}@media(min-width:1920px){.enhanced-sidebar{width:320px}.graph-view .sidebar-container .code-panel{width:480px!important}}@media(max-width:1200px){.enhanced-sidebar{width:220px}.code-panel{width:300px!important}}@media(max-width:900px){.toolbar-btn span{display:none}.toolbar-btn,.toolbar-btn.icon-only{padding:6px}.zoom-display{min-width:40px;font-size:11px}.enhanced-sidebar{width:180px}.sidebar-deployment{padding:10px 12px}.deployment-label{font-size:12px}}@media print{.toolbar,.enhanced-sidebar,.sidebar-container,.zoom-panel,.graph-legend{display:none!important}.graph-panel{width:100%!important}}.shortcuts-modal{position:fixed;top:0;left:0;right:0;bottom:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000}.shortcuts-modal-content{background:var(--bg-primary);border-radius:12px;box-shadow:var(--shadow-lg);max-width:480px;width:90%;max-height:80vh;overflow:hidden}.shortcuts-modal-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid var(--border)}.shortcuts-modal-header h3{margin:0;font-size:16px;font-weight:600;color:var(--text-primary)}.shortcuts-close{background:none;border:none;font-size:24px;cursor:pointer;color:var(--text-secondary);padding:0;line-height:1}.shortcuts-close:hover{color:var(--text-primary)}.shortcuts-modal-body{padding:20px;overflow-y:auto;max-height:calc(80vh - 60px)}.shortcuts-section{margin-bottom:20px}.shortcuts-section:last-child{margin-bottom:0}.shortcuts-section h4{font-size:12px;font-weight:600;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.5px;margin:0 0 12px}.shortcut-item{display:flex;align-items:center;gap:8px;padding:8px 0;font-size:13px;color:var(--text-primary)}.shortcut-item kbd{display:inline-flex;align-items:center;justify-content:center;min-width:24px;height:24px;padding:0 8px;background:var(--bg-tertiary);border:1px solid var(--border);border-radius:4px;font-family:var(--font-mono);font-size:11px;color:var(--text-primary)}*{box-sizing:border-box;margin:0;padding:0}html,body{width:100%;height:100%;overflow-x:hidden}:root{--bg-primary: #faf8f5;--bg-secondary: #f5f3f0;--bg-hover: #ebe9e6;--text-primary: #1a1a1a;--text-secondary: #6b6b6b;--border: #e6e4e1;--accent: #8b7355;--accent-interactive: #EB5601;--accent-hover: #d14a01;--success: #4a8c5c;--warning: #c4842d;--info: #4a7c9b;--font-mono: "SF Mono", Monaco, "Cascadia Code", "Courier New", monospace;--chart-gradient-start: #EB5601;--chart-gradient-end: #d14a01;--chart-area-opacity: .1}[data-theme=dark]{--bg-primary: #1e1e1e;--bg-secondary: #252526;--bg-hover: #37373d;--text-primary: #cccccc;--text-secondary: #8b8b8b;--border: #3c3c3c;--accent: #c9a87c;--accent-interactive: #ff6b35;--accent-hover: #ff8555;--success: #4ec9b0;--warning: #dcdcaa;--info: #4fc1ff;--chart-gradient-start: #ff6b35;--chart-gradient-end: #ff8555;--chart-area-opacity: .15}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,sans-serif;background:var(--bg-primary);color:var(--text-primary);min-height:100vh;padding:16px 20px;margin:0}.header{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}.header h1{font-size:20px;display:flex;align-items:center;gap:10px;color:var(--text-primary)}.header-right{display:flex;align-items:center;gap:16px}.deployment-url{font-size:12px;color:var(--text-secondary);font-family:var(--font-mono);margin-right:16px}.last-update{color:var(--text-secondary);font-size:12px}.theme-toggle-btn{background:var(--bg-hover);border:1px solid var(--border);color:var(--text-primary);width:32px;height:32px;border-radius:6px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:16px;transition:all .2s ease}.theme-toggle-btn:hover{background:var(--accent-interactive);color:#fff;border-color:var(--accent-interactive)}.chart-subtitle{font-size:12px;color:var(--text-secondary)}.pie-legend{padding:20px}.pie-item{display:flex;align-items:center;gap:8px;padding:8px 0;border-bottom:1px solid var(--border)}.pie-item:last-child{border-bottom:none}.pie-color{width:12px;height:12px;border-radius:2px}.pie-label{flex:1;font-size:13px}.pie-value{font-family:var(--font-mono);font-size:12px;color:var(--text-secondary)}.status-dot{width:8px;height:8px;border-radius:50%;background:var(--success);animation:pulse 2s infinite}.status-dot.disconnected{background:var(--accent-interactive);animation:none}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.metrics-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;margin-bottom:24px}.metric-card{background:var(--bg-secondary);border:1px solid var(--border);border-radius:12px;padding:20px;transition:transform .2s,box-shadow .2s}.metric-card:hover{transform:translateY(-2px);box-shadow:0 4px 12px #00000014}.metric-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:12px}.metric-label{font-size:12px;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.5px;font-weight:600}.metric-icon{width:32px;height:32px;background:var(--bg-hover);border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:16px;color:var(--accent)}.metric-value{font-size:36px;font-weight:700;margin-bottom:8px;font-family:var(--font-mono);color:var(--text-primary)}.metric-change{font-size:13px;display:flex;align-items:center;gap:4px}.metric-change.positive{color:var(--success)}.metric-change.negative{color:var(--accent-interactive)}.metric-change.neutral{color:var(--text-secondary)}.metric-source{font-size:11px;color:var(--text-secondary);margin-top:8px;font-family:var(--font-mono)}.charts-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(400px,1fr));gap:16px}@media(max-width:900px){.charts-grid{grid-template-columns:1fr}}.chart-card{background:var(--bg-secondary);border:1px solid var(--border);border-radius:12px;padding:20px}.chart-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.chart-title{font-size:14px;font-weight:600;color:var(--text-primary)}.chart-actions{display:flex;gap:8px}.chart-btn{background:var(--bg-hover);border:1px solid var(--border);color:var(--text-secondary);padding:4px 8px;border-radius:4px;font-size:11px;cursor:pointer;transition:all .2s}.chart-btn:hover{color:var(--text-primary);border-color:var(--accent)}.chart-btn.active{background:var(--accent-interactive);color:#fff;border-color:var(--accent-interactive)}.chart-container{height:220px;background:var(--bg-hover);border-radius:8px;display:flex;align-items:center;justify-content:center;position:relative;overflow:hidden}.bar-chart{display:flex;align-items:flex-end;justify-content:space-around;height:100%;width:100%;padding:20px;gap:8px}.bar{flex:1;max-width:40px;background:linear-gradient(to top,var(--accent-interactive),var(--accent-hover));border-radius:4px 4px 0 0;transition:height .3s ease;position:relative;min-height:4px}.bar:after{content:attr(data-value);position:absolute;top:-20px;left:50%;transform:translate(-50%);font-size:11px;color:var(--text-secondary);font-family:var(--font-mono)}.bar-label{position:absolute;bottom:-20px;left:50%;transform:translate(-50%);font-size:10px;color:var(--text-secondary);white-space:nowrap}.line-chart{width:100%;height:100%;padding:20px}.line-chart svg{width:100%;height:100%}.line-chart path{fill:none;stroke:var(--accent-interactive);stroke-width:2}.line-chart .area{fill:var(--accent-interactive);opacity:.1}.table-chart{width:100%;height:100%;overflow:auto;padding:0}.table-chart table{width:100%;border-collapse:collapse;font-size:12px}.table-chart th,.table-chart td{padding:10px 12px;text-align:left;border-bottom:1px solid var(--border)}.table-chart th{background:var(--bg-secondary);font-weight:600;position:sticky;top:0;color:var(--text-primary)}.table-chart tr:hover td{background:#eb56010d}.empty-state{text-align:center;color:var(--text-secondary);padding:40px}.empty-state h3{color:var(--text-primary);margin-bottom:8px;font-size:16px}.empty-state p{font-size:13px}.loading{display:flex;align-items:center;justify-content:center;gap:12px;color:var(--text-secondary)}.spinner{width:20px;height:20px;border:2px solid var(--border);border-top-color:var(--accent-interactive);border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}@media(max-width:600px){body{padding:12px}.header{flex-direction:column;align-items:flex-start;gap:12px}.metrics-grid{grid-template-columns:1fr}.metric-value{font-size:28px}}
|
package/package.json
CHANGED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
var h=Object.defineProperty;var m=(o,e,t)=>e in o?h(o,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):o[e]=t;var d=(o,e,t)=>m(o,typeof e!="symbol"?e+"":e,t);import"./modulepreload-polyfill-B5Qt9EMX.js";class v{constructor(){d(this,"config",null);d(this,"refreshTimer",null);d(this,"isConnected",!0);d(this,"currentTheme","light");this.init()}init(){var e;if(this.initTheme(),window.__CONVEX_CONFIG__)this.config=window.__CONVEX_CONFIG__;else{const s=new URLSearchParams(window.location.search).get("config");if(s)try{this.config=JSON.parse(decodeURIComponent(s))}catch(r){console.error("Failed to parse config:",r)}}this.isConnected=!!((e=this.config)!=null&&e.deploymentUrl),this.render(),this.startAutoRefresh()}initTheme(){const e=localStorage.getItem("convex-dashboard-theme");e?this.currentTheme=e:window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches&&(this.currentTheme="light"),this.applyTheme()}applyTheme(){document.documentElement.setAttribute("data-theme",this.currentTheme),localStorage.setItem("convex-dashboard-theme",this.currentTheme)}toggleTheme(){this.currentTheme=this.currentTheme==="light"?"dark":"light",this.applyTheme();const e=document.getElementById("themeIcon");e&&(e.innerHTML=this.currentTheme==="light"?'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>':'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>')}render(){var r;const e=document.getElementById("app");if(!e)return;const t=((r=this.config)==null?void 0:r.deploymentUrl)||"Not connected";e.innerHTML=`
|
|
2
|
-
<div class="header">
|
|
3
|
-
<h1>
|
|
4
|
-
<span class="status-dot ${this.isConnected?"":"disconnected"}" id="statusDot"></span>
|
|
5
|
-
Realtime Dashboard
|
|
6
|
-
</h1>
|
|
7
|
-
<div class="header-right">
|
|
8
|
-
<span class="deployment-url">${t}</span>
|
|
9
|
-
<span class="last-update" id="lastUpdate"></span>
|
|
10
|
-
<button class="theme-toggle-btn" id="themeToggle" title="Toggle dark mode">
|
|
11
|
-
<span id="themeIcon">${this.currentTheme==="light"?'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>':'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>'}</span>
|
|
12
|
-
</button>
|
|
13
|
-
</div>
|
|
14
|
-
</div>
|
|
15
|
-
|
|
16
|
-
<div class="metrics-grid" id="metricsGrid"></div>
|
|
17
|
-
<div class="charts-grid" id="chartsGrid"></div>
|
|
18
|
-
`;const s=document.getElementById("themeToggle");s&&s.addEventListener("click",()=>this.toggleTheme()),this.renderMetrics(),this.renderCharts(),this.updateTimestamp()}renderMetrics(){var s;const e=document.getElementById("metricsGrid");if(!e)return;const t=((s=this.config)==null?void 0:s.metrics)||[];if(t.length===0){e.innerHTML=`
|
|
19
|
-
<div class="metric-card">
|
|
20
|
-
<div class="metric-header">
|
|
21
|
-
<span class="metric-label">No Metrics Configured</span>
|
|
22
|
-
</div>
|
|
23
|
-
<div class="metric-value">--</div>
|
|
24
|
-
<div class="metric-change neutral">Add metrics via MCP tool parameters</div>
|
|
25
|
-
</div>
|
|
26
|
-
`;return}e.innerHTML=t.map(r=>`
|
|
27
|
-
<div class="metric-card">
|
|
28
|
-
<div class="metric-header">
|
|
29
|
-
<span class="metric-label">${r.name}</span>
|
|
30
|
-
<span class="metric-icon">${this.getMetricIcon(r.aggregation)}</span>
|
|
31
|
-
</div>
|
|
32
|
-
<div class="metric-value">${this.formatNumber(r.value||0)}</div>
|
|
33
|
-
<div class="metric-change neutral">
|
|
34
|
-
${r.documentCount!==void 0?`${r.documentCount} documents`:""}
|
|
35
|
-
</div>
|
|
36
|
-
<div class="metric-source">${r.table} / ${r.aggregation}${r.field?`(${r.field})`:""}</div>
|
|
37
|
-
</div>
|
|
38
|
-
`).join("")}getMetricIcon(e){return{count:"#",sum:"Σ",avg:"x̄",min:"↓",max:"↑"}[e]||"#"}formatNumber(e){return e>=1e6?(e/1e6).toFixed(1)+"M":e>=1e3?(e/1e3).toFixed(1)+"k":Number.isInteger(e)?e.toLocaleString():e.toFixed(2)}renderCharts(){var i,a;const e=document.getElementById("chartsGrid");if(!e)return;const t=((i=this.config)==null?void 0:i.charts)||[],s=((a=this.config)==null?void 0:a.allDocuments)||{},r=this.renderActivityTable(s);if(t.length===0){e.innerHTML=`
|
|
39
|
-
${this.renderTablesOverview()}
|
|
40
|
-
${r}
|
|
41
|
-
`;return}e.innerHTML=t.map((n,c)=>{const l=s[n.table]||[];return`
|
|
42
|
-
<div class="chart-card">
|
|
43
|
-
<div class="chart-header">
|
|
44
|
-
<span class="chart-title">${n.title}</span>
|
|
45
|
-
<span class="chart-subtitle">${l.length} documents</span>
|
|
46
|
-
</div>
|
|
47
|
-
<div class="chart-container" id="chart-${c}">
|
|
48
|
-
${this.renderChartContent(n,l)}
|
|
49
|
-
</div>
|
|
50
|
-
</div>
|
|
51
|
-
`}).join("")+r}renderTablesOverview(){var s;const e=((s=this.config)==null?void 0:s.tables)||[];if(e.length===0)return"";const t=Math.max(...e.map(r=>r.documentCount),1);return`
|
|
52
|
-
<div class="chart-card">
|
|
53
|
-
<div class="chart-header">
|
|
54
|
-
<span class="chart-title">Tables Overview</span>
|
|
55
|
-
</div>
|
|
56
|
-
<div class="chart-container">
|
|
57
|
-
<div class="bar-chart">
|
|
58
|
-
${e.map(r=>`
|
|
59
|
-
<div class="bar" style="height: ${r.documentCount/t*100}%" data-value="${r.documentCount}">
|
|
60
|
-
<span class="bar-label">${r.name}</span>
|
|
61
|
-
</div>
|
|
62
|
-
`).join("")}
|
|
63
|
-
</div>
|
|
64
|
-
</div>
|
|
65
|
-
</div>
|
|
66
|
-
`}renderChartContent(e,t){switch(e.type){case"bar":return this.renderBarChart(t,e);case"line":return this.renderLineChart(t,e);case"pie":return this.renderPieChart(t,e);case"table":return this.renderDataTable(t);default:return`<div class="empty-state">Unknown chart type: ${e.type}</div>`}}renderBarChart(e,t){if(e.length===0)return'<div class="empty-state">No data available</div>';let s=[];if(t.groupBy){const i=new Map;for(const a of e){const n=String(a[t.groupBy]||"Unknown");i.set(n,(i.get(n)||0)+1)}s=Array.from(i.entries()).map(([a,n])=>({label:a,value:n})).sort((a,n)=>n.value-a.value).slice(0,7)}else{const i=new Map;for(const a of e){const n=new Date(a._creationTime).toLocaleDateString();i.set(n,(i.get(n)||0)+1)}s=Array.from(i.entries()).map(([a,n])=>({label:a,value:n})).slice(-7)}const r=Math.max(...s.map(i=>i.value),1);return`
|
|
67
|
-
<div class="bar-chart">
|
|
68
|
-
${s.map(i=>`
|
|
69
|
-
<div class="bar" style="height: ${i.value/r*100}%" data-value="${i.value}">
|
|
70
|
-
<span class="bar-label">${i.label.slice(0,8)}</span>
|
|
71
|
-
</div>
|
|
72
|
-
`).join("")}
|
|
73
|
-
</div>
|
|
74
|
-
`}renderLineChart(e,t){return e.length===0?'<div class="empty-state">No data available</div>':this.renderBarChart(e,t)}renderPieChart(e,t){if(e.length===0||!t.groupBy)return'<div class="empty-state">No data or groupBy field specified</div>';const s=new Map;for(const n of e){const c=String(n[t.groupBy]||"Unknown");s.set(c,(s.get(c)||0)+1)}const r=e.length,i=Array.from(s.entries()).map(([n,c])=>({label:n,count:c,percent:(c/r*100).toFixed(1)})).sort((n,c)=>c.count-n.count).slice(0,5),a=["#e94560","#0f3460","#16213e","#1a1a2e","#4caf50"];return`
|
|
75
|
-
<div class="pie-legend">
|
|
76
|
-
${i.map((n,c)=>`
|
|
77
|
-
<div class="pie-item">
|
|
78
|
-
<span class="pie-color" style="background: ${a[c%a.length]}"></span>
|
|
79
|
-
<span class="pie-label">${n.label}</span>
|
|
80
|
-
<span class="pie-value">${n.percent}%</span>
|
|
81
|
-
</div>
|
|
82
|
-
`).join("")}
|
|
83
|
-
</div>
|
|
84
|
-
`}renderDataTable(e){const t=e.slice(0,5);if(t.length===0)return'<div class="empty-state">No documents</div>';const s=Object.keys(t[0]).slice(0,4);return`
|
|
85
|
-
<div class="table-chart">
|
|
86
|
-
<table>
|
|
87
|
-
<thead>
|
|
88
|
-
<tr>
|
|
89
|
-
${s.map(r=>`<th>${r}</th>`).join("")}
|
|
90
|
-
</tr>
|
|
91
|
-
</thead>
|
|
92
|
-
<tbody>
|
|
93
|
-
${t.map(r=>`
|
|
94
|
-
<tr>
|
|
95
|
-
${s.map(i=>`<td>${this.formatValue(r[i])}</td>`).join("")}
|
|
96
|
-
</tr>
|
|
97
|
-
`).join("")}
|
|
98
|
-
</tbody>
|
|
99
|
-
</table>
|
|
100
|
-
</div>
|
|
101
|
-
`}renderActivityTable(e){const t=Object.entries(e).flatMap(([s,r])=>r.map(i=>({table:s,...i}))).sort((s,r)=>{const i=s._creationTime||0;return(r._creationTime||0)-i}).slice(0,10);return t.length===0?`
|
|
102
|
-
<div class="chart-card">
|
|
103
|
-
<div class="chart-header">
|
|
104
|
-
<span class="chart-title">Recent Activity</span>
|
|
105
|
-
</div>
|
|
106
|
-
<div class="chart-container">
|
|
107
|
-
<div class="empty-state">No documents found</div>
|
|
108
|
-
</div>
|
|
109
|
-
</div>
|
|
110
|
-
`:`
|
|
111
|
-
<div class="chart-card">
|
|
112
|
-
<div class="chart-header">
|
|
113
|
-
<span class="chart-title">Recent Activity</span>
|
|
114
|
-
</div>
|
|
115
|
-
<div class="chart-container">
|
|
116
|
-
<div class="table-chart">
|
|
117
|
-
<table>
|
|
118
|
-
<thead>
|
|
119
|
-
<tr>
|
|
120
|
-
<th>Table</th>
|
|
121
|
-
<th>ID</th>
|
|
122
|
-
<th>Created</th>
|
|
123
|
-
</tr>
|
|
124
|
-
</thead>
|
|
125
|
-
<tbody>
|
|
126
|
-
${t.map(s=>`
|
|
127
|
-
<tr>
|
|
128
|
-
<td>${s.table}</td>
|
|
129
|
-
<td style="font-family: var(--font-mono); color: var(--accent);">
|
|
130
|
-
${String(s._id||"").slice(0,12)}...
|
|
131
|
-
</td>
|
|
132
|
-
<td>${this.formatTimeAgo(s._creationTime)}</td>
|
|
133
|
-
</tr>
|
|
134
|
-
`).join("")}
|
|
135
|
-
</tbody>
|
|
136
|
-
</table>
|
|
137
|
-
</div>
|
|
138
|
-
</div>
|
|
139
|
-
</div>
|
|
140
|
-
`}formatValue(e){if(e==null)return'<span style="color: var(--text-secondary)">null</span>';if(typeof e=="object"){const t=JSON.stringify(e);return t.length>30?t.slice(0,30)+"...":t}return typeof e=="string"&&e.length>30?e.slice(0,30)+"...":String(e)}formatTimeAgo(e){if(!e)return"Unknown";const s=Date.now()-e,r=Math.floor(s/6e4),i=Math.floor(s/36e5),a=Math.floor(s/864e5);return r<1?"Just now":r<60?`${r}m ago`:i<24?`${i}h ago`:`${a}d ago`}updateTimestamp(){const e=document.getElementById("lastUpdate");e&&(e.textContent="Updated: "+new Date().toLocaleTimeString())}startAutoRefresh(){var t;const e=(((t=this.config)==null?void 0:t.refreshInterval)||5)*1e3;this.refreshTimer=window.setInterval(()=>{this.updateTimestamp()},e)}stopAutoRefresh(){this.refreshTimer&&(clearInterval(this.refreshTimer),this.refreshTimer=null)}destroy(){this.stopAutoRefresh()}}const p=new v;window.addEventListener("beforeunload",()=>{p.destroy()});
|