@sava-info-systems/api-maker-with-extensions 2.3.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. package/.dockerignore +5 -1
  2. package/Dockerfile +1 -35
  3. package/Dockerfile.base +22 -0
  4. package/Dockerfile.extended-base +44 -0
  5. package/dist/1025.5c34b11ab88519fd.js +1 -0
  6. package/dist/1097.3c5b2b4587cefa6e.js +1 -0
  7. package/dist/115.8d10b2b963e7d632.js +1 -0
  8. package/dist/148.f8f560a5f818328e.js +1 -0
  9. package/dist/1558.576af7585833e289.js +1 -0
  10. package/dist/1678.14f917ee37080d63.js +1 -0
  11. package/dist/1754.fc6d6e9f6c1e96d5.js +1 -0
  12. package/dist/1928.9c9375dd036b025a.js +1 -0
  13. package/dist/1957.b718aa368330b524.js +1 -0
  14. package/dist/2140.bb097d69bcb6a1e5.js +1 -0
  15. package/dist/2295.aa3514c8d1238cb2.js +1 -0
  16. package/dist/238.553f30dbe5124419.js +1 -0
  17. package/dist/2473.260a6b26a70eaa8d.js +1 -0
  18. package/dist/2579.b02fe6a8ba3a2e71.js +1 -0
  19. package/dist/2714.b41d14f7378785df.js +1 -0
  20. package/dist/2735.4046cae4c68ac75f.js +1 -0
  21. package/dist/292.bb85e0ea91dcc118.js +1 -0
  22. package/dist/3139.5dd9289cc24b4100.js +1 -0
  23. package/dist/3450.588aafa515b19133.js +1 -0
  24. package/dist/3664.ed1232e3c70195fc.js +1 -0
  25. package/dist/3752.d4e0a280a37c1426.js +1 -0
  26. package/dist/3rdpartylicenses.txt +1 -1
  27. package/dist/4110.b9ebfd9a7382c450.js +1 -0
  28. package/dist/4186.2fd0537d5bc6d882.js +1 -0
  29. package/dist/4211.b6b9f495b100e985.js +1 -0
  30. package/dist/4575.1fee055d10b57afc.js +1 -0
  31. package/dist/4842.ccf765ed3e9753a7.js +1 -0
  32. package/dist/485.7870367c9475fed8.js +1 -0
  33. package/dist/{4895.a54759704d1994b1.js → 4895.fa7b24d66157c531.js} +1 -1
  34. package/dist/5334.e0a2450fc295f659.js +1 -0
  35. package/dist/5421.2c2c0fbf926792db.js +1 -0
  36. package/dist/5434.0997994b319a866a.js +1 -0
  37. package/dist/5679.e362a3e10cb1b953.js +1 -0
  38. package/dist/5761.6c20b09245ac31ef.js +1 -0
  39. package/dist/5765.8d761aef1708e8a1.js +1 -0
  40. package/dist/6236.969b45cad747277c.js +1 -0
  41. package/dist/6393.1b7b097f17d75290.js +1 -0
  42. package/dist/6401.f18e3c98d6deaa22.js +1 -0
  43. package/dist/6650.05ea40acd68697c3.js +1 -0
  44. package/dist/7368.85d940d08fdca6e2.js +1 -0
  45. package/dist/7436.4a62f7ef13cffb21.js +1 -0
  46. package/dist/7470.d24359dc7a3ef75a.js +1 -0
  47. package/dist/7649.8e5ff8a951b474e0.js +1 -0
  48. package/dist/7975.d3d7064d527ac017.js +1 -0
  49. package/dist/7977.85b17bdb465e9a90.js +1 -0
  50. package/dist/7987.c1637bec96a9cc03.js +1 -0
  51. package/dist/8125.8390bc1b477ec6b9.js +1 -0
  52. package/dist/8266.0bf85311cf40ddff.js +1 -0
  53. package/dist/8296.fe54c3787977fad3.js +1 -0
  54. package/dist/885.7e1ec0f82adbdd6f.js +1 -0
  55. package/dist/891.fee03277c7f1c29d.js +1 -0
  56. package/dist/{913.4580b4ce4307a40f.js → 913.e49eb351997bc25c.js} +1 -1
  57. package/dist/9243.eaa2c312b42fc4b3.js +1 -0
  58. package/dist/9575.8404e98d640fd9f9.js +1 -0
  59. package/dist/9939.537c5e355804804b.js +1 -0
  60. package/dist/assets/images/icons/api-group-light.svg +11 -0
  61. package/dist/assets/images/icons/api-group.svg +5 -5
  62. package/dist/assets/images/icons/api-maker-deployment-light.svg +5 -0
  63. package/dist/assets/images/icons/api-maker-deployment.svg +3 -3
  64. package/dist/assets/images/icons/api-user-light.svg +12 -0
  65. package/dist/assets/images/icons/api-user.svg +2 -2
  66. package/dist/assets/images/icons/comparator-light.svg +61 -0
  67. package/dist/assets/images/icons/comparator.svg +2 -2
  68. package/dist/assets/images/icons/custom-light.svg +6 -0
  69. package/dist/assets/images/icons/custom.svg +5 -5
  70. package/dist/assets/images/icons/database-light.svg +29 -0
  71. package/dist/assets/images/icons/database.svg +6 -6
  72. package/dist/assets/images/icons/db-m-light.svg +10 -0
  73. package/dist/assets/images/icons/db-m.svg +4 -4
  74. package/dist/assets/images/icons/db-token-generator-light.svg +4 -0
  75. package/dist/assets/images/icons/db-token-generator.svg +4 -0
  76. package/dist/assets/images/icons/dev-light.svg +13 -0
  77. package/dist/assets/images/icons/dev.svg +4 -4
  78. package/dist/assets/images/icons/diagram-light.svg +28 -0
  79. package/dist/assets/images/icons/diagram.svg +9 -9
  80. package/dist/assets/images/icons/event-light.svg +16 -0
  81. package/dist/assets/images/icons/event.svg +4 -4
  82. package/dist/assets/images/icons/film-movies-icon.svg +3 -3
  83. package/dist/assets/images/icons/finder-light.svg +14 -0
  84. package/dist/assets/images/icons/finder.svg +3 -3
  85. package/dist/assets/images/icons/i18n-light.svg +11 -0
  86. package/dist/assets/images/icons/i18n.svg +6 -6
  87. package/dist/assets/images/icons/increment-light.svg +5 -0
  88. package/dist/assets/images/icons/increment.svg +2 -2
  89. package/dist/assets/images/icons/license-light.svg +6 -0
  90. package/dist/assets/images/icons/license.svg +4 -4
  91. package/dist/assets/images/icons/log-light.svg +17 -0
  92. package/dist/assets/images/icons/log-profile-light.svg +23 -0
  93. package/dist/assets/images/icons/log-profile.svg +7 -7
  94. package/dist/assets/images/icons/log.svg +6 -6
  95. package/dist/assets/images/icons/masters.svg +3 -3
  96. package/dist/assets/images/icons/native-process-logs-light.svg +13 -0
  97. package/dist/assets/images/icons/native-process-logs.svg +6 -6
  98. package/dist/assets/images/icons/nodes-light.svg +5 -0
  99. package/dist/assets/images/icons/nodes.svg +2 -2
  100. package/dist/assets/images/icons/notes-light.svg +16 -0
  101. package/dist/assets/images/icons/notes.svg +2 -2
  102. package/dist/assets/images/icons/process_initializers-light.svg +20 -0
  103. package/dist/assets/images/icons/process_initializers.svg +7 -7
  104. package/dist/assets/images/icons/rsa_encryption-light.svg +6 -0
  105. package/dist/assets/images/icons/rsa_encryption.svg +2 -2
  106. package/dist/assets/images/icons/sandbox-light.svg +11 -0
  107. package/dist/assets/images/icons/sandbox.svg +2 -2
  108. package/dist/assets/images/icons/scanning-period-light.svg +13 -0
  109. package/dist/assets/images/icons/scanning-period.svg +4 -4
  110. package/dist/assets/images/icons/schedulers-light.svg +8 -0
  111. package/dist/assets/images/icons/schedulers.svg +5 -5
  112. package/dist/assets/images/icons/secret-light.svg +15 -0
  113. package/dist/assets/images/icons/secret.svg +2 -2
  114. package/dist/assets/images/icons/swagger-logo-light.svg +9 -0
  115. package/dist/assets/images/icons/swagger-logo.svg +2 -2
  116. package/dist/assets/images/icons/systems-light.svg +8 -0
  117. package/dist/assets/images/icons/systems.svg +3 -3
  118. package/dist/assets/images/icons/testing-light.svg +12 -0
  119. package/dist/assets/images/icons/testing.svg +6 -6
  120. package/dist/assets/images/icons/third-party-light.svg +19 -0
  121. package/dist/assets/images/icons/third-party.svg +4 -4
  122. package/dist/assets/images/icons/typescript-light.svg +12 -0
  123. package/dist/assets/images/icons/typescript.svg +2 -2
  124. package/dist/assets/images/icons/ui-maker-light.svg +23 -0
  125. package/dist/assets/images/icons/ui-maker.svg +10 -10
  126. package/dist/assets/images/icons/utility-light.svg +19 -0
  127. package/dist/assets/images/icons/utility.svg +2 -2
  128. package/dist/assets/images/icons/vulnerability-light.svg +20 -0
  129. package/dist/assets/images/icons/vulnerability.svg +5 -5
  130. package/dist/assets/images/icons/web-light.svg +16 -0
  131. package/dist/assets/images/icons/web.svg +3 -3
  132. package/dist/assets/images/logo/api_maker_logo_dark.svg +29 -0
  133. package/dist/assets/images/logo/api_maker_logo_full_white.svg +29 -0
  134. package/dist/assets/images/logo/api_maker_logo_light.svg +22 -0
  135. package/dist/assets/images/logo/apple-touch-icon.png +0 -0
  136. package/dist/assets/images/logo/favicon-192x192.png +0 -0
  137. package/dist/assets/images/logo/favicon-32x32.png +0 -0
  138. package/dist/assets/images/logo/favicon-512x512.png +0 -0
  139. package/dist/assets/images/logo/logo_docs-light.svg +15 -0
  140. package/dist/assets/images/logo/logo_docs.svg +3 -3
  141. package/dist/assets/images/logo/logo_store-light.svg +17 -0
  142. package/dist/assets/images/logo/logo_store.svg +3 -3
  143. package/dist/assets/images/logo/store_logo-dark.svg +11 -0
  144. package/dist/assets/images/logo/store_logo-light.svg +11 -0
  145. package/dist/assets/images/logo/store_logo.svg +2 -2
  146. package/dist/assets/primeng-theme/primeng-theme-overrides.scss +1426 -31
  147. package/dist/assets/public/diagram-viewer/index.html +59 -20
  148. package/dist/assets/public/diagram-viewer/index.js +1 -1
  149. package/dist/assets/public/monaco-editor/index.html +105 -9
  150. package/dist/assets/public/monaco-editor/index.js +1 -1
  151. package/dist/assets/public/table-viewer/index.html +124 -38
  152. package/dist/assets/schema-types/InterfaceProps.ts +71 -13
  153. package/dist/assets/schema-types/store-types.ts +2 -0
  154. package/dist/assets/schema-types/types.ts +65 -17
  155. package/dist/assets/styles/bootstrap-overrides.scss +170 -7
  156. package/dist/assets/styles/colors-dark.scss +320 -33
  157. package/dist/assets/styles/colors-light.scss +453 -9
  158. package/dist/assets/styles/out/styles.css.map +1 -1
  159. package/dist/assets/styles/styles.scss +213 -24
  160. package/dist/assets/styles/variables.scss +2 -2
  161. package/dist/bootstrap-overrides.css +1 -0
  162. package/dist/colors-dark.css +1 -0
  163. package/dist/colors-light.css +1 -0
  164. package/dist/{common.912ad7a22e5de63a.js → common.86c514055f788290.js} +1 -1
  165. package/dist/index.html +111 -27
  166. package/dist/main.0ac98a72f1b9c1dd.js +1 -0
  167. package/dist/runtime.0e9fc303007ced09.js +1 -0
  168. package/dist/styles-global.css +1 -0
  169. package/dist/{styles.242217e145c3f7ab.css → styles.c1032bc4f4d7829b.css} +1 -1
  170. package/main.js +1 -1
  171. package/package-sandbox.json +1 -1
  172. package/package.json +9 -9
  173. package/pnpm-workspace.yaml +15 -0
  174. package/sandbox.js +1 -1
  175. package/src/assets/schema-types/store-types.ts +2 -0
  176. package/src/assets/schema-types/types.ts +65 -17
  177. package/src/schema-types/store-types.js +2 -0
  178. package/src/schema-types/types.js +1 -0
  179. package/src/tools/.github/copilot-instructions.md +17 -9
  180. package/src/tools/local-client.js +1 -1
  181. package/dist/1025.a0db9347453600d0.js +0 -1
  182. package/dist/1097.d0d6fd13031aa1f0.js +0 -1
  183. package/dist/148.1dc7d9e0cfe45510.js +0 -1
  184. package/dist/1558.0aae3e5c538d3815.js +0 -1
  185. package/dist/1678.ed1e4a269e0f42bc.js +0 -1
  186. package/dist/1754.691f142aa1eabe28.js +0 -1
  187. package/dist/1928.2b376f7cf66ed038.js +0 -1
  188. package/dist/1957.ab7fda21d9978597.js +0 -1
  189. package/dist/2140.1ce150285aebe3cc.js +0 -1
  190. package/dist/2295.4fa1d4b5ff482f3a.js +0 -1
  191. package/dist/238.891233aa670bdd2b.js +0 -1
  192. package/dist/2473.4b2bb1ad5b1cc99d.js +0 -1
  193. package/dist/2579.6558b67b2d98f116.js +0 -1
  194. package/dist/2714.0d6939336a02dd0a.js +0 -1
  195. package/dist/2735.86437a8639a0c1bb.js +0 -1
  196. package/dist/292.f6482cd1a572ba4b.js +0 -1
  197. package/dist/3139.0921102d181c220d.js +0 -1
  198. package/dist/3450.9b5cfc83039b02b5.js +0 -1
  199. package/dist/3664.4a1dd7f16f8b57fb.js +0 -1
  200. package/dist/4110.d108e786ecbdfd1b.js +0 -1
  201. package/dist/4186.ac3e8af0efeeac14.js +0 -1
  202. package/dist/4211.1bfe72ce0b7b2f76.js +0 -1
  203. package/dist/443.7e3946651373e09a.js +0 -1
  204. package/dist/4575.169008cbbbae66bb.js +0 -1
  205. package/dist/4842.e7649d81606ad80d.js +0 -1
  206. package/dist/485.b546656b466cb8a4.js +0 -1
  207. package/dist/5334.72c1c6d89fdf136e.js +0 -1
  208. package/dist/5421.2067001c10533303.js +0 -1
  209. package/dist/5679.4e22239895c7f520.js +0 -1
  210. package/dist/5761.331ca41ab64cca92.js +0 -1
  211. package/dist/5765.2b640be5d177d794.js +0 -1
  212. package/dist/6236.6c0fc469f0c96549.js +0 -1
  213. package/dist/6393.173ecdfc3b022973.js +0 -1
  214. package/dist/6401.374823002788afe7.js +0 -1
  215. package/dist/6650.1d18ee25901d68be.js +0 -1
  216. package/dist/7368.9ebf62d518f8ad7a.js +0 -1
  217. package/dist/7436.640bf5fe48814cbe.js +0 -1
  218. package/dist/7470.b1ce7eedb9cedb2d.js +0 -1
  219. package/dist/7649.ab4744cfe713eec0.js +0 -1
  220. package/dist/7975.578a00ebd1160a1e.js +0 -1
  221. package/dist/7977.afeded970bac8b66.js +0 -1
  222. package/dist/7987.5958ad5b81873058.js +0 -1
  223. package/dist/8125.b2f908096882ed45.js +0 -1
  224. package/dist/8266.aca59a0a04590ab1.js +0 -1
  225. package/dist/8296.a387c28f5702c5e8.js +0 -1
  226. package/dist/885.be44d92efca1aaf6.js +0 -1
  227. package/dist/891.5e08c81e5200413e.js +0 -1
  228. package/dist/9243.08630ad94e1fddba.js +0 -1
  229. package/dist/9575.30ac91b65602c0ca.js +0 -1
  230. package/dist/9939.a3389cc6764e3cbc.js +0 -1
  231. package/dist/assets/images/logo/API Maker Logo 2021 black.svg +0 -47
  232. package/dist/assets/images/logo/API Maker Logo 2021.svg +0 -49
  233. package/dist/assets/images/logo/logo.svg +0 -41
  234. package/dist/assets/styles/out/bootstrap-overrides.css +0 -2
  235. package/dist/assets/styles/out/colors-dark.css +0 -2
  236. package/dist/assets/styles/out/colors-light.css +0 -2
  237. package/dist/assets/styles/out/monaco-editor-overrides.css +0 -2
  238. package/dist/assets/styles/out/styles.css +0 -2
  239. package/dist/assets/styles/out/variables.css +0 -2
  240. package/dist/favicon.ico +0 -0
  241. package/dist/main.2046fb4b0dd1b8e0.js +0 -1
  242. package/dist/runtime.a86d5cd27767506e.js +0 -1
@@ -4,6 +4,17 @@
4
4
  <meta charset="UTF-8"/>
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
6
  <title>Entity Diagram Viewer</title>
7
+ <script>
8
+ // Apply the app theme (shared localStorage, same origin) before render; live-switch via postMessage.
9
+ (function () {
10
+ function applyTheme(t) { document.documentElement.classList.toggle('light', t === 'LIGHT'); }
11
+ try { applyTheme(localStorage.getItem('THEME')); } catch (e) {}
12
+ window.addEventListener('message', function (ev) {
13
+ var m = ev.data;
14
+ if (m && m.type === 'SET_THEME') applyTheme(m.payload && m.payload.theme);
15
+ });
16
+ }());
17
+ </script>
7
18
  <link rel="preconnect" href="https://fonts.googleapis.com"/>
8
19
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
9
20
  <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=Inter:wght@400;500;600&display=swap" rel="stylesheet"/>
@@ -15,20 +26,48 @@
15
26
  }
16
27
 
17
28
  :root {
18
- --bg: #0d1117;
19
- --surface: #161b22;
20
- --surface2: #1c2128;
21
- --border: #30363d;
22
- --border2: #21262d;
23
- --text: #e6edf3;
24
- --text-muted: #8b949e;
25
- --text-dim: #484f58;
26
- --accent: #a8171e;
27
- --accent2: #a60911;
29
+ /* Brand navy palette — matches the Monaco editor (#0E1726) and the
30
+ table viewer (#141c29 / #22324a surfaces, brand-blue hairlines). */
31
+ --bg: #0e1726;
32
+ --surface: #141c29;
33
+ --surface2: #1b2636;
34
+ --border: rgba(97, 175, 239, 0.18);
35
+ --border2: rgba(97, 175, 239, 0.10);
36
+ --text: #e6ebf2;
37
+ --text-muted: #9aa6b5;
38
+ --text-dim: #6f7c8c;
39
+ --accent: #0C53A4;
40
+ --accent2: #00893F;
28
41
  --radius: 10px;
29
42
  --toolbar-h: 40px;
30
43
  }
31
44
 
45
+ /* LIGHT theme — applied when <html> has class "light" (from app THEME) */
46
+ html.light {
47
+ --bg: #eef2f7;
48
+ --surface: #ffffff;
49
+ --surface2: #f3f7fc;
50
+ --border: rgba(12, 83, 164, 0.18);
51
+ --border2: rgba(12, 83, 164, 0.10);
52
+ --text: #1b2330;
53
+ --text-muted: #4a5563;
54
+ --text-dim: #7a8694;
55
+ --accent: #0C53A4;
56
+ --accent2: #00893F;
57
+ }
58
+ /* light-mode overrides for elements with hardcoded dark backgrounds */
59
+ html.light #toolbar { background: rgba(255, 255, 255, 0.92); }
60
+ html.light #tooltip { background: rgba(255, 255, 255, 0.97); box-shadow: 0 8px 32px rgba(12, 30, 60, 0.18); color: #1b2330; }
61
+ /* Auto-Arrange (primary) button — brand blue gradient in both themes */
62
+ html.light .tb-btn.primary { background: linear-gradient(135deg, #0C53A4 0%, #00893F 100%); border-color: rgba(12, 83, 164, 0.4); color: #fff; }
63
+ html.light .tb-btn.primary:hover { background: linear-gradient(135deg, #0e5fbd 0%, #00a04a 100%); }
64
+ /* Export dropdown popup — light glass */
65
+ html.light .tb-dropdown-menu { background: rgba(255, 255, 255, 0.97); box-shadow: 0 8px 24px rgba(12, 30, 60, 0.16); }
66
+ html.light .tb-dropdown-item { color: #1b2330; }
67
+ html.light .tb-dropdown-item:hover { background: #eef3fa; }
68
+ html.light .tb-dropdown-item.export-png { color: #00893F; }
69
+ html.light .tb-dropdown-item.export-svg { color: #d97706; }
70
+
32
71
  body {
33
72
  background: var(--bg);
34
73
  color: var(--text);
@@ -45,7 +84,7 @@
45
84
  left: 0;
46
85
  right: 0;
47
86
  height: var(--toolbar-h);
48
- background: rgba(13, 17, 23, 0.92);
87
+ background: rgba(14, 23, 38, 0.92);
49
88
  backdrop-filter: blur(12px);
50
89
  border-bottom: 1px solid var(--border);
51
90
  display: flex;
@@ -101,7 +140,7 @@
101
140
  gap: 6px;
102
141
  padding: 4px 8px;
103
142
  border: 1px solid transparent;
104
- border-radius: 7px;
143
+ border-radius: 4px;
105
144
  background: transparent;
106
145
  color: var(--text-muted);
107
146
  font-family: 'Inter', sans-serif;
@@ -127,13 +166,13 @@
127
166
  }
128
167
 
129
168
  .tb-btn.primary {
130
- background: #3a3a3a;
131
- border-color: black;
169
+ background: linear-gradient(135deg, #0C53A4 0%, #00893F 100%);
170
+ border-color: rgba(97, 175, 239, 0.4);
132
171
  color: #fff;
133
172
  }
134
173
 
135
174
  .tb-btn.primary:hover {
136
- background: #7c060a;
175
+ background: linear-gradient(135deg, #1565c9 0%, #00a04a 100%);
137
176
  }
138
177
 
139
178
  .tb-btn.export-png {
@@ -228,7 +267,7 @@
228
267
  #cmd-palette {
229
268
  width: 580px;
230
269
  max-width: calc(100vw - 32px);
231
- background: rgba(14, 18, 26, 0.98);
270
+ background: rgba(20, 28, 41, 0.98);
232
271
  border: 1px solid rgba(59, 130, 246, 0.25);
233
272
  border-radius: 14px;
234
273
  box-shadow: 0 32px 80px rgba(0, 0, 0, 0.75),
@@ -416,7 +455,7 @@
416
455
  #tooltip {
417
456
  position: fixed;
418
457
  display: none;
419
- background: rgba(22, 27, 34, 0.97);
458
+ background: rgba(20, 28, 41, 0.97);
420
459
  border: 1px solid var(--border);
421
460
  border-radius: var(--radius);
422
461
  padding: 10px 14px;
@@ -519,7 +558,7 @@
519
558
  position: absolute;
520
559
  top: calc(100% + 6px);
521
560
  right: 0;
522
- background: rgba(22, 27, 34, 0.97);
561
+ background: rgba(20, 28, 41, 0.97);
523
562
  border: 1px solid var(--border);
524
563
  border-radius: 8px;
525
564
  padding: 4px;
@@ -568,7 +607,7 @@
568
607
  position: fixed;
569
608
  bottom: 20px;
570
609
  left: 20px;
571
- background: rgba(22, 27, 34, 0.92);
610
+ background: rgba(20, 28, 41, 0.92);
572
611
  border: 1px solid var(--border);
573
612
  border-radius: var(--radius);
574
613
  padding: 12px 16px;
@@ -622,7 +661,7 @@
622
661
  position: fixed;
623
662
  bottom: 20px;
624
663
  right: 20px;
625
- background: rgba(22, 27, 34, 0.92);
664
+ background: rgba(20, 28, 41, 0.92);
626
665
  border: 1px solid var(--border);
627
666
  border-radius: var(--radius);
628
667
  padding: 10px 14px;
@@ -1 +1 @@
1
- window.DIAGRAM_DATA=null;const ACCENT=[117,0,8];function ac(t=1,e=1){const[n,a,o]=ACCENT,s=Math.min(255,Math.round(n*t)),l=Math.min(255,Math.round(a*t)),c=Math.min(255,Math.round(o*t));return e<1?`rgba(${s},${l},${c},${e})`:"#"+[s,l,c].map((t=>t.toString(16).padStart(2,"0"))).join("")}const C={bg:"#0d1117",grid:"rgba(48,54,61,0.35)",instBg:"rgba(6,9,15,0.98)",instBorder:"#303539",instHeader1:"#131920",instHeader2:"#0e1318",dbBg:"rgba(22,27,34,0.97)",dbBorder:"#424a4e",dbHeader1:"#1c2128",dbHeader2:"#22272e",tableBg:"#0d1117",tableBgHov:"#141414",tableBgSel:"#0a0a0a",tableBorder:"#424a4e",tableBorderHov:ac(1.44,1),tableHeader1:"#1f2937",tableHeader2:"#111827",tableHdrLine:ac(1,.5),tableHeaderSel1:ac(1.18),tableHeaderSel2:ac(.52),tableAccent:ac(),tableAccentSel:ac(1.44),text:"#e6edf3",textMuted:"#8b949e",textDim:"#484f58",pk:"#fbbf24",fk:"#22d3ee",virtual:"#975d02",required:"#f87171",glow:"rgba(96,165,250,0.6)",types:{string:"#60a5fa",number:"#a78bfa",boolean:"#34d399",date:"#fb923c",objectId:"#fbbf24",file:"#c084fc",files:"#c084fc"},rels:[["#60a5fa","#22d3ee"],["#f472b6","#c084fc"],["#34d399","#60a5fa"],["#fbbf24","#fb923c"],["#a78bfa","#f472b6"],["#22d3ee","#34d399"]]},L={TABLE_MIN_W:230,TABLE_MAX_W:320,TABLE_HDR_H:40,TABLE_ROW_H:27,TABLE_PAD_X:12,TABLE_FOOT_PAD:6,TABLE_R:8,DB_HDR_H:44,DB_PAD_X:40,DB_PAD_Y:68,DB_GAP_X:56,DB_GAP_Y:48,DB_R:12,INST_HDR_H:52,INST_PAD_X:52,INST_PAD_Y:68,INST_GAP:120,INST_R:16,GRID_SIZE:40,TENSION_MIN:60,TENSION_MAX:220},canvas=document.getElementById("canvas"),ctx=canvas.getContext("2d"),S={rawData:[],tables:{},databases:{},instances:{},relations:[],panX:0,panY:0,zoom:1,vw:0,vh:0,selected:null,hovTable:null,hovRow:-1,dragging:null,mouseDown:!1,mouseStartX:0,mouseStartY:0,mouseMoved:!1,mouseTarget:null,hovConn:null,connDrag:null,raf:null,pulseT:0,ripples:[],animating:!1,_headerObsCache:null,_layoutVersion:0,_gradCache:new Map,_lastPulse:-1};function invalidateLayout(){S._layoutVersion++,S._headerObsCache=null,S._gradCache.clear()}function resize(){const t=window.devicePixelRatio||1;S.vw=window.innerWidth,S.vh=window.innerHeight-40,canvas.style.width=S.vw+"px",canvas.style.height=S.vh+"px",canvas.width=S.vw*t,canvas.height=S.vh*t,ctx.setTransform(t,0,0,t,0,0),scheduleRender()}function parseData(t){S.rawData=t,S.tables={},S.databases={},S.instances={},S.relations=[],t.forEach((t=>{const e=t.instanceName||"default",n=t.databaseName||"default",a=t.collectionName,o=`${e}\0${n}\0${a}`,s=t.calculated?.primaryKey||null,l=buildColumns(t.properties||{},s);S.tables[o]={key:o,inst:e,db:n,coll:a,columns:l,pk:s,x:0,y:0,w:0,h:0},S.instances[e]||(S.instances[e]={name:e,databases:[]});const c=`${e}\0${n}`;S.databases[c]||(S.databases[c]={key:c,inst:e,name:n,tables:[]},S.instances[e].databases.push(c)),S.databases[c].tables.includes(o)||S.databases[c].tables.push(o)}));let e=0;t.forEach((t=>{const n=t.instanceName||"default",a=t.databaseName||"default",o=t.collectionName,s=`${n}\0${a}\0${o}`;!function t(o,l){Object.entries(o).forEach((([o,c])=>{const r=l?l+"."+o:o;if(Array.isArray(c)&&(c=c[0]??{}),"object"!=typeof c||!c)return;if(!(c.__type||c.collection||c.table||c.instance||c.isVirtualField))return void t(c,r);const i=c.collection||c.table;if(!i)return;const x=`${c.instance||n}\0${c.database||a}\0${i}`;if(!S.tables[x])return;const d=S.tables[x];S.relations.push({fromTable:s,fromCol:r,toTable:x,toCol:d.pk||"_id",color:C.rels[e%C.rels.length],idx:e}),e++}))}(t.properties||{},"")})),computeTableSizes(),autoArrange()}function buildColumns(t,e,n,a){n=n||"",a=a||0;const o=[];return Object.entries(t).forEach((([t,s])=>{const l=n?n+"."+t:t;let c=!1;if(Array.isArray(s)&&(c=!0,s=s[0]??{}),"string"==typeof s)return void o.push({name:l,depth:a,type:s,isArray:c,isPK:l===e,isFK:!1,isVirtual:!1,isAutoIncDB:!1,isAutoIncAM:!1,validations:{},conversions:{},targetInst:null,targetDb:null,targetColl:null,targetCol:null});const r=s||{};if(!(r.__type||r.collection||r.table||r.instance||r.isVirtualField))return o.push({name:l,depth:a,type:"object",isArray:c,isPK:!1,isFK:!1,isVirtual:!1,isAutoIncDB:!1,isAutoIncAM:!1,isGroupHeader:!0,validations:{},conversions:{},targetInst:null,targetDb:null,targetColl:null,targetCol:null}),void o.push(...buildColumns(r,e,l,a+1));const i=r.collection||r.table||null;o.push({name:l,depth:a,type:r.__type||"object",isArray:c,isPK:l===e||!!r.isPrimaryKey,isFK:!!i&&!r.isVirtualField,isVirtual:!!r.isVirtualField,isAutoIncDB:!!r.isAutoIncrementByDB,isAutoIncAM:!!r.isAutoIncrementByAM,validations:r.validations||{},conversions:r.conversions||{},targetInst:r.instance||null,targetDb:r.database||null,targetColl:i,targetCol:r.column||null})})),o}function computeTableSizes(){ctx.save(),Object.values(S.tables).forEach((t=>{ctx.font='bold 13px "JetBrains Mono", monospace';let e=ctx.measureText(t.coll).width+64;ctx.font='11px "JetBrains Mono", monospace',t.columns.forEach((t=>{const n=10*(t.depth||0),a=t.name.includes(".")?t.name.split(".").pop():t.name,o=t.isGroupHeader?a+(t.isArray?" []":" {}"):a+(t.isArray?" []":""),s=t.isGroupHeader?"":t.type||"",l=ctx.measureText(o).width+n+(s?ctx.measureText(s).width+14:0)+(t.isGroupHeader?0:13*countIcons(t))+36;l>e&&(e=l)})),t.w=Math.max(L.TABLE_MIN_W,Math.min(L.TABLE_MAX_W,Math.ceil(e))),t.h=L.TABLE_HDR_H+t.columns.length*L.TABLE_ROW_H+L.TABLE_FOOT_PAD})),ctx.restore()}function countIcons(t){const e=t.validations,n=t.conversions;let a=1;return t.isVirtual&&a++,e.required&&a++,e.unique&&a++,e.email&&a++,e.enum&&a++,n.encryption&&a++,n.hashing&&a++,(n.defaults?.defaultFun||n.defaults?.defaultValue)&&a++,a}function autoArrange(){const t=L.DB_GAP_Y;let e=L.INST_PAD_X;Object.values(S.instances).forEach((n=>{let a=L.INST_HDR_H+L.INST_PAD_Y,o=0;n.databases.forEach((n=>{const s=S.databases[n],l=s.tables,c=computeTableLevels(l),r=l.length>0?Math.max(...l.map((t=>c[t]??0))):0,i=Array.from({length:r+1},(()=>[]));l.forEach((t=>i[c[t]??0].push(t)));const x=e+L.INST_PAD_X+L.DB_PAD_X,d=a+L.INST_PAD_Y+L.DB_HDR_H+L.DB_PAD_Y;let u=x,h=0;i.forEach((e=>{if(0!==e.length)for(let n=0;n<e.length;n+=3){const a=e.slice(n,n+3);let o=d,s=0;a.forEach((e=>{const n=S.tables[e];n.x=u,n.y=o,o+=n.h+t,s=Math.max(s,n.w)})),h=Math.max(h,o-d-t),u+=s+180}}));const f=u-x-180;s._layoutW=Math.max(f,L.TABLE_MIN_W)+2*L.DB_PAD_X,s._layoutH=h+L.DB_HDR_H+2*L.DB_PAD_Y,o=Math.max(o,s._layoutW),a+=s._layoutH+t})),e+=o+2*L.INST_PAD_X+L.INST_GAP})),centerView()}function computeTableLevels(t){if(!t||0===t.length)return{};const e=new Set(t),n={},a={};t.forEach((t=>{n[t]=[],a[t]=0})),S.relations.forEach((t=>{t.fromTable!==t.toTable&&e.has(t.fromTable)&&e.has(t.toTable)&&(n[t.fromTable].includes(t.toTable)||(n[t.fromTable].push(t.toTable),a[t.toTable]++))}));const o={};t.forEach((t=>{o[t]=0}));const s={...a},l=t.filter((t=>0===s[t]));let c=0;for(;c<l.length;){const t=l[c++];n[t].forEach((e=>{o[e]=Math.max(o[e],o[t]+1),s[e]--,0===s[e]&&l.push(e)}))}return o}function dbBounds(t){const e=S.databases[t];let n=1/0,a=1/0,o=-1/0,s=-1/0;return e.tables.forEach((t=>{const e=S.tables[t];n=Math.min(n,e.x),a=Math.min(a,e.y),o=Math.max(o,e.x+e.w),s=Math.max(s,e.y+e.h)})),{x:n-L.DB_PAD_X,y:a-L.DB_HDR_H-L.DB_PAD_Y,w:o-n+2*L.DB_PAD_X,h:s-a+L.DB_HDR_H+2*L.DB_PAD_Y}}function instBounds(t){const e=S.instances[t];let n=1/0,a=1/0,o=-1/0,s=-1/0;return e.databases.forEach((t=>{const e=dbBounds(t);n=Math.min(n,e.x),a=Math.min(a,e.y),o=Math.max(o,e.x+e.w),s=Math.max(s,e.y+e.h)})),{x:n-L.INST_PAD_X,y:a-L.INST_HDR_H-L.INST_PAD_Y,w:o-n+2*L.INST_PAD_X,h:s-a+L.INST_HDR_H+2*L.INST_PAD_Y}}function centerView(){if(!Object.keys(S.instances).length)return;let t=1/0,e=1/0,n=-1/0,a=-1/0;Object.keys(S.instances).forEach((o=>{const s=instBounds(o);t=Math.min(t,s.x),e=Math.min(e,s.y),n=Math.max(n,s.x+s.w),a=Math.max(a,s.y+s.h)}));const o=n-t,s=a-e;S.zoom=Math.max(.1,Math.min((S.vw-48)/o,(S.vh-48)/s)),S.panX=(S.vw-o*S.zoom)/2-t*S.zoom,S.panY=(S.vh-s*S.zoom)/2-e*S.zoom,updateZoomLabel()}function scheduleRender(){S.animating||requestAnimationFrame(renderFrame)}function startAnimation(){S.animating||(S.animating=!0,requestAnimationFrame(renderFrame))}function renderFrame(t){S.pulseT=(t||0)/1e3;const e=.5*Math.sin(2.5*S.pulseT)+.5,n=S.ripples.length>0||S.selected;n&&Math.abs(e-S._lastPulse)<.005&&!S.ripples.length?requestAnimationFrame(renderFrame):(S._lastPulse=e,render(),n?requestAnimationFrame(renderFrame):S.animating=!1)}function screenToWorldRect(){return{x:-S.panX/S.zoom,y:-S.panY/S.zoom,w:S.vw/S.zoom,h:S.vh/S.zoom}}function isRectVisible(t,e,n,a,o){return t<o.x+o.w&&t+n>o.x&&e<o.y+o.h&&e+a>o.y}function render(){ctx.clearRect(0,0,S.vw,S.vh),ctx.fillStyle=C.bg,ctx.fillRect(0,0,S.vw,S.vh),drawGrid(),ctx.save(),ctx.translate(S.panX,S.panY),ctx.scale(S.zoom,S.zoom);const t=screenToWorldRect();Object.keys(S.instances).forEach((e=>{const n=instBounds(e);isRectVisible(n.x,n.y,n.w,n.h,t)&&drawInstanceBox(e)})),Object.keys(S.databases).forEach((e=>{const n=dbBounds(e);isRectVisible(n.x,n.y,n.w,n.h,t)&&drawDatabaseBox(e)})),Object.values(S.tables).forEach((e=>{isRectVisible(e.x,e.y,e.w,e.h,t)&&drawTable(e)})),drawRelations(t),drawRipples(),ctx.restore()}function drawGrid(){const t=L.GRID_SIZE*S.zoom,e=(S.panX%t+t)%t,n=(S.panY%t+t)%t;ctx.save(),ctx.strokeStyle=C.grid,ctx.lineWidth=.5,ctx.beginPath();for(let n=e;n<S.vw;n+=t)ctx.moveTo(n,0),ctx.lineTo(n,S.vh);for(let e=n;e<S.vh;e+=t)ctx.moveTo(0,e),ctx.lineTo(S.vw,e);ctx.stroke(),ctx.fillStyle="rgba(48,54,61,0.6)",ctx.beginPath();for(let a=e;a<S.vw;a+=t)for(let e=n;e<S.vh;e+=t)ctx.moveTo(a+.7,e),ctx.arc(a,e,.7,0,2*Math.PI);ctx.fill(),ctx.restore()}function drawInstanceBox(t){const e=instBounds(t),n=L.INST_R;ctx.save(),ctx.shadowBlur=0,ctx.beginPath(),roundRect(ctx,e.x,e.y,e.w,e.h,n),ctx.clip(),ctx.fillStyle=C.instBg,ctx.fillRect(e.x,e.y,e.w,e.h);const a=ctx.createLinearGradient(e.x,e.y,e.x+e.w,e.y+e.h);a.addColorStop(0,"rgba(29,78,216,0.06)"),a.addColorStop(1,"rgba(6,182,212,0.04)"),ctx.fillStyle=a,ctx.fillRect(e.x,e.y,e.w,e.h),ctx.restore(),ctx.shadowBlur=0;const o=L.INST_HDR_H;ctx.save(),ctx.beginPath(),roundRect(ctx,e.x,e.y,e.w,o,[n,n,0,0]),ctx.clip();const s=ctx.createLinearGradient(e.x,e.y,e.x+e.w,e.y);s.addColorStop(0,C.instHeader1),s.addColorStop(1,C.instHeader2),ctx.fillStyle=s,ctx.fillRect(e.x,e.y,e.w,o),ctx.fillStyle="rgba(255,255,255,0.04)",ctx.fillRect(e.x,e.y,e.w,1),ctx.restore(),ctx.strokeStyle=C.instBorder,ctx.lineWidth=1,ctx.beginPath(),roundRect(ctx,e.x,e.y,e.w,e.h,n),ctx.stroke(),drawServerIcon(e.x+14,e.y+12,28,28),ctx.fillStyle="#fff",ctx.font='bold 14px "JetBrains Mono", monospace',ctx.textBaseline="middle",ctx.fillText(t.toUpperCase(),e.x+50,e.y+o/2),ctx.font='9px "JetBrains Mono", monospace';const l="INSTANCE",c=ctx.measureText(l).width+14;ctx.fillStyle="rgba(255,255,255,0.1)",roundRectFill(ctx,e.x+e.w-c-10,e.y+o/2-9,c,18,9),ctx.fillStyle="rgba(255,255,255,0.55)",ctx.fillText(l,e.x+e.w-c-3,e.y+o/2),ctx.textBaseline="alphabetic"}function drawDatabaseBox(t){const e=dbBounds(t),n=S.databases[t],a=L.DB_R;ctx.save(),ctx.beginPath(),roundRect(ctx,e.x,e.y,e.w,e.h,a),ctx.clip(),ctx.fillStyle=C.dbBg,ctx.fillRect(e.x,e.y,e.w,e.h),ctx.restore();const o=L.DB_HDR_H;ctx.save(),ctx.beginPath(),roundRect(ctx,e.x,e.y,e.w,o,[a,a,0,0]),ctx.clip();const s=ctx.createLinearGradient(e.x,e.y,e.x+e.w,e.y);s.addColorStop(0,C.dbHeader1),s.addColorStop(1,C.dbHeader2),ctx.fillStyle=s,ctx.fillRect(e.x,e.y,e.w,o),ctx.restore(),ctx.strokeStyle=C.dbBorder,ctx.lineWidth=1,ctx.beginPath(),roundRect(ctx,e.x,e.y,e.w,e.h,a),ctx.stroke(),drawDBIcon(e.x+12,e.y+8,26,26),ctx.fillStyle="#c9d1d9",ctx.font='bold 12px "JetBrains Mono", monospace',ctx.textBaseline="middle",ctx.fillText(n.name,e.x+44,e.y+o/2),ctx.font='9px "JetBrains Mono", monospace',ctx.fillStyle=C.textMuted;const l="DATABASE";ctx.fillText(l,e.x+e.w-ctx.measureText(l).width-10,e.y+o/2),ctx.textBaseline="alphabetic"}function drawTable(t){const{x:e,y:n,w:a,h:o,coll:s,columns:l,key:c}=t,r=S.selected===c,i=S.hovTable===c,x=.5*Math.sin(3*S.pulseT)+.5,d=L.TABLE_R;ctx.save(),ctx.shadowColor=r?ac(1,.4):"rgba(0,0,0,0.5)",ctx.shadowBlur=r?24+12*x:12,ctx.shadowOffsetY=4,ctx.beginPath(),roundRect(ctx,e,n,a,o,d),ctx.fillStyle="transparent",ctx.fill(),ctx.restore(),ctx.save(),ctx.beginPath(),roundRect(ctx,e,n,a,o,d),ctx.clip(),ctx.fillStyle=r?C.tableBgSel:i?C.tableBgHov:C.tableBg,ctx.fillRect(e,n,a,o);const u=c;let h=S._gradCache.get(u);const f=`${e},${n},${a},${o},${r?1:0},${S._layoutVersion}`;if(!h||h.state!==f){const t=ctx.createLinearGradient(e,n,e,n+o);t.addColorStop(0,"rgba(255,255,255,0.025)"),t.addColorStop(1,"rgba(0,0,0,0)");const s=ctx.createLinearGradient(e,n,e,n+L.TABLE_HDR_H);s.addColorStop(0,r?C.tableHeaderSel1:C.tableHeader1),s.addColorStop(1,r?C.tableHeaderSel2:C.tableHeader2);const l=ctx.createLinearGradient(e,n,e+a,n);l.addColorStop(0,r?C.tableAccentSel:C.tableAccent),l.addColorStop(1,ac(r?1.71:.63)),h={state:f,bgOverlay:t,hGrad:s,accentGrad:l},S._gradCache.set(u,h)}ctx.fillStyle=h.bgOverlay,ctx.fillRect(e,n,a,o),ctx.fillStyle=h.hGrad,ctx.fillRect(e,n,a,L.TABLE_HDR_H),ctx.fillStyle=h.accentGrad,ctx.fillRect(e,n,a,2),ctx.restore(),ctx.fillStyle="#f0f6fc",ctx.font='bold 13px "JetBrains Mono", monospace',ctx.textBaseline="middle",ctx.fillText(s,e+L.TABLE_PAD_X,n+L.TABLE_HDR_H/2+1);const y=String(l.length);ctx.font='10px "JetBrains Mono", monospace';const p=ctx.measureText(y).width+10,b=e+a-p-8,m=n+L.TABLE_HDR_H/2-9;ctx.fillStyle="rgba(255,255,255,0.12)",roundRectFill(ctx,b,m,p,18,9),ctx.fillStyle="rgba(255,255,255,0.55)",ctx.fillText(y,b+5,m+9),ctx.textBaseline="alphabetic",ctx.strokeStyle=C.tableHdrLine,ctx.lineWidth=1,ctx.beginPath(),ctx.moveTo(e+1,n+L.TABLE_HDR_H),ctx.lineTo(e+a-1,n+L.TABLE_HDR_H),ctx.stroke(),l.forEach(((e,n)=>drawColumn(t,e,n)));const g=r?.6+.4*x:i?.4:1;ctx.strokeStyle=r?ac(1.44,g):i?C.tableBorderHov:C.tableBorder,ctx.lineWidth=r?2:1,ctx.beginPath(),roundRect(ctx,e,n,a,o,d),ctx.stroke()}function drawColumn(t,e,n){const{x:a,y:o,w:s,key:l}=t,c=o+L.TABLE_HDR_H+L.TABLE_FOOT_PAD/2+n*L.TABLE_ROW_H,r=L.TABLE_ROW_H,i=S.hovTable===l&&S.hovRow===n;S.selected;e.isGroupHeader?(ctx.fillStyle="rgba(139,148,158,0.05)",ctx.fillRect(a+1,c,s-2,r)):e.isVirtual&&(ctx.fillStyle="rgba(245,158,11,0.07)",ctx.fillRect(a+1,c,s-2,r)),i&&(ctx.fillStyle="rgba(96,165,250,0.07)",ctx.fillRect(a+1,c,s-2,r)),e.isGroupHeader?(ctx.fillStyle="rgba(139,148,158,0.2)",ctx.fillRect(a+1,c+2,2,r-4)):e.isPK?(ctx.fillStyle=C.pk,ctx.fillRect(a+1,c+5,3,r-10)):e.isVirtual?(ctx.fillStyle=C.virtual,ctx.fillRect(a+1,c+5,3,r-10)):e.isFK&&(ctx.fillStyle=C.fk,ctx.fillRect(a+1,c+5,3,r-10));const x=10*(e.depth||0),d=e.name.includes(".")?e.name.split(".").pop():e.name,u=e.isGroupHeader?d+(e.isArray?" []":" {}"):d+(e.isArray?" []":"");ctx.font=e.isPK?'bold 11px "JetBrains Mono", monospace':e.isGroupHeader?'bold 10px "JetBrains Mono", monospace':'11px "JetBrains Mono", monospace',ctx.textBaseline="middle",e.isGroupHeader?ctx.fillStyle=C.textMuted:e.isVirtual?ctx.fillStyle=C.virtual:e.isFK?ctx.fillStyle=C.fk:e.isPK?ctx.fillStyle=C.pk:ctx.fillStyle=C.text,ctx.fillText(u,a+L.TABLE_PAD_X+2+x,c+r/2),ctx.textBaseline="alphabetic";let h=a+s-6;if(e.isGroupHeader)return;const f=C.types[e.type]||"#94a3b8",y=e.type||"?";ctx.font='9px "JetBrains Mono", monospace';const p=ctx.measureText(y).width+10;h-=p,roundRectFill(ctx,h,c+(r-16)/2,p,16,8,f+"22"),ctx.strokeStyle=f+"55",ctx.lineWidth=.8,ctx.beginPath(),roundRect(ctx,h,c+(r-16)/2,p,16,8),ctx.stroke(),ctx.fillStyle=f,ctx.textBaseline="middle",ctx.fillText(y,h+5,c+r/2),ctx.textBaseline="alphabetic",h-=4;const b=e.validations,m=e.conversions;e.isVirtual&&(h-=14,ctx.fillStyle="#f59e0b",ctx.font='bold 9px "JetBrains Mono", monospace',ctx.textBaseline="middle",ctx.fillText("V",h+2,c+r/2),ctx.textBaseline="alphabetic"),b.required&&(h-=12,ctx.fillStyle=C.required,ctx.beginPath(),ctx.arc(h+5,c+r/2,3.5,0,2*Math.PI),ctx.fill()),b.unique&&(h-=14,drawUniqueIcon(h,c+(r-12)/2,12,12)),b.email&&(h-=14,ctx.fillStyle="#60a5fa",ctx.font="bold 10px sans-serif",ctx.textBaseline="middle",ctx.fillText("@",h+1,c+r/2),ctx.textBaseline="alphabetic"),b.enum&&(h-=14,ctx.fillStyle="#a78bfa",ctx.font="10px sans-serif",ctx.textBaseline="middle",ctx.fillText("≡",h+1,c+r/2),ctx.textBaseline="alphabetic"),m.encryption&&(h-=14,drawLockIcon(h,c+(r-12)/2,10,12)),m.hashing&&(h-=14,ctx.fillStyle="#f59e0b",ctx.font='bold 10px "JetBrains Mono", monospace',ctx.textBaseline="middle",ctx.fillText("#",h+2,c+r/2),ctx.textBaseline="alphabetic"),(m.defaults?.defaultFun||m.defaults?.defaultValue)&&(h-=14,ctx.fillStyle="#6ee7b7",ctx.font='bold 9px "JetBrains Mono", monospace',ctx.textBaseline="middle",ctx.fillText("D",h+2,c+r/2),ctx.textBaseline="alphabetic"),(e.isAutoIncDB||e.isAutoIncAM)&&(h-=14,ctx.fillStyle="#fb923c",ctx.font='9px "JetBrains Mono", monospace',ctx.textBaseline="middle",ctx.fillText("AI",h,c+r/2),ctx.textBaseline="alphabetic")}function getHeaderObstacles(){if(S._headerObsCache)return S._headerObsCache;const t=[];return Object.keys(S.databases).forEach((e=>{const n=dbBounds(e);t.push({x:n.x,y:n.y,w:n.w,h:L.DB_HDR_H})})),Object.keys(S.instances).forEach((e=>{const n=instBounds(e);t.push({x:n.x,y:n.y,w:n.w,h:L.INST_HDR_H})})),S._headerObsCache=t,t}function findLabelPos(t,e=80,n=[]){const a=1.5*RT.STUB,o=getHeaderObstacles(),s=Object.values(S.tables);function l(t,n){return s.some((a=>t+e>a.x&&t-e<a.x+a.w&&n>a.y-12&&n<a.y+a.h+12))}function c(t,n){return o.some((a=>t+e>a.x&&t-e<a.x+a.w&&n>a.y-8&&n<a.y+a.h+8))}function r(e){let n=0,o=null;for(let s=0;s<t.length-1;s++){const l=t[s],c=t[s+1],r=Math.hypot(c.x-l.x,c.y-l.y);if(r<a)continue;const i=Math.max(2,Math.ceil(r/10));let x=-1,d=0;for(let t=0;t<=i;t++){const a=t/i;if(e(l.x+(c.x-l.x)*a,l.y+(c.y-l.y)*a))x<0&&(x=t),d++;else{if(d>n){n=d;const t=(x+d/2)/i;o={x:l.x+(c.x-l.x)*t,y:l.y+(c.y-l.y)*t}}x=-1,d=0}}if(d>n){n=d;const t=(x+d/2)/i;o={x:l.x+(c.x-l.x)*Math.min(1,t),y:l.y+(c.y-l.y)*Math.min(1,t)}}}return o}const i=r(((t,a)=>!l(t,a)&&!c(t,a)&&!function(t,a){return n.some((n=>t+e>n.x&&t-e<n.x+n.w&&a+13>n.y&&a-13<n.y+n.h))}(t,a)));if(i)return i;const x=r(((t,e)=>!l(t,e)&&!c(t,e)));if(x)return x;let d=t[Math.floor(t.length/2)],u=-1;for(let e=0;e<t.length-1;e++){const n=t[e],o=t[e+1],s=Math.hypot(o.x-n.x,o.y-n.y);s>=a&&s>u&&(u=s,d={x:(n.x+o.x)/2,y:(n.y+o.y)/2})}return d}function spreadOverlappingLines(){const t=RT.GAP+6,e=S.relations.filter((t=>t._wpts&&!t._pinWpts&&t._wpts.length>=6));if(e.length<2)return;const n=getHeaderObstacles(),a=Object.values(S.tables);function o(e,o,s){const l=Math.min(o,s)-8,c=Math.max(o,s)+8;let r=e;for(let o=0;o<10;o++){let o=!1;for(const s of[a,n])for(const n of s)if(!(n.x>=c||n.x+n.w<=l)&&r>n.y-t&&r<n.y+n.h+t){const a=n.y-t,s=n.y+n.h+t;r=Math.abs(e-a)<=Math.abs(e-s)?a:s,o=!0}if(!o)break}return r}function s(e,o,s){const l=Math.min(o,s)-8,c=Math.max(o,s)+8;let r=e;for(let o=0;o<10;o++){let o=!1;for(const s of[a,n])for(const n of s)if(!(n.y>=c||n.y+n.h<=l)&&r>n.x-t&&r<n.x+n.w+t){const a=n.x-t,s=n.x+n.w+t;r=Math.abs(e-a)<=Math.abs(e-s)?a:s,o=!0}if(!o)break}return r}const l=e.map((t=>{const e=t._wpts,n=Math.floor((e.length-1)/2),a=e[n],o=e[n+1];return{a:a,b:o,horiz:Math.abs(a.y-o.y)<1,vert:Math.abs(a.x-o.x)<1}}));function c(t,e,n){const a=t.map(((t,e)=>e));function o(t){return a[t]===t?t:a[t]=o(a[t])}for(let s=0;s<t.length;s++)for(let l=s+1;l<t.length;l++){const c=t[s],r=t[l];if(Math.abs(c.a[e]-r.a[e])>=4)continue;const i=Math.min(c.a[n],c.b[n]),x=Math.max(c.a[n],c.b[n]),d=Math.min(r.a[n],r.b[n]);i<Math.max(r.a[n],r.b[n])&&d<x&&(a[o(s)]=o(l))}const s=new Map;return t.forEach(((t,e)=>{const n=o(e);s.has(n)||s.set(n,[]),s.get(n).push(t)})),[...s.values()].filter((t=>t.length>1))}for(const t of c(l.filter((t=>t.horiz)),"y","x")){const e=t.reduce(((t,e)=>t+e.a.y),0)/t.length;t.forEach(((n,a)=>{const s=o(e+12*(a-(t.length-1)/2),n.a.x,n.b.x)-n.a.y;n.a.y+=s,n.b.y+=s}))}for(const t of c(l.filter((t=>t.vert)),"x","y")){const e=t.reduce(((t,e)=>t+e.a.x),0)/t.length;t.forEach(((n,a)=>{const o=s(e+12*(a-(t.length-1)/2),n.a.y,n.b.y)-n.a.x;n.a.x+=o,n.b.x+=o}))}}function distToSeg(t,e,n,a,o,s){const l=o-n,c=s-a,r=l*l+c*c;if(r<1e-4)return Math.hypot(t-n,e-a);const i=Math.max(0,Math.min(1,((t-n)*l+(e-a)*c)/r));return Math.hypot(t-(n+i*l),e-(a+i*c))}function hitConnHandle(t,e){const n=10/S.zoom;for(const a of S.relations){const o=a._wpts;if(o&&!(o.length<6))for(let s=2;s<=o.length-4;s++){const l=o[s],c=o[s+1];if(Math.hypot(c.x-l.x,c.y-l.y)<30)continue;const r=(l.x+c.x)/2,i=(l.y+c.y)/2;if(Math.hypot(t-r,e-i)<=n)return{rel:a,segIdx:s}}}return null}function hitConnSeg(t,e){const n=6/S.zoom;for(const a of S.relations){const o=a._wpts;if(o)for(let s=0;s<o.length-1;s++){const l=o[s],c=o[s+1];if(distToSeg(t,e,l.x,l.y,c.x,c.y)<=n)return a}}return null}function drawRelations(t){const e=getHeaderObstacles();S.relations.forEach((t=>{t._labelMid=null;const n=S.tables[t.fromTable],a=S.tables[t.toTable];if(!n||!a||t.fromTable===t.toTable)return void(t._wpts=null);if(t._pinWpts)return void(t._wpts=t._pinWpts);if(t._wpts&&t._wptVersion===S._layoutVersion)return;const{p1:o,s1:s,p2:l,s2:c}=connPoints(n,a,t.fromCol,t.toCol);t._wpts=computeWaypoints(o,s,l,c,n.key,a.key,e),t._wptVersion=S._layoutVersion})),spreadOverlappingLines(),ctx.font='9px "JetBrains Mono", monospace';const n=[];S.relations.forEach((t=>{if(S.selected!==t.fromTable&&S.selected!==t.toTable)return;if(!t._wpts)return;const e=ctx.measureText(`${t.fromCol} → ${t.toCol}`).width+12,a=findLabelPos(t._wpts,e/2,n);t._labelMid=a,n.push({x:a.x-e/2-6,y:a.y-10-6,w:e+12,h:30})}));const a=t?S.relations.filter((e=>{if(S.selected===e.fromTable||S.selected===e.toTable)return!0;const n=S.tables[e.fromTable],a=S.tables[e.toTable];return!(!n||!a)&&(isRectVisible(n.x,n.y,n.w,n.h,t)||isRectVisible(a.x,a.y,a.w,a.h,t))})):S.relations;a.forEach((t=>drawRelation(t,!1))),a.forEach((t=>drawRelation(t,!0)))}function drawRelation(t,e){const n=S.tables[t.fromTable],a=S.tables[t.toTable];if(!n||!a)return;const o=S.selected===t.fromTable||S.selected===t.toTable,s=.5*Math.sin(2.5*S.pulseT)+.5,l=o?1.8+.8*s:1.4;if(ctx.save(),ctx.globalAlpha=o?1:.22,t.fromTable===t.toTable){const s={x:n.x+n.w,y:colRowY(n,t.fromCol)},c={x:a.x+a.w,y:colRowY(a,t.toCol)};if(e||drawSelfLoop(s,c,t.color[0],t.color[1],l,o),e&&o){const e=`${t.fromCol} → ${t.toCol}`;ctx.font='9px "JetBrains Mono", monospace';const a=ctx.measureText(e).width+12,o=n.x+n.w+48,l=(s.y+c.y)/2;roundRectFill(ctx,o-a/2,l-10,a,18,9,"rgba(13,17,23,0.85)"),ctx.strokeStyle=t.color[0]+"66",ctx.lineWidth=.8,ctx.beginPath(),roundRect(ctx,o-a/2,l-10,a,18,9),ctx.stroke(),ctx.fillStyle=t.color[0],ctx.textBaseline="middle",ctx.fillText(e,o-a/2+6,l),ctx.textBaseline="alphabetic"}return void ctx.restore()}const c=t._wpts;if(c){if(!e&&(drawRoutedPath(c,t.color[0],t.color[1],l,o),drawArrowHead(c,t.color[1]),drawSourceDot(c[0],t.color[0]),(S.hovConn?.rel===t||o)&&c.length>=6))for(let e=2;e<=c.length-4;e++){const n=c[e],a=c[e+1];if(Math.hypot(a.x-n.x,a.y-n.y)<30)continue;const o=(n.x+a.x)/2,s=(n.y+a.y)/2,l=S.hovConn?.rel===t&&S.hovConn?.segIdx===e;ctx.save(),ctx.globalAlpha=1,ctx.beginPath(),ctx.arc(o,s,l?6:4,0,2*Math.PI),ctx.fillStyle=l?"#ffffff":"rgba(255,255,255,0.75)",ctx.fill(),ctx.strokeStyle=t.color[0],ctx.lineWidth=1.5/S.zoom,ctx.stroke(),ctx.restore()}if(e&&o){const e=`${t.fromCol} → ${t.toCol}`;ctx.font='9px "JetBrains Mono", monospace';const n=ctx.measureText(e).width+12,a=t._labelMid||findLabelPos(c,n/2);roundRectFill(ctx,a.x-n/2,a.y-10,n,18,9,"rgba(13,17,23,0.9)"),ctx.strokeStyle=t.color[0]+"66",ctx.lineWidth=.8,ctx.beginPath(),roundRect(ctx,a.x-n/2,a.y-10,n,18,9),ctx.stroke(),ctx.fillStyle=t.color[0],ctx.textBaseline="middle",ctx.fillText(e,a.x-n/2+6,a.y),ctx.textBaseline="alphabetic"}ctx.restore()}else ctx.restore()}function colRowY(t,e){if(!e)return t.y+t.h/2;const n=t.columns.findIndex((t=>t.name===e));return n<0?t.y+t.h/2:t.y+L.TABLE_HDR_H+L.TABLE_FOOT_PAD/2+n*L.TABLE_ROW_H+L.TABLE_ROW_H/2}function connPoints(t,e,n,a){const o=colRowY(t,n),s=colRowY(e,a),l=t.x+t.w/2,c=e.x+e.w/2;let r,i,x,d;const u=e.x-(t.x+t.w),h=t.x-(e.x+e.w);if(u>=-4)r={x:t.x+t.w,y:o},i="r",x={x:e.x,y:s},d="l";else if(h>=-4)r={x:t.x,y:o},i="l",x={x:e.x+e.w,y:s},d="r";else{e.y+e.h/2-(t.y+t.h/2)>=0?(r={x:l,y:t.y+t.h},i="d",x={x:c,y:e.y},d="u"):(r={x:l,y:t.y},i="u",x={x:c,y:e.y+e.h},d="d")}return{p1:r,s1:i,p2:x,s2:d}}window.addEventListener("resize",resize);const RT={STUB:36,GAP:22,CORNER_R:9,SELF_W:56};function addDir(t,e,n){return{x:t.x+("r"===e?n:"l"===e?-n:0),y:t.y+("d"===e?n:"u"===e?-n:0)}}function segHitsTable(t,e,n,a,o,s){const l=o.x-s,c=o.y-s,r=o.x+o.w+s,i=o.y+o.h+s;if(t===n){const n=Math.min(e,a),o=Math.max(e,a);return t>l&&t<r&&o>c&&n<i}const x=Math.min(t,n),d=Math.max(t,n);return e>c&&e<i&&d>l&&x<r}function pathHitsObs(t,e){for(let n=0;n<t.length-1;n++){const a=t[n],o=t[n+1];for(const t of e)if(segHitsTable(a.x,a.y,o.x,o.y,t,RT.GAP))return!0}return!1}function findClearHBand(t,e,n){const a=Math.min(t.x,e.x)-RT.GAP,o=Math.max(t.x,e.x)+RT.GAP,s=n.filter((t=>t.x<o&&t.x+t.w>a));if(!s.length)return(t.y+e.y)/2;const l=(t.y+e.y)/2,c=RT.GAP+8,r=[];for(const t of s)r.push(t.y-c),r.push(t.y+t.h+c);const i=r.filter((t=>!s.some((e=>t>e.y-c&&t<e.y+e.h+c))));return i.length>0?i.reduce(((t,e)=>Math.abs(e-l)<Math.abs(t-l)?e:t)):Math.max(...s.map((t=>t.y+t.h)))+c}function findClearVBand(t,e,n){const a=Math.min(t.y,e.y)-RT.GAP,o=Math.max(t.y,e.y)+RT.GAP,s=n.filter((t=>t.y<o&&t.y+t.h>a));if(!s.length)return(t.x+e.x)/2;const l=(t.x+e.x)/2,c=RT.GAP+8,r=[];for(const t of s)r.push(t.x-c),r.push(t.x+t.w+c);const i=r.filter((t=>!s.some((e=>t>e.x-c&&t<e.x+e.w+c))));return i.length>0?i.reduce(((t,e)=>Math.abs(e-l)<Math.abs(t-l)?e:t)):Math.max(...s.map((t=>t.x+t.w)))+c}function routeHH(t,e,n,a,o){const s=[(e.x+n.x)/2,e.x+RT.STUB,n.x-RT.STUB,e.x+.35*(n.x-e.x),e.x+.65*(n.x-e.x)];for(const l of s){const s=[t,e,{x:l,y:e.y},{x:l,y:n.y},n,a];if(!pathHitsObs(s,o))return s}const l=Math.min(e.x,n.x)-RT.GAP,c=Math.max(e.x,n.x)+RT.GAP,r=o.filter((t=>t.x<c&&t.x+t.w>l)),i=RT.GAP+8,x=[];for(const t of r)x.push(t.y-i),x.push(t.y+t.h+i);const d=(e.y+n.y)/2;x.sort(((t,e)=>Math.abs(t-d)-Math.abs(e-d)));for(const s of x){const l=[t,e,{x:e.x,y:s},{x:n.x,y:s},n,a];if(!pathHitsObs(l,o))return l}const u=x.length?x[0]:findClearHBand(e,n,o);return[t,e,{x:e.x,y:u},{x:n.x,y:u},n,a]}function routeVV(t,e,n,a,o){const s=[(e.y+n.y)/2,e.y+RT.STUB,n.y-RT.STUB];for(const l of s){const s=[t,e,{x:e.x,y:l},{x:n.x,y:l},n,a];if(!pathHitsObs(s,o))return s}const l=Math.min(e.y,n.y)-RT.GAP,c=Math.max(e.y,n.y)+RT.GAP,r=o.filter((t=>t.y<c&&t.y+t.h>l)),i=RT.GAP+8,x=[];for(const t of r)x.push(t.x-i),x.push(t.x+t.w+i);const d=(e.x+n.x)/2;x.sort(((t,e)=>Math.abs(t-d)-Math.abs(e-d)));for(const s of x){const l=[t,e,{x:s,y:e.y},{x:s,y:n.y},n,a];if(!pathHitsObs(l,o))return l}const u=x.length?x[0]:findClearVBand(e,n,o);return[t,e,{x:u,y:e.y},{x:u,y:n.y},n,a]}function routeL(t,e,n,a,o){const s={x:e.x,y:n.y},l={x:n.x,y:e.y},c=[t,e,s,n,a];if(!pathHitsObs(c,o))return c;const r=[t,e,l,n,a];if(!pathHitsObs(r,o))return r;const i=findClearVBand(e,n,o);return[t,e,{x:i,y:e.y},{x:i,y:n.y},n,a]}function computeWaypoints(t,e,n,a,o,s,l=[]){const c=Object.values(S.tables).filter((t=>t.key!==o&&t.key!==s)),r=l.length?[...c,...l]:c,i=addDir(t,e,RT.STUB),x=addDir(n,a,RT.STUB),d="r"===e||"l"===e,u="r"===a||"l"===a;return d&&u?routeHH(t,i,x,n,r):d||u?routeL(t,i,x,n,r):routeVV(t,i,x,n,r)}function drawRoutedPath(t,e,n,a,o){if(t.length<2)return;const s=t[0],l=t[t.length-1],c=ctx.createLinearGradient(s.x,s.y,l.x,l.y);c.addColorStop(0,e),c.addColorStop(1,n),o&&(ctx.shadowColor=e+"aa",ctx.shadowBlur=10),ctx.strokeStyle=c,ctx.lineWidth=a,ctx.lineCap="round",ctx.lineJoin="round",ctx.beginPath(),ctx.moveTo(s.x,s.y);for(let e=1;e<t.length-1;e++){const n=t[e-1],a=t[e],o=t[e+1],s=Math.hypot(a.x-n.x,a.y-n.y),l=Math.hypot(o.x-a.x,o.y-a.y);if(s<.5||l<.5){ctx.lineTo(a.x,a.y);continue}const c=Math.min(RT.CORNER_R,s/2,l/2),r=a.x-c*(a.x-n.x)/s,i=a.y-c*(a.y-n.y)/s,x=a.x+c*(o.x-a.x)/l,d=a.y+c*(o.y-a.y)/l;ctx.lineTo(r,i),ctx.quadraticCurveTo(a.x,a.y,x,d)}ctx.lineTo(l.x,l.y),ctx.stroke(),ctx.shadowBlur=0}function drawArrowHead(t,e){const n=t.length;if(n<2)return;const a=t[n-2],o=t[n-1],s=Math.atan2(o.y-a.y,o.x-a.x);ctx.fillStyle=e,ctx.beginPath(),ctx.moveTo(o.x,o.y),ctx.lineTo(o.x-10*Math.cos(s-.42),o.y-10*Math.sin(s-.42)),ctx.lineTo(o.x-10*Math.cos(s+.42),o.y-10*Math.sin(s+.42)),ctx.closePath(),ctx.fill()}function drawSourceDot(t,e){ctx.fillStyle=e,ctx.beginPath(),ctx.arc(t.x,t.y,3.5,0,2*Math.PI),ctx.fill()}function drawSelfLoop(t,e,n,a,o,s){const l=t.x+RT.SELF_W,c=[t,{x:l,y:t.y},{x:l,y:e.y},e];drawRoutedPath(c,n,a,o,s),drawArrowHead(c,a),drawSourceDot(t,n)}function drawRipples(){S.ripples=S.ripples.filter((t=>t.a>.015)),S.ripples.forEach((t=>{ctx.strokeStyle=`rgba(96,165,250,${t.a})`,ctx.lineWidth=1.5,ctx.beginPath(),ctx.arc(t.x,t.y,t.r,0,2*Math.PI),ctx.stroke(),t.r+=5,t.a*=.88}))}function drawServerIcon(t,e,n,a){ctx.save(),ctx.strokeStyle="rgba(255,255,255,0.75)",ctx.lineWidth=1.2;const o=(a-4)/3;for(let a=0;a<3;a++){const s=e+a*(o+2);ctx.fillStyle="rgba(255,255,255,0.08)",ctx.beginPath(),roundRect(ctx,t,s,n,o,2),ctx.fill(),ctx.stroke(),ctx.fillStyle=0===a?"#34d399":1===a?"#fbbf24":"rgba(255,255,255,0.2)",ctx.beginPath(),ctx.arc(t+n-5,s+o/2,2,0,2*Math.PI),ctx.fill()}ctx.restore()}function drawDBIcon(t,e,n,a){ctx.save(),ctx.strokeStyle="rgba(255,255,255,0.55)",ctx.lineWidth=1;const o=n,s=.28*a,l=t+o/2;ctx.fillStyle="rgba(255,255,255,0.08)",ctx.beginPath(),ctx.ellipse(l,e+s/2,o/2,s/2,0,0,2*Math.PI),ctx.fill(),ctx.stroke(),ctx.beginPath(),ctx.moveTo(t,e+s/2),ctx.lineTo(t,e+a-s/2),ctx.moveTo(t+o,e+s/2),ctx.lineTo(t+o,e+a-s/2),ctx.stroke(),ctx.fillStyle="rgba(255,255,255,0.05)",ctx.beginPath(),ctx.ellipse(l,e+a-s/2,o/2,s/2,0,0,2*Math.PI),ctx.fill(),ctx.stroke(),[.42,.65].forEach((t=>{ctx.beginPath(),ctx.ellipse(l,e+a*t,o/2,s/2,0,Math.PI,2*Math.PI),ctx.stroke()})),ctx.restore()}function drawUniqueIcon(t,e,n,a){ctx.save(),ctx.strokeStyle="#60a5fa",ctx.lineWidth=1.2,ctx.beginPath(),ctx.moveTo(t+2,e),ctx.lineTo(t+2,e+.6*a),ctx.quadraticCurveTo(t+2,e+a,t+n/2,e+a),ctx.quadraticCurveTo(t+n-2,e+a,t+n-2,e+.6*a),ctx.lineTo(t+n-2,e),ctx.stroke(),ctx.restore()}function drawLockIcon(t,e,n,a){ctx.save(),ctx.strokeStyle="#fbbf24",ctx.lineWidth=1,ctx.beginPath(),ctx.arc(t+n/2,e+.38*a,.32*n,Math.PI,0),ctx.stroke(),ctx.fillStyle="rgba(251,191,36,0.12)",roundRectFill(ctx,t+.12*n,e+.46*a,.76*n,.54*a,2),ctx.beginPath(),roundRect(ctx,t+.12*n,e+.46*a,.76*n,.54*a,2),ctx.stroke(),ctx.restore()}function toWorld(t,e){return{x:(t-S.panX)/S.zoom,y:(e-S.panY)/S.zoom}}function hitTable(t,e){const n=Object.keys(S.tables);for(let a=n.length-1;a>=0;a--){const o=S.tables[n[a]];if(t>=o.x&&t<=o.x+o.w&&e>=o.y&&e<=o.y+o.h)return n[a]}return null}function hitRow(t,e,n){const a=S.tables[n];if(!a)return-1;const o=e-a.y-L.TABLE_HDR_H-L.TABLE_FOOT_PAD/2;if(o<0)return-1;const s=Math.floor(o/L.TABLE_ROW_H);return s>=0&&s<a.columns.length?s:-1}function zoomBy(t){S.zoom=Math.max(.1,Math.min(10,S.zoom*t)),updateZoomLabel(),scheduleRender()}function updateTooltip(t,e,n,a){const o=document.getElementById("tooltip");if(!e)return void(o.style.display="none");const s=S.tables[e],l=hitRow(n,a,e);if(l<0)return void(o.style.display="none");const c=s.columns[l];if(!c)return void(o.style.display="none");const r=c.validations,i=c.conversions;let x=`<div class="tt-name">${c.name}</div>`;x+=`<div class="tt-type">${c.type}${c.isPK?" · 🔑 Primary Key":""}${c.isFK?" · 🔗 Foreign Key":""}${c.isVirtual?' · <span style="color:#f59e0b">V Virtual</span>':""}</div>`;const d=[];r.required&&d.push('<span class="tt-badge req">required</span>'),r.unique&&d.push('<span class="tt-badge uniq">unique</span>'),r.email&&d.push('<span class="tt-badge email">email</span>'),void 0!==r.min&&d.push(`<span class="tt-badge">min: ${r.min}</span>`),void 0!==r.max&&d.push(`<span class="tt-badge">max: ${r.max}</span>`),void 0!==r.minLength&&d.push(`<span class="tt-badge">minLen: ${r.minLength}</span>`),void 0!==r.maxLength&&d.push(`<span class="tt-badge">maxLen: ${r.maxLength}</span>`),r.enum&&d.push(`<span class="tt-badge enum">[${r.enum.join(", ")}]</span>`),d.length&&(x+=`<div class="tt-section"><strong>Validations</strong><br>${d.join("")}</div>`);const u=[];if(i.trim&&u.push('<span class="tt-badge">trim</span>'),i.trimStart&&u.push('<span class="tt-badge">trimStart</span>'),i.trimEnd&&u.push('<span class="tt-badge">trimEnd</span>'),i.toLowerCase&&u.push('<span class="tt-badge">toLowerCase</span>'),i.toUpperCase&&u.push('<span class="tt-badge">toUpperCase</span>'),i.encryption&&u.push('<span class="tt-badge enc">encrypted</span>'),i.hashing&&u.push('<span class="tt-badge hash">hashed</span>'),i.defaults?.defaultFun&&u.push('<span class="tt-badge def">defaultFun</span>'),i.defaults?.defaultValue&&u.push(`<span class="tt-badge def">default: ${JSON.stringify(i.defaults.defaultValue)}</span>`),u.length&&(x+=`<div class="tt-section"><strong>Conversions</strong><br>${u.join("")}</div>`),c.isFK&&c.targetColl){x+=`<div class="tt-section tt-fk">→ ${[c.targetInst,c.targetDb,c.targetColl].filter(Boolean).join(".")}${c.targetCol?"."+c.targetCol:""}</div>`}o.innerHTML=x,o.style.display="block";const h=t.clientX+18,f=t.clientY-10;o.style.left=h+"px",o.style.top=f+"px",requestAnimationFrame((()=>{const t=o.getBoundingClientRect();t.right>window.innerWidth-8&&(o.style.left=h-t.width-28+"px"),t.bottom>window.innerHeight-8&&(o.style.top=f-t.height+10+"px")}))}function exportPNG(){let t=1/0,e=1/0,n=-1/0,a=-1/0;Object.keys(S.instances).forEach((o=>{const s=instBounds(o);t=Math.min(t,s.x),e=Math.min(e,s.y),n=Math.max(n,s.x+s.w),a=Math.max(a,s.y+s.h)}));const o=n-t+120,s=a-e+120,l=document.createElement("canvas");l.width=2*o,l.height=2*s;l.getContext("2d").setTransform(2,0,0,2,0,0);const c=S.panX,r=S.panY,i=S.zoom;S.panX=60-t,S.panY=60-e,S.zoom=1,canvas.width=2*o,canvas.height=2*s,canvas.style.width=o+"px",canvas.style.height=s+"px",ctx.setTransform(2,0,0,2,0,0),S.vw=o,S.vh=s,render();const x=document.createElement("a");x.download="entity-diagram.png",x.href=canvas.toDataURL("image/png"),x.click(),S.panX=c,S.panY=r,S.zoom=i,resize(),scheduleRender()}function exportSVG(){let t=1/0,e=1/0,n=-1/0,a=-1/0;Object.keys(S.instances).forEach((o=>{const s=instBounds(o);t=Math.min(t,s.x),e=Math.min(e,s.y),n=Math.max(n,s.x+s.w),a=Math.max(a,s.y+s.h)}));const o=n-t+120,s=a-e+120,l=60-t,c=60-e,r=[];r.push(`<svg xmlns="http://www.w3.org/2000/svg" width="${o}" height="${s}" viewBox="0 0 ${o} ${s}">`),r.push("<style>text{font-family:'JetBrains Mono',monospace;}</style>"),r.push(`<rect width="${o}" height="${s}" fill="${C.bg}"/>`);const i=L.GRID_SIZE;for(let t=i;t<o;t+=i)for(let e=i;e<s;e+=i)r.push(`<circle cx="${t}" cy="${e}" r="0.7" fill="rgba(48,54,61,0.6)"/>`);Object.keys(S.instances).forEach((t=>{const e=instBounds(t),n=e.x+l,a=e.y+c;r.push(`<rect x="${n}" y="${a}" width="${e.w}" height="${e.h}" rx="${L.INST_R}" fill="#0d1423" stroke="#3b82f6" stroke-width="1.5" opacity="0.95"/>`),r.push(`<rect x="${n}" y="${a}" width="${e.w}" height="${L.INST_HDR_H}" rx="${L.INST_R}" fill="#1d4ed8"/>`),r.push(`<text x="${n+14}" y="${a+L.INST_HDR_H/2+5}" fill="white" font-weight="bold" font-size="13">${t.toUpperCase()}</text>`)})),Object.keys(S.databases).forEach((t=>{const e=dbBounds(t),n=S.databases[t],a=e.x+l,o=e.y+c;r.push(`<rect x="${a}" y="${o}" width="${e.w}" height="${e.h}" rx="${L.DB_R}" fill="#161b22" stroke="#2d333b"/>`),r.push(`<rect x="${a}" y="${o}" width="${e.w}" height="${L.DB_HDR_H}" rx="${L.DB_R}" fill="#1c2128"/>`),r.push(`<text x="${a+14}" y="${o+L.DB_HDR_H/2+5}" fill="#c9d1d9" font-weight="bold" font-size="11">${n.name}</text>`)})),S.relations.forEach((t=>{const e=S.tables[t.fromTable],n=S.tables[t.toTable];if(!e||!n)return;const{p1:a,p2:o}=connPoints(e,n,t.fromCol,t.toCol),s=`g_${t.idx}`;r.push(`<defs><linearGradient id="${s}" x1="0%" y1="0%" x2="100%" y2="0%"><stop offset="0%" stop-color="${t.color[0]}"/><stop offset="100%" stop-color="${t.color[1]}"/></linearGradient></defs>`),r.push(`<circle cx="${a.x+l}" cy="${a.y+c}" r="3.5" fill="${t.color[0]}" opacity="0.6"/>`),r.push(`<path d="M${a.x+l} ${a.y+c} L${o.x+l} ${o.y+c}" stroke="url(#${s})" stroke-width="1.5" fill="none" opacity="0.6"/>`)})),Object.values(S.tables).forEach((t=>{const e=t.x+l,n=t.y+c;r.push(`<rect x="${e}" y="${n}" width="${t.w}" height="${t.h}" rx="${L.TABLE_R}" fill="#161b22" stroke="#30363d"/>`),r.push(`<rect x="${e}" y="${n}" width="${t.w}" height="${L.TABLE_HDR_H}" rx="${L.TABLE_R}" fill="#1f2937"/>`),r.push(`<rect x="${e+L.TABLE_R}" y="${n}" width="${t.w-2*L.TABLE_R}" height="2" fill="#3b82f6"/>`),r.push(`<text x="${e+12}" y="${n+26}" fill="#f0f6fc" font-weight="bold" font-size="12">${esc(t.coll)}</text>`),r.push(`<line x1="${e}" y1="${n+L.TABLE_HDR_H}" x2="${e+t.w}" y2="${n+L.TABLE_HDR_H}" stroke="#21262d"/>`),t.columns.forEach(((a,o)=>{const s=n+L.TABLE_HDR_H+o*L.TABLE_ROW_H+L.TABLE_ROW_H/2+4,l=10*(a.depth||0),c=a.name.includes(".")?a.name.split(".").pop():a.name,i=a.isGroupHeader?c+(a.isArray?" []":" {}"):c+(a.isArray?" []":""),x=a.isGroupHeader?"#8b949e":a.isPK?C.pk:a.isFK?C.fk:a.isVirtual?C.virtual:"#e6edf3";if(r.push(`<text x="${e+14+l}" y="${s}" fill="${x}" font-size="${a.isGroupHeader?9:10}">${esc(i)}</text>`),!a.isGroupHeader){const n=C.types[a.type]||"#94a3b8";r.push(`<text x="${e+t.w-8}" y="${s}" fill="${n}" font-size="9" text-anchor="end">${esc(a.type||"?")}</text>`),a.isVirtual&&r.push(`<text x="${e+t.w-8-6*(a.type||"?").length-10}" y="${s}" fill="#f59e0b" font-size="9" text-anchor="end" font-weight="bold">V</text>`)}}))})),r.push("</svg>");const x=new Blob([r.join("\n")],{type:"image/svg+xml"}),d=document.createElement("a");d.download="entity-diagram.svg",d.href=URL.createObjectURL(x),d.click()}function exportJSON(){const t={};Object.entries(S.tables).forEach((([e,n])=>{t[e]={x:n.x,y:n.y,w:n.w,h:n.h}}));const e={entities:S.rawData,relations:S.relations.map((t=>({from:`${t.fromTable} . ${t.fromCol}`,to:`${t.toTable} . ${t.toCol}`}))),layout:t,camera:{panX:S.panX,panY:S.panY,zoom:S.zoom}},n=new Blob([JSON.stringify(e,null,2)],{type:"application/json"}),a=document.createElement("a");a.download="entity-diagram.json",a.href=URL.createObjectURL(n),a.click()}function esc(t){return String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function roundRect(t,e,n,a,o,s){const[l,c,r,i]=Array.isArray(s)?s:[s,s,s,s];t.beginPath(),t.moveTo(e+l,n),t.arcTo(e+a,n,e+a,n+o,c),t.arcTo(e+a,n+o,e,n+o,r),t.arcTo(e,n+o,e,n,i),t.arcTo(e,n,e+a,n,l),t.closePath()}function roundRectFill(t,e,n,a,o,s,l){void 0!==l&&(t.fillStyle=l),roundRect(t,e,n,a,o,s),t.fill()}function updateZoomLabel(){document.getElementById("zoom-display").textContent=Math.round(100*S.zoom)+"%"}function loadDiagramData(t){window.DIAGRAM_DATA=t,parseData(t),scheduleRender();const e=document.getElementById("loader");e&&(e.classList.add("hidden"),setTimeout((()=>e.remove()),500)),window.onDiagramLoaded?.()}async function init(){if(resize(),window.addEventListener("message",(function(t){t.data&&"LOAD_DIAGRAM_DATA"===t.data.type&&loadDiagramData(t.data.schemas)})),window.parent&&window.parent!==window){window.parent.postMessage({type:"DIAGRAM_READY"},"*");const t=document.getElementById("loader");t&&t.remove()}else try{const t=await fetch("./data.json");if(!t.ok)throw new Error(`HTTP ${t.status}`);loadDiagramData(await t.json())}catch(t){console.error("Failed to load data.json:",t),document.querySelector(".loader-text").textContent=`Error: ${t.message}`}}canvas.addEventListener("mousedown",(t=>{t.preventDefault();const{x:e,y:n}=toWorld(t.clientX,t.clientY-40),a=hitConnHandle(e,n);if(a){const{rel:o,segIdx:s}=a,l=o._wpts,c=Math.abs(l[s].y-l[s+1].y)<1;return S.connDrag={rel:o,segIdx:s,isHoriz:c,startWx:e,startWy:n,origPts:l.map((t=>({x:t.x,y:t.y})))},S.mouseDown=!0,S.mouseStartX=t.clientX,S.mouseStartY=t.clientY,S.mouseMoved=!1,S.mouseTarget=null,void(canvas.style.cursor=c?"ns-resize":"ew-resize")}const o=hitTable(e,n);if(S.mouseDown=!0,S.mouseStartX=t.clientX,S.mouseStartY=t.clientY,S.mouseMoved=!1,S.mouseTarget=o,o){const e=S.tables[o];S.dragging={type:"table",key:o,ox:e.x,oy:e.y,sx:t.clientX,sy:t.clientY},canvas.style.cursor="grabbing"}else S.dragging={type:"canvas",opx:S.panX,opy:S.panY,sx:t.clientX,sy:t.clientY},canvas.style.cursor="grabbing"})),canvas.addEventListener("mousemove",(t=>{const{x:e,y:n}=toWorld(t.clientX,t.clientY-40);if(!S.mouseDown){const a=hitTable(e,n),o=a?hitRow(e,n,a):-1,s=a!==S.hovTable||o!==S.hovRow;S.hovTable=a,S.hovRow=o;const l=a?null:hitConnHandle(e,n),c=S.hovConn;S.hovConn=l||null;const r=S.hovConn?.rel!==c?.rel||S.hovConn?.segIdx!==c?.segIdx;if((s||r)&&scheduleRender(),a)canvas.style.cursor="pointer";else if(l){const t=l.rel._wpts,e=Math.abs(t[l.segIdx].y-t[l.segIdx+1].y)<1;canvas.style.cursor=e?"ns-resize":"ew-resize"}else canvas.style.cursor="default";return void updateTooltip(t,a,e,n)}const a=t.clientX-S.mouseStartX,o=t.clientY-S.mouseStartY;if(!S.mouseMoved&&(Math.abs(a)>3||Math.abs(o)>3)&&(S.mouseMoved=!0),S.mouseMoved)if(S.connDrag){const{rel:t,segIdx:a,isHoriz:o,startWx:s,startWy:l,origPts:c}=S.connDrag,r=c.map((t=>({x:t.x,y:t.y})));if(o){const t=c[a].y+(n-l);r[a].y=t,r[a+1].y=t}else{const t=c[a].x+(e-s);r[a].x=t,r[a+1].x=t}t._pinWpts=r,t._wpts=r,scheduleRender()}else if("table"===S.dragging?.type){const e=S.dragging;S.tables[e.key].x=e.ox+(t.clientX-e.sx)/S.zoom,S.tables[e.key].y=e.oy+(t.clientY-e.sy)/S.zoom,S.relations.forEach((t=>{t.fromTable!==e.key&&t.toTable!==e.key||(t._pinWpts=null)})),invalidateLayout(),scheduleRender()}else if("canvas"===S.dragging?.type){const e=S.dragging;S.panX=e.opx+(t.clientX-e.sx),S.panY=e.opy+(t.clientY-e.sy),scheduleRender()}})),canvas.addEventListener("mouseup",(t=>{if(S.connDrag)return S.connDrag=null,S.mouseDown=!1,void(canvas.style.cursor=S.hovConn?S.hovConn.rel._wpts&&Math.abs(S.hovConn.rel._wpts[S.hovConn.segIdx]?.y-S.hovConn.rel._wpts[S.hovConn.segIdx+1]?.y)<1?"ns-resize":"ew-resize":S.hovTable?"pointer":"default");if(!S.mouseMoved&&S.mouseTarget){const t=S.mouseTarget,e=S.selected;if(S.selected=e===t?null:t,S._gradCache.clear(),S.selected){const e=S.tables[t],n=e.x+e.w/2,a=e.y+e.h/2;for(let t=0;t<4;t++)setTimeout((()=>{S.ripples.push({x:n,y:a,r:8+22*t,a:.9-.18*t})}),90*t)}startAnimation()}S.mouseDown=!1,S.dragging=null,canvas.style.cursor=S.hovTable?"pointer":"default"})),canvas.addEventListener("mouseleave",(()=>{S.hovTable=null,S.hovRow=-1,S.hovConn=null,S.connDrag=null,document.getElementById("tooltip").style.display="none",scheduleRender()})),canvas.addEventListener("dblclick",(t=>{const{x:e,y:n}=toWorld(t.clientX,t.clientY-40),a=hitConnSeg(e,n);a&&(a._pinWpts=null,scheduleRender())})),canvas.addEventListener("wheel",(t=>{if(t.preventDefault(),t.metaKey||t.ctrlKey){const e=t.deltaY<0?1.1:1/1.1,n=Math.max(.1,Math.min(10,S.zoom*e)),a=t.clientX,o=t.clientY-40;S.panX=a-(a-S.panX)*(n/S.zoom),S.panY=o-(o-S.panY)*(n/S.zoom),S.zoom=n,updateZoomLabel(),scheduleRender()}else S.panX-=t.deltaX,S.panY-=t.deltaY,scheduleRender()}),{passive:!1}),document.addEventListener("keydown",(t=>{"Escape"===t.key&&(S.selected=null,scheduleRender()),(t.metaKey||t.ctrlKey)&&"="===t.key&&(t.preventDefault(),zoomBy(1.2)),(t.metaKey||t.ctrlKey)&&"-"===t.key&&(t.preventDefault(),zoomBy(1/1.2)),(t.metaKey||t.ctrlKey)&&"0"===t.key&&(t.preventDefault(),centerView(),scheduleRender())})),document.getElementById("btn-fit").addEventListener("click",(()=>{centerView(),scheduleRender()})),document.getElementById("btn-zoom-in").addEventListener("click",(()=>{zoomBy(1.2)})),document.getElementById("btn-zoom-out").addEventListener("click",(()=>{zoomBy(1/1.2)})),document.getElementById("btn-deselect").addEventListener("click",(()=>{S.selected=null,scheduleRender()})),document.getElementById("btn-arrange").addEventListener("click",(()=>{autoArrange(),invalidateLayout(),scheduleRender()})),document.getElementById("btn-png").addEventListener("click",(()=>{exportPNG(),document.getElementById("export-menu").classList.remove("open")})),document.getElementById("btn-svg").addEventListener("click",(()=>{exportSVG(),document.getElementById("export-menu").classList.remove("open")})),window.loadFromGlobal=function(){window.DIAGRAM_DATA&&loadDiagramData(window.DIAGRAM_DATA)},init();
1
+ window.DIAGRAM_DATA=null;const ACCENT=[12,83,164];function ac(t=1,e=1){const[n,a,o]=ACCENT,s=Math.min(255,Math.round(n*t)),l=Math.min(255,Math.round(a*t)),c=Math.min(255,Math.round(o*t));return e<1?`rgba(${s},${l},${c},${e})`:"#"+[s,l,c].map((t=>t.toString(16).padStart(2,"0"))).join("")}const DARK_C={bg:"#0d1117",grid:"rgba(48,54,61,0.35)",instBg:"rgba(6,9,15,0.98)",instBorder:"#303539",instHeader1:"#131920",instHeader2:"#0e1318",dbBg:"rgba(22,27,34,0.97)",dbBorder:"#424a4e",dbHeader1:"#1c2128",dbHeader2:"#22272e",tableBg:"#0d1117",tableBgHov:"#141414",tableBgSel:"#0a0a0a",tableBorder:"#424a4e",tableBorderHov:ac(1.44,1),tableHeader1:"#1f2937",tableHeader2:"#111827",tableHdrLine:ac(1,.5),tableHeaderSel1:ac(1.18),tableHeaderSel2:ac(.52),tableAccent:ac(),tableAccentSel:ac(1.44),text:"#e6edf3",textMuted:"#8b949e",textDim:"#484f58",pk:"#fbbf24",fk:"#22d3ee",virtual:"#975d02",required:"#f87171",glow:"rgba(96,165,250,0.6)",types:{string:"#60a5fa",number:"#a78bfa",boolean:"#34d399",date:"#fb923c",objectId:"#fbbf24",file:"#c084fc",files:"#c084fc"},rels:[["#60a5fa","#22d3ee"],["#f472b6","#c084fc"],["#34d399","#60a5fa"],["#fbbf24","#fb923c"],["#a78bfa","#f472b6"],["#22d3ee","#34d399"]]},LIGHT_C=Object.assign({},DARK_C,{bg:"#eef2f7",grid:"rgba(12,83,164,0.10)",instBg:"rgba(255,255,255,0.98)",instBorder:"#cdd6e2",instHeader1:"#eef3fa",instHeader2:"#e3eaf3",dbBg:"rgba(255,255,255,0.98)",dbBorder:"#cdd6e2",dbHeader1:"#f3f7fc",dbHeader2:"#e9eff6",tableBg:"#ffffff",tableBgHov:"#f3f7fc",tableBgSel:"#eaf1fb",tableBorder:"#cdd6e2",tableHeader1:"#eef3fa",tableHeader2:"#e3eaf3",text:"#1b2330",textMuted:"#4a5563",textDim:"#7a8694"});let C=DARK_C;function applyDiagramTheme(t){if(C=t?LIGHT_C:DARK_C,document.documentElement.classList.toggle("light",!!t),"function"==typeof scheduleRender)try{scheduleRender()}catch(t){}}const L={TABLE_MIN_W:230,TABLE_MAX_W:320,TABLE_HDR_H:40,TABLE_ROW_H:27,TABLE_PAD_X:12,TABLE_FOOT_PAD:6,TABLE_R:8,DB_HDR_H:44,DB_PAD_X:40,DB_PAD_Y:68,DB_GAP_X:56,DB_GAP_Y:48,DB_R:12,INST_HDR_H:52,INST_PAD_X:52,INST_PAD_Y:68,INST_GAP:120,INST_R:16,GRID_SIZE:40,TENSION_MIN:60,TENSION_MAX:220},canvas=document.getElementById("canvas"),ctx=canvas.getContext("2d"),S={rawData:[],tables:{},databases:{},instances:{},relations:[],panX:0,panY:0,zoom:1,vw:0,vh:0,selected:null,hovTable:null,hovRow:-1,dragging:null,mouseDown:!1,mouseStartX:0,mouseStartY:0,mouseMoved:!1,mouseTarget:null,hovConn:null,connDrag:null,raf:null,pulseT:0,ripples:[],animating:!1,_headerObsCache:null,_layoutVersion:0,_gradCache:new Map,_lastPulse:-1};function invalidateLayout(){S._layoutVersion++,S._headerObsCache=null,S._gradCache.clear()}function resize(){const t=window.devicePixelRatio||1;S.vw=window.innerWidth,S.vh=window.innerHeight-40,canvas.style.width=S.vw+"px",canvas.style.height=S.vh+"px",canvas.width=S.vw*t,canvas.height=S.vh*t,ctx.setTransform(t,0,0,t,0,0),scheduleRender()}function parseData(t){S.rawData=t,S.tables={},S.databases={},S.instances={},S.relations=[],t.forEach((t=>{const e=t.instanceName||"default",n=t.databaseName||"default",a=t.collectionName,o=`${e}\0${n}\0${a}`,s=t.calculated?.primaryKey||null,l=buildColumns(t.properties||{},s);S.tables[o]={key:o,inst:e,db:n,coll:a,columns:l,pk:s,x:0,y:0,w:0,h:0},S.instances[e]||(S.instances[e]={name:e,databases:[]});const c=`${e}\0${n}`;S.databases[c]||(S.databases[c]={key:c,inst:e,name:n,tables:[]},S.instances[e].databases.push(c)),S.databases[c].tables.includes(o)||S.databases[c].tables.push(o)}));let e=0;t.forEach((t=>{const n=t.instanceName||"default",a=t.databaseName||"default",o=t.collectionName,s=`${n}\0${a}\0${o}`;!function t(o,l){Object.entries(o).forEach((([o,c])=>{const r=l?l+"."+o:o;if(Array.isArray(c)&&(c=c[0]??{}),"object"!=typeof c||!c)return;if(!(c.__type||c.collection||c.table||c.instance||c.isVirtualField))return void t(c,r);const i=c.collection||c.table;if(!i)return;const x=`${c.instance||n}\0${c.database||a}\0${i}`;if(!S.tables[x])return;const d=S.tables[x];S.relations.push({fromTable:s,fromCol:r,toTable:x,toCol:d.pk||"_id",color:C.rels[e%C.rels.length],idx:e}),e++}))}(t.properties||{},"")})),computeTableSizes(),autoArrange()}function buildColumns(t,e,n,a){n=n||"",a=a||0;const o=[];return Object.entries(t).forEach((([t,s])=>{const l=n?n+"."+t:t;let c=!1;if(Array.isArray(s)&&(c=!0,s=s[0]??{}),"string"==typeof s)return void o.push({name:l,depth:a,type:s,isArray:c,isPK:l===e,isFK:!1,isVirtual:!1,isAutoIncDB:!1,isAutoIncAM:!1,validations:{},conversions:{},targetInst:null,targetDb:null,targetColl:null,targetCol:null});const r=s||{};if(!(r.__type||r.collection||r.table||r.instance||r.isVirtualField))return o.push({name:l,depth:a,type:"object",isArray:c,isPK:!1,isFK:!1,isVirtual:!1,isAutoIncDB:!1,isAutoIncAM:!1,isGroupHeader:!0,validations:{},conversions:{},targetInst:null,targetDb:null,targetColl:null,targetCol:null}),void o.push(...buildColumns(r,e,l,a+1));const i=r.collection||r.table||null;o.push({name:l,depth:a,type:r.__type||"object",isArray:c,isPK:l===e||!!r.isPrimaryKey,isFK:!!i&&!r.isVirtualField,isVirtual:!!r.isVirtualField,isAutoIncDB:!!r.isAutoIncrementByDB,isAutoIncAM:!!r.isAutoIncrementByAM,validations:r.validations||{},conversions:r.conversions||{},targetInst:r.instance||null,targetDb:r.database||null,targetColl:i,targetCol:r.column||null})})),o}function computeTableSizes(){ctx.save(),Object.values(S.tables).forEach((t=>{ctx.font='bold 13px "JetBrains Mono", monospace';let e=ctx.measureText(t.coll).width+64;ctx.font='11px "JetBrains Mono", monospace',t.columns.forEach((t=>{const n=10*(t.depth||0),a=t.name.includes(".")?t.name.split(".").pop():t.name,o=t.isGroupHeader?a+(t.isArray?" []":" {}"):a+(t.isArray?" []":""),s=t.isGroupHeader?"":t.type||"",l=ctx.measureText(o).width+n+(s?ctx.measureText(s).width+14:0)+(t.isGroupHeader?0:13*countIcons(t))+36;l>e&&(e=l)})),t.w=Math.max(L.TABLE_MIN_W,Math.min(L.TABLE_MAX_W,Math.ceil(e))),t.h=L.TABLE_HDR_H+t.columns.length*L.TABLE_ROW_H+L.TABLE_FOOT_PAD})),ctx.restore()}function countIcons(t){const e=t.validations,n=t.conversions;let a=1;return t.isVirtual&&a++,e.required&&a++,e.unique&&a++,e.email&&a++,e.enum&&a++,n.encryption&&a++,n.hashing&&a++,(n.defaults?.defaultFun||n.defaults?.defaultValue)&&a++,a}function autoArrange(){const t=L.DB_GAP_Y;let e=L.INST_PAD_X;Object.values(S.instances).forEach((n=>{let a=L.INST_HDR_H+L.INST_PAD_Y,o=0;n.databases.forEach((n=>{const s=S.databases[n],l=s.tables,c=computeTableLevels(l),r=l.length>0?Math.max(...l.map((t=>c[t]??0))):0,i=Array.from({length:r+1},(()=>[]));l.forEach((t=>i[c[t]??0].push(t)));const x=e+L.INST_PAD_X+L.DB_PAD_X,d=a+L.INST_PAD_Y+L.DB_HDR_H+L.DB_PAD_Y;let u=x,h=0;i.forEach((e=>{if(0!==e.length)for(let n=0;n<e.length;n+=3){const a=e.slice(n,n+3);let o=d,s=0;a.forEach((e=>{const n=S.tables[e];n.x=u,n.y=o,o+=n.h+t,s=Math.max(s,n.w)})),h=Math.max(h,o-d-t),u+=s+180}}));const f=u-x-180;s._layoutW=Math.max(f,L.TABLE_MIN_W)+2*L.DB_PAD_X,s._layoutH=h+L.DB_HDR_H+2*L.DB_PAD_Y,o=Math.max(o,s._layoutW),a+=s._layoutH+t})),e+=o+2*L.INST_PAD_X+L.INST_GAP})),centerView()}function computeTableLevels(t){if(!t||0===t.length)return{};const e=new Set(t),n={},a={};t.forEach((t=>{n[t]=[],a[t]=0})),S.relations.forEach((t=>{t.fromTable!==t.toTable&&e.has(t.fromTable)&&e.has(t.toTable)&&(n[t.fromTable].includes(t.toTable)||(n[t.fromTable].push(t.toTable),a[t.toTable]++))}));const o={};t.forEach((t=>{o[t]=0}));const s={...a},l=t.filter((t=>0===s[t]));let c=0;for(;c<l.length;){const t=l[c++];n[t].forEach((e=>{o[e]=Math.max(o[e],o[t]+1),s[e]--,0===s[e]&&l.push(e)}))}return o}function dbBounds(t){const e=S.databases[t];let n=1/0,a=1/0,o=-1/0,s=-1/0;return e.tables.forEach((t=>{const e=S.tables[t];n=Math.min(n,e.x),a=Math.min(a,e.y),o=Math.max(o,e.x+e.w),s=Math.max(s,e.y+e.h)})),{x:n-L.DB_PAD_X,y:a-L.DB_HDR_H-L.DB_PAD_Y,w:o-n+2*L.DB_PAD_X,h:s-a+L.DB_HDR_H+2*L.DB_PAD_Y}}function instBounds(t){const e=S.instances[t];let n=1/0,a=1/0,o=-1/0,s=-1/0;return e.databases.forEach((t=>{const e=dbBounds(t);n=Math.min(n,e.x),a=Math.min(a,e.y),o=Math.max(o,e.x+e.w),s=Math.max(s,e.y+e.h)})),{x:n-L.INST_PAD_X,y:a-L.INST_HDR_H-L.INST_PAD_Y,w:o-n+2*L.INST_PAD_X,h:s-a+L.INST_HDR_H+2*L.INST_PAD_Y}}function centerView(){if(!Object.keys(S.instances).length)return;let t=1/0,e=1/0,n=-1/0,a=-1/0;Object.keys(S.instances).forEach((o=>{const s=instBounds(o);t=Math.min(t,s.x),e=Math.min(e,s.y),n=Math.max(n,s.x+s.w),a=Math.max(a,s.y+s.h)}));const o=n-t,s=a-e;S.zoom=Math.max(.1,Math.min((S.vw-48)/o,(S.vh-48)/s)),S.panX=(S.vw-o*S.zoom)/2-t*S.zoom,S.panY=(S.vh-s*S.zoom)/2-e*S.zoom,updateZoomLabel()}function scheduleRender(){S.animating||requestAnimationFrame(renderFrame)}function startAnimation(){S.animating||(S.animating=!0,requestAnimationFrame(renderFrame))}function renderFrame(t){S.pulseT=(t||0)/1e3;const e=.5*Math.sin(2.5*S.pulseT)+.5,n=S.ripples.length>0||S.selected;n&&Math.abs(e-S._lastPulse)<.005&&!S.ripples.length?requestAnimationFrame(renderFrame):(S._lastPulse=e,render(),n?requestAnimationFrame(renderFrame):S.animating=!1)}function screenToWorldRect(){return{x:-S.panX/S.zoom,y:-S.panY/S.zoom,w:S.vw/S.zoom,h:S.vh/S.zoom}}function isRectVisible(t,e,n,a,o){return t<o.x+o.w&&t+n>o.x&&e<o.y+o.h&&e+a>o.y}function render(){ctx.clearRect(0,0,S.vw,S.vh),ctx.fillStyle=C.bg,ctx.fillRect(0,0,S.vw,S.vh),drawGrid(),ctx.save(),ctx.translate(S.panX,S.panY),ctx.scale(S.zoom,S.zoom);const t=screenToWorldRect();Object.keys(S.instances).forEach((e=>{const n=instBounds(e);isRectVisible(n.x,n.y,n.w,n.h,t)&&drawInstanceBox(e)})),Object.keys(S.databases).forEach((e=>{const n=dbBounds(e);isRectVisible(n.x,n.y,n.w,n.h,t)&&drawDatabaseBox(e)})),Object.values(S.tables).forEach((e=>{isRectVisible(e.x,e.y,e.w,e.h,t)&&drawTable(e)})),drawRelations(t),drawRipples(),ctx.restore()}function drawGrid(){const t=L.GRID_SIZE*S.zoom,e=(S.panX%t+t)%t,n=(S.panY%t+t)%t;ctx.save(),ctx.strokeStyle=C.grid,ctx.lineWidth=.5,ctx.beginPath();for(let n=e;n<S.vw;n+=t)ctx.moveTo(n,0),ctx.lineTo(n,S.vh);for(let e=n;e<S.vh;e+=t)ctx.moveTo(0,e),ctx.lineTo(S.vw,e);ctx.stroke(),ctx.fillStyle="rgba(48,54,61,0.6)",ctx.beginPath();for(let a=e;a<S.vw;a+=t)for(let e=n;e<S.vh;e+=t)ctx.moveTo(a+.7,e),ctx.arc(a,e,.7,0,2*Math.PI);ctx.fill(),ctx.restore()}function drawInstanceBox(t){const e=instBounds(t),n=L.INST_R;ctx.save(),ctx.shadowBlur=0,ctx.beginPath(),roundRect(ctx,e.x,e.y,e.w,e.h,n),ctx.clip(),ctx.fillStyle=C.instBg,ctx.fillRect(e.x,e.y,e.w,e.h);const a=ctx.createLinearGradient(e.x,e.y,e.x+e.w,e.y+e.h);a.addColorStop(0,"rgba(29,78,216,0.06)"),a.addColorStop(1,"rgba(6,182,212,0.04)"),ctx.fillStyle=a,ctx.fillRect(e.x,e.y,e.w,e.h),ctx.restore(),ctx.shadowBlur=0;const o=L.INST_HDR_H;ctx.save(),ctx.beginPath(),roundRect(ctx,e.x,e.y,e.w,o,[n,n,0,0]),ctx.clip();const s=ctx.createLinearGradient(e.x,e.y,e.x+e.w,e.y);s.addColorStop(0,C.instHeader1),s.addColorStop(1,C.instHeader2),ctx.fillStyle=s,ctx.fillRect(e.x,e.y,e.w,o),ctx.fillStyle="rgba(255,255,255,0.04)",ctx.fillRect(e.x,e.y,e.w,1),ctx.restore(),ctx.strokeStyle=C.instBorder,ctx.lineWidth=1,ctx.beginPath(),roundRect(ctx,e.x,e.y,e.w,e.h,n),ctx.stroke(),drawServerIcon(e.x+14,e.y+12,28,28),ctx.fillStyle="#fff",ctx.font='bold 14px "JetBrains Mono", monospace',ctx.textBaseline="middle",ctx.fillText(t.toUpperCase(),e.x+50,e.y+o/2),ctx.font='9px "JetBrains Mono", monospace';const l="INSTANCE",c=ctx.measureText(l).width+14;ctx.fillStyle="rgba(255,255,255,0.1)",roundRectFill(ctx,e.x+e.w-c-10,e.y+o/2-9,c,18,9),ctx.fillStyle="rgba(255,255,255,0.55)",ctx.fillText(l,e.x+e.w-c-3,e.y+o/2),ctx.textBaseline="alphabetic"}function drawDatabaseBox(t){const e=dbBounds(t),n=S.databases[t],a=L.DB_R;ctx.save(),ctx.beginPath(),roundRect(ctx,e.x,e.y,e.w,e.h,a),ctx.clip(),ctx.fillStyle=C.dbBg,ctx.fillRect(e.x,e.y,e.w,e.h),ctx.restore();const o=L.DB_HDR_H;ctx.save(),ctx.beginPath(),roundRect(ctx,e.x,e.y,e.w,o,[a,a,0,0]),ctx.clip();const s=ctx.createLinearGradient(e.x,e.y,e.x+e.w,e.y);s.addColorStop(0,C.dbHeader1),s.addColorStop(1,C.dbHeader2),ctx.fillStyle=s,ctx.fillRect(e.x,e.y,e.w,o),ctx.restore(),ctx.strokeStyle=C.dbBorder,ctx.lineWidth=1,ctx.beginPath(),roundRect(ctx,e.x,e.y,e.w,e.h,a),ctx.stroke(),drawDBIcon(e.x+12,e.y+8,26,26),ctx.fillStyle="#c9d1d9",ctx.font='bold 12px "JetBrains Mono", monospace',ctx.textBaseline="middle",ctx.fillText(n.name,e.x+44,e.y+o/2),ctx.font='9px "JetBrains Mono", monospace',ctx.fillStyle=C.textMuted;const l="DATABASE";ctx.fillText(l,e.x+e.w-ctx.measureText(l).width-10,e.y+o/2),ctx.textBaseline="alphabetic"}function drawTable(t){const{x:e,y:n,w:a,h:o,coll:s,columns:l,key:c}=t,r=S.selected===c,i=S.hovTable===c,x=.5*Math.sin(3*S.pulseT)+.5,d=L.TABLE_R;ctx.save(),ctx.shadowColor=r?ac(1,.4):"rgba(0,0,0,0.5)",ctx.shadowBlur=r?24+12*x:12,ctx.shadowOffsetY=4,ctx.beginPath(),roundRect(ctx,e,n,a,o,d),ctx.fillStyle="transparent",ctx.fill(),ctx.restore(),ctx.save(),ctx.beginPath(),roundRect(ctx,e,n,a,o,d),ctx.clip(),ctx.fillStyle=r?C.tableBgSel:i?C.tableBgHov:C.tableBg,ctx.fillRect(e,n,a,o);const u=c;let h=S._gradCache.get(u);const f=`${e},${n},${a},${o},${r?1:0},${S._layoutVersion}`;if(!h||h.state!==f){const t=ctx.createLinearGradient(e,n,e,n+o);t.addColorStop(0,"rgba(255,255,255,0.025)"),t.addColorStop(1,"rgba(0,0,0,0)");const s=ctx.createLinearGradient(e,n,e,n+L.TABLE_HDR_H);s.addColorStop(0,r?C.tableHeaderSel1:C.tableHeader1),s.addColorStop(1,r?C.tableHeaderSel2:C.tableHeader2);const l=ctx.createLinearGradient(e,n,e+a,n);l.addColorStop(0,r?C.tableAccentSel:C.tableAccent),l.addColorStop(1,ac(r?1.71:.63)),h={state:f,bgOverlay:t,hGrad:s,accentGrad:l},S._gradCache.set(u,h)}ctx.fillStyle=h.bgOverlay,ctx.fillRect(e,n,a,o),ctx.fillStyle=h.hGrad,ctx.fillRect(e,n,a,L.TABLE_HDR_H),ctx.fillStyle=h.accentGrad,ctx.fillRect(e,n,a,2),ctx.restore(),ctx.fillStyle="#f0f6fc",ctx.font='bold 13px "JetBrains Mono", monospace',ctx.textBaseline="middle",ctx.fillText(s,e+L.TABLE_PAD_X,n+L.TABLE_HDR_H/2+1);const y=String(l.length);ctx.font='10px "JetBrains Mono", monospace';const p=ctx.measureText(y).width+10,b=e+a-p-8,m=n+L.TABLE_HDR_H/2-9;ctx.fillStyle="rgba(255,255,255,0.12)",roundRectFill(ctx,b,m,p,18,9),ctx.fillStyle="rgba(255,255,255,0.55)",ctx.fillText(y,b+5,m+9),ctx.textBaseline="alphabetic",ctx.strokeStyle=C.tableHdrLine,ctx.lineWidth=1,ctx.beginPath(),ctx.moveTo(e+1,n+L.TABLE_HDR_H),ctx.lineTo(e+a-1,n+L.TABLE_HDR_H),ctx.stroke(),l.forEach(((e,n)=>drawColumn(t,e,n)));const g=r?.6+.4*x:i?.4:1;ctx.strokeStyle=r?ac(1.44,g):i?C.tableBorderHov:C.tableBorder,ctx.lineWidth=r?2:1,ctx.beginPath(),roundRect(ctx,e,n,a,o,d),ctx.stroke()}function drawColumn(t,e,n){const{x:a,y:o,w:s,key:l}=t,c=o+L.TABLE_HDR_H+L.TABLE_FOOT_PAD/2+n*L.TABLE_ROW_H,r=L.TABLE_ROW_H,i=S.hovTable===l&&S.hovRow===n;S.selected;e.isGroupHeader?(ctx.fillStyle="rgba(139,148,158,0.05)",ctx.fillRect(a+1,c,s-2,r)):e.isVirtual&&(ctx.fillStyle="rgba(245,158,11,0.07)",ctx.fillRect(a+1,c,s-2,r)),i&&(ctx.fillStyle="rgba(96,165,250,0.07)",ctx.fillRect(a+1,c,s-2,r)),e.isGroupHeader?(ctx.fillStyle="rgba(139,148,158,0.2)",ctx.fillRect(a+1,c+2,2,r-4)):e.isPK?(ctx.fillStyle=C.pk,ctx.fillRect(a+1,c+5,3,r-10)):e.isVirtual?(ctx.fillStyle=C.virtual,ctx.fillRect(a+1,c+5,3,r-10)):e.isFK&&(ctx.fillStyle=C.fk,ctx.fillRect(a+1,c+5,3,r-10));const x=10*(e.depth||0),d=e.name.includes(".")?e.name.split(".").pop():e.name,u=e.isGroupHeader?d+(e.isArray?" []":" {}"):d+(e.isArray?" []":"");ctx.font=e.isPK?'bold 11px "JetBrains Mono", monospace':e.isGroupHeader?'bold 10px "JetBrains Mono", monospace':'11px "JetBrains Mono", monospace',ctx.textBaseline="middle",e.isGroupHeader?ctx.fillStyle=C.textMuted:e.isVirtual?ctx.fillStyle=C.virtual:e.isFK?ctx.fillStyle=C.fk:e.isPK?ctx.fillStyle=C.pk:ctx.fillStyle=C.text,ctx.fillText(u,a+L.TABLE_PAD_X+2+x,c+r/2),ctx.textBaseline="alphabetic";let h=a+s-6;if(e.isGroupHeader)return;const f=C.types[e.type]||"#94a3b8",y=e.type||"?";ctx.font='9px "JetBrains Mono", monospace';const p=ctx.measureText(y).width+10;h-=p,roundRectFill(ctx,h,c+(r-16)/2,p,16,8,f+"22"),ctx.strokeStyle=f+"55",ctx.lineWidth=.8,ctx.beginPath(),roundRect(ctx,h,c+(r-16)/2,p,16,8),ctx.stroke(),ctx.fillStyle=f,ctx.textBaseline="middle",ctx.fillText(y,h+5,c+r/2),ctx.textBaseline="alphabetic",h-=4;const b=e.validations,m=e.conversions;e.isVirtual&&(h-=14,ctx.fillStyle="#f59e0b",ctx.font='bold 9px "JetBrains Mono", monospace',ctx.textBaseline="middle",ctx.fillText("V",h+2,c+r/2),ctx.textBaseline="alphabetic"),b.required&&(h-=12,ctx.fillStyle=C.required,ctx.beginPath(),ctx.arc(h+5,c+r/2,3.5,0,2*Math.PI),ctx.fill()),b.unique&&(h-=14,drawUniqueIcon(h,c+(r-12)/2,12,12)),b.email&&(h-=14,ctx.fillStyle="#60a5fa",ctx.font="bold 10px sans-serif",ctx.textBaseline="middle",ctx.fillText("@",h+1,c+r/2),ctx.textBaseline="alphabetic"),b.enum&&(h-=14,ctx.fillStyle="#a78bfa",ctx.font="10px sans-serif",ctx.textBaseline="middle",ctx.fillText("≡",h+1,c+r/2),ctx.textBaseline="alphabetic"),m.encryption&&(h-=14,drawLockIcon(h,c+(r-12)/2,10,12)),m.hashing&&(h-=14,ctx.fillStyle="#f59e0b",ctx.font='bold 10px "JetBrains Mono", monospace',ctx.textBaseline="middle",ctx.fillText("#",h+2,c+r/2),ctx.textBaseline="alphabetic"),(m.defaults?.defaultFun||m.defaults?.defaultValue)&&(h-=14,ctx.fillStyle="#6ee7b7",ctx.font='bold 9px "JetBrains Mono", monospace',ctx.textBaseline="middle",ctx.fillText("D",h+2,c+r/2),ctx.textBaseline="alphabetic"),(e.isAutoIncDB||e.isAutoIncAM)&&(h-=14,ctx.fillStyle="#fb923c",ctx.font='9px "JetBrains Mono", monospace',ctx.textBaseline="middle",ctx.fillText("AI",h,c+r/2),ctx.textBaseline="alphabetic")}function getHeaderObstacles(){if(S._headerObsCache)return S._headerObsCache;const t=[];return Object.keys(S.databases).forEach((e=>{const n=dbBounds(e);t.push({x:n.x,y:n.y,w:n.w,h:L.DB_HDR_H})})),Object.keys(S.instances).forEach((e=>{const n=instBounds(e);t.push({x:n.x,y:n.y,w:n.w,h:L.INST_HDR_H})})),S._headerObsCache=t,t}function findLabelPos(t,e=80,n=[]){const a=1.5*RT.STUB,o=getHeaderObstacles(),s=Object.values(S.tables);function l(t,n){return s.some((a=>t+e>a.x&&t-e<a.x+a.w&&n>a.y-12&&n<a.y+a.h+12))}function c(t,n){return o.some((a=>t+e>a.x&&t-e<a.x+a.w&&n>a.y-8&&n<a.y+a.h+8))}function r(e){let n=0,o=null;for(let s=0;s<t.length-1;s++){const l=t[s],c=t[s+1],r=Math.hypot(c.x-l.x,c.y-l.y);if(r<a)continue;const i=Math.max(2,Math.ceil(r/10));let x=-1,d=0;for(let t=0;t<=i;t++){const a=t/i;if(e(l.x+(c.x-l.x)*a,l.y+(c.y-l.y)*a))x<0&&(x=t),d++;else{if(d>n){n=d;const t=(x+d/2)/i;o={x:l.x+(c.x-l.x)*t,y:l.y+(c.y-l.y)*t}}x=-1,d=0}}if(d>n){n=d;const t=(x+d/2)/i;o={x:l.x+(c.x-l.x)*Math.min(1,t),y:l.y+(c.y-l.y)*Math.min(1,t)}}}return o}const i=r(((t,a)=>!l(t,a)&&!c(t,a)&&!function(t,a){return n.some((n=>t+e>n.x&&t-e<n.x+n.w&&a+13>n.y&&a-13<n.y+n.h))}(t,a)));if(i)return i;const x=r(((t,e)=>!l(t,e)&&!c(t,e)));if(x)return x;let d=t[Math.floor(t.length/2)],u=-1;for(let e=0;e<t.length-1;e++){const n=t[e],o=t[e+1],s=Math.hypot(o.x-n.x,o.y-n.y);s>=a&&s>u&&(u=s,d={x:(n.x+o.x)/2,y:(n.y+o.y)/2})}return d}function spreadOverlappingLines(){const t=RT.GAP+6,e=S.relations.filter((t=>t._wpts&&!t._pinWpts&&t._wpts.length>=6));if(e.length<2)return;const n=getHeaderObstacles(),a=Object.values(S.tables);function o(e,o,s){const l=Math.min(o,s)-8,c=Math.max(o,s)+8;let r=e;for(let o=0;o<10;o++){let o=!1;for(const s of[a,n])for(const n of s)if(!(n.x>=c||n.x+n.w<=l)&&r>n.y-t&&r<n.y+n.h+t){const a=n.y-t,s=n.y+n.h+t;r=Math.abs(e-a)<=Math.abs(e-s)?a:s,o=!0}if(!o)break}return r}function s(e,o,s){const l=Math.min(o,s)-8,c=Math.max(o,s)+8;let r=e;for(let o=0;o<10;o++){let o=!1;for(const s of[a,n])for(const n of s)if(!(n.y>=c||n.y+n.h<=l)&&r>n.x-t&&r<n.x+n.w+t){const a=n.x-t,s=n.x+n.w+t;r=Math.abs(e-a)<=Math.abs(e-s)?a:s,o=!0}if(!o)break}return r}const l=e.map((t=>{const e=t._wpts,n=Math.floor((e.length-1)/2),a=e[n],o=e[n+1];return{a:a,b:o,horiz:Math.abs(a.y-o.y)<1,vert:Math.abs(a.x-o.x)<1}}));function c(t,e,n){const a=t.map(((t,e)=>e));function o(t){return a[t]===t?t:a[t]=o(a[t])}for(let s=0;s<t.length;s++)for(let l=s+1;l<t.length;l++){const c=t[s],r=t[l];if(Math.abs(c.a[e]-r.a[e])>=4)continue;const i=Math.min(c.a[n],c.b[n]),x=Math.max(c.a[n],c.b[n]),d=Math.min(r.a[n],r.b[n]);i<Math.max(r.a[n],r.b[n])&&d<x&&(a[o(s)]=o(l))}const s=new Map;return t.forEach(((t,e)=>{const n=o(e);s.has(n)||s.set(n,[]),s.get(n).push(t)})),[...s.values()].filter((t=>t.length>1))}for(const t of c(l.filter((t=>t.horiz)),"y","x")){const e=t.reduce(((t,e)=>t+e.a.y),0)/t.length;t.forEach(((n,a)=>{const s=o(e+12*(a-(t.length-1)/2),n.a.x,n.b.x)-n.a.y;n.a.y+=s,n.b.y+=s}))}for(const t of c(l.filter((t=>t.vert)),"x","y")){const e=t.reduce(((t,e)=>t+e.a.x),0)/t.length;t.forEach(((n,a)=>{const o=s(e+12*(a-(t.length-1)/2),n.a.y,n.b.y)-n.a.x;n.a.x+=o,n.b.x+=o}))}}function distToSeg(t,e,n,a,o,s){const l=o-n,c=s-a,r=l*l+c*c;if(r<1e-4)return Math.hypot(t-n,e-a);const i=Math.max(0,Math.min(1,((t-n)*l+(e-a)*c)/r));return Math.hypot(t-(n+i*l),e-(a+i*c))}function hitConnHandle(t,e){const n=10/S.zoom;for(const a of S.relations){const o=a._wpts;if(o&&!(o.length<6))for(let s=2;s<=o.length-4;s++){const l=o[s],c=o[s+1];if(Math.hypot(c.x-l.x,c.y-l.y)<30)continue;const r=(l.x+c.x)/2,i=(l.y+c.y)/2;if(Math.hypot(t-r,e-i)<=n)return{rel:a,segIdx:s}}}return null}function hitConnSeg(t,e){const n=6/S.zoom;for(const a of S.relations){const o=a._wpts;if(o)for(let s=0;s<o.length-1;s++){const l=o[s],c=o[s+1];if(distToSeg(t,e,l.x,l.y,c.x,c.y)<=n)return a}}return null}function drawRelations(t){const e=getHeaderObstacles();S.relations.forEach((t=>{t._labelMid=null;const n=S.tables[t.fromTable],a=S.tables[t.toTable];if(!n||!a||t.fromTable===t.toTable)return void(t._wpts=null);if(t._pinWpts)return void(t._wpts=t._pinWpts);if(t._wpts&&t._wptVersion===S._layoutVersion)return;const{p1:o,s1:s,p2:l,s2:c}=connPoints(n,a,t.fromCol,t.toCol);t._wpts=computeWaypoints(o,s,l,c,n.key,a.key,e),t._wptVersion=S._layoutVersion})),spreadOverlappingLines(),ctx.font='9px "JetBrains Mono", monospace';const n=[];S.relations.forEach((t=>{if(S.selected!==t.fromTable&&S.selected!==t.toTable)return;if(!t._wpts)return;const e=ctx.measureText(`${t.fromCol} → ${t.toCol}`).width+12,a=findLabelPos(t._wpts,e/2,n);t._labelMid=a,n.push({x:a.x-e/2-6,y:a.y-10-6,w:e+12,h:30})}));const a=t?S.relations.filter((e=>{if(S.selected===e.fromTable||S.selected===e.toTable)return!0;const n=S.tables[e.fromTable],a=S.tables[e.toTable];return!(!n||!a)&&(isRectVisible(n.x,n.y,n.w,n.h,t)||isRectVisible(a.x,a.y,a.w,a.h,t))})):S.relations;a.forEach((t=>drawRelation(t,!1))),a.forEach((t=>drawRelation(t,!0)))}function drawRelation(t,e){const n=S.tables[t.fromTable],a=S.tables[t.toTable];if(!n||!a)return;const o=S.selected===t.fromTable||S.selected===t.toTable,s=.5*Math.sin(2.5*S.pulseT)+.5,l=o?1.8+.8*s:1.4;if(ctx.save(),ctx.globalAlpha=o?1:.22,t.fromTable===t.toTable){const s={x:n.x+n.w,y:colRowY(n,t.fromCol)},c={x:a.x+a.w,y:colRowY(a,t.toCol)};if(e||drawSelfLoop(s,c,t.color[0],t.color[1],l,o),e&&o){const e=`${t.fromCol} → ${t.toCol}`;ctx.font='9px "JetBrains Mono", monospace';const a=ctx.measureText(e).width+12,o=n.x+n.w+48,l=(s.y+c.y)/2;roundRectFill(ctx,o-a/2,l-10,a,18,9,"rgba(13,17,23,0.85)"),ctx.strokeStyle=t.color[0]+"66",ctx.lineWidth=.8,ctx.beginPath(),roundRect(ctx,o-a/2,l-10,a,18,9),ctx.stroke(),ctx.fillStyle=t.color[0],ctx.textBaseline="middle",ctx.fillText(e,o-a/2+6,l),ctx.textBaseline="alphabetic"}return void ctx.restore()}const c=t._wpts;if(c){if(!e&&(drawRoutedPath(c,t.color[0],t.color[1],l,o),drawArrowHead(c,t.color[1]),drawSourceDot(c[0],t.color[0]),(S.hovConn?.rel===t||o)&&c.length>=6))for(let e=2;e<=c.length-4;e++){const n=c[e],a=c[e+1];if(Math.hypot(a.x-n.x,a.y-n.y)<30)continue;const o=(n.x+a.x)/2,s=(n.y+a.y)/2,l=S.hovConn?.rel===t&&S.hovConn?.segIdx===e;ctx.save(),ctx.globalAlpha=1,ctx.beginPath(),ctx.arc(o,s,l?6:4,0,2*Math.PI),ctx.fillStyle=l?"#ffffff":"rgba(255,255,255,0.75)",ctx.fill(),ctx.strokeStyle=t.color[0],ctx.lineWidth=1.5/S.zoom,ctx.stroke(),ctx.restore()}if(e&&o){const e=`${t.fromCol} → ${t.toCol}`;ctx.font='9px "JetBrains Mono", monospace';const n=ctx.measureText(e).width+12,a=t._labelMid||findLabelPos(c,n/2);roundRectFill(ctx,a.x-n/2,a.y-10,n,18,9,"rgba(13,17,23,0.9)"),ctx.strokeStyle=t.color[0]+"66",ctx.lineWidth=.8,ctx.beginPath(),roundRect(ctx,a.x-n/2,a.y-10,n,18,9),ctx.stroke(),ctx.fillStyle=t.color[0],ctx.textBaseline="middle",ctx.fillText(e,a.x-n/2+6,a.y),ctx.textBaseline="alphabetic"}ctx.restore()}else ctx.restore()}function colRowY(t,e){if(!e)return t.y+t.h/2;const n=t.columns.findIndex((t=>t.name===e));return n<0?t.y+t.h/2:t.y+L.TABLE_HDR_H+L.TABLE_FOOT_PAD/2+n*L.TABLE_ROW_H+L.TABLE_ROW_H/2}function connPoints(t,e,n,a){const o=colRowY(t,n),s=colRowY(e,a),l=t.x+t.w/2,c=e.x+e.w/2;let r,i,x,d;const u=e.x-(t.x+t.w),h=t.x-(e.x+e.w);if(u>=-4)r={x:t.x+t.w,y:o},i="r",x={x:e.x,y:s},d="l";else if(h>=-4)r={x:t.x,y:o},i="l",x={x:e.x+e.w,y:s},d="r";else{e.y+e.h/2-(t.y+t.h/2)>=0?(r={x:l,y:t.y+t.h},i="d",x={x:c,y:e.y},d="u"):(r={x:l,y:t.y},i="u",x={x:c,y:e.y+e.h},d="d")}return{p1:r,s1:i,p2:x,s2:d}}window.addEventListener("resize",resize);const RT={STUB:36,GAP:22,CORNER_R:9,SELF_W:56};function addDir(t,e,n){return{x:t.x+("r"===e?n:"l"===e?-n:0),y:t.y+("d"===e?n:"u"===e?-n:0)}}function segHitsTable(t,e,n,a,o,s){const l=o.x-s,c=o.y-s,r=o.x+o.w+s,i=o.y+o.h+s;if(t===n){const n=Math.min(e,a),o=Math.max(e,a);return t>l&&t<r&&o>c&&n<i}const x=Math.min(t,n),d=Math.max(t,n);return e>c&&e<i&&d>l&&x<r}function pathHitsObs(t,e){for(let n=0;n<t.length-1;n++){const a=t[n],o=t[n+1];for(const t of e)if(segHitsTable(a.x,a.y,o.x,o.y,t,RT.GAP))return!0}return!1}function findClearHBand(t,e,n){const a=Math.min(t.x,e.x)-RT.GAP,o=Math.max(t.x,e.x)+RT.GAP,s=n.filter((t=>t.x<o&&t.x+t.w>a));if(!s.length)return(t.y+e.y)/2;const l=(t.y+e.y)/2,c=RT.GAP+8,r=[];for(const t of s)r.push(t.y-c),r.push(t.y+t.h+c);const i=r.filter((t=>!s.some((e=>t>e.y-c&&t<e.y+e.h+c))));return i.length>0?i.reduce(((t,e)=>Math.abs(e-l)<Math.abs(t-l)?e:t)):Math.max(...s.map((t=>t.y+t.h)))+c}function findClearVBand(t,e,n){const a=Math.min(t.y,e.y)-RT.GAP,o=Math.max(t.y,e.y)+RT.GAP,s=n.filter((t=>t.y<o&&t.y+t.h>a));if(!s.length)return(t.x+e.x)/2;const l=(t.x+e.x)/2,c=RT.GAP+8,r=[];for(const t of s)r.push(t.x-c),r.push(t.x+t.w+c);const i=r.filter((t=>!s.some((e=>t>e.x-c&&t<e.x+e.w+c))));return i.length>0?i.reduce(((t,e)=>Math.abs(e-l)<Math.abs(t-l)?e:t)):Math.max(...s.map((t=>t.x+t.w)))+c}function routeHH(t,e,n,a,o){const s=[(e.x+n.x)/2,e.x+RT.STUB,n.x-RT.STUB,e.x+.35*(n.x-e.x),e.x+.65*(n.x-e.x)];for(const l of s){const s=[t,e,{x:l,y:e.y},{x:l,y:n.y},n,a];if(!pathHitsObs(s,o))return s}const l=Math.min(e.x,n.x)-RT.GAP,c=Math.max(e.x,n.x)+RT.GAP,r=o.filter((t=>t.x<c&&t.x+t.w>l)),i=RT.GAP+8,x=[];for(const t of r)x.push(t.y-i),x.push(t.y+t.h+i);const d=(e.y+n.y)/2;x.sort(((t,e)=>Math.abs(t-d)-Math.abs(e-d)));for(const s of x){const l=[t,e,{x:e.x,y:s},{x:n.x,y:s},n,a];if(!pathHitsObs(l,o))return l}const u=x.length?x[0]:findClearHBand(e,n,o);return[t,e,{x:e.x,y:u},{x:n.x,y:u},n,a]}function routeVV(t,e,n,a,o){const s=[(e.y+n.y)/2,e.y+RT.STUB,n.y-RT.STUB];for(const l of s){const s=[t,e,{x:e.x,y:l},{x:n.x,y:l},n,a];if(!pathHitsObs(s,o))return s}const l=Math.min(e.y,n.y)-RT.GAP,c=Math.max(e.y,n.y)+RT.GAP,r=o.filter((t=>t.y<c&&t.y+t.h>l)),i=RT.GAP+8,x=[];for(const t of r)x.push(t.x-i),x.push(t.x+t.w+i);const d=(e.x+n.x)/2;x.sort(((t,e)=>Math.abs(t-d)-Math.abs(e-d)));for(const s of x){const l=[t,e,{x:s,y:e.y},{x:s,y:n.y},n,a];if(!pathHitsObs(l,o))return l}const u=x.length?x[0]:findClearVBand(e,n,o);return[t,e,{x:u,y:e.y},{x:u,y:n.y},n,a]}function routeL(t,e,n,a,o){const s={x:e.x,y:n.y},l={x:n.x,y:e.y},c=[t,e,s,n,a];if(!pathHitsObs(c,o))return c;const r=[t,e,l,n,a];if(!pathHitsObs(r,o))return r;const i=findClearVBand(e,n,o);return[t,e,{x:i,y:e.y},{x:i,y:n.y},n,a]}function computeWaypoints(t,e,n,a,o,s,l=[]){const c=Object.values(S.tables).filter((t=>t.key!==o&&t.key!==s)),r=l.length?[...c,...l]:c,i=addDir(t,e,RT.STUB),x=addDir(n,a,RT.STUB),d="r"===e||"l"===e,u="r"===a||"l"===a;return d&&u?routeHH(t,i,x,n,r):d||u?routeL(t,i,x,n,r):routeVV(t,i,x,n,r)}function drawRoutedPath(t,e,n,a,o){if(t.length<2)return;const s=t[0],l=t[t.length-1],c=ctx.createLinearGradient(s.x,s.y,l.x,l.y);c.addColorStop(0,e),c.addColorStop(1,n),o&&(ctx.shadowColor=e+"aa",ctx.shadowBlur=10),ctx.strokeStyle=c,ctx.lineWidth=a,ctx.lineCap="round",ctx.lineJoin="round",ctx.beginPath(),ctx.moveTo(s.x,s.y);for(let e=1;e<t.length-1;e++){const n=t[e-1],a=t[e],o=t[e+1],s=Math.hypot(a.x-n.x,a.y-n.y),l=Math.hypot(o.x-a.x,o.y-a.y);if(s<.5||l<.5){ctx.lineTo(a.x,a.y);continue}const c=Math.min(RT.CORNER_R,s/2,l/2),r=a.x-c*(a.x-n.x)/s,i=a.y-c*(a.y-n.y)/s,x=a.x+c*(o.x-a.x)/l,d=a.y+c*(o.y-a.y)/l;ctx.lineTo(r,i),ctx.quadraticCurveTo(a.x,a.y,x,d)}ctx.lineTo(l.x,l.y),ctx.stroke(),ctx.shadowBlur=0}function drawArrowHead(t,e){const n=t.length;if(n<2)return;const a=t[n-2],o=t[n-1],s=Math.atan2(o.y-a.y,o.x-a.x);ctx.fillStyle=e,ctx.beginPath(),ctx.moveTo(o.x,o.y),ctx.lineTo(o.x-10*Math.cos(s-.42),o.y-10*Math.sin(s-.42)),ctx.lineTo(o.x-10*Math.cos(s+.42),o.y-10*Math.sin(s+.42)),ctx.closePath(),ctx.fill()}function drawSourceDot(t,e){ctx.fillStyle=e,ctx.beginPath(),ctx.arc(t.x,t.y,3.5,0,2*Math.PI),ctx.fill()}function drawSelfLoop(t,e,n,a,o,s){const l=t.x+RT.SELF_W,c=[t,{x:l,y:t.y},{x:l,y:e.y},e];drawRoutedPath(c,n,a,o,s),drawArrowHead(c,a),drawSourceDot(t,n)}function drawRipples(){S.ripples=S.ripples.filter((t=>t.a>.015)),S.ripples.forEach((t=>{ctx.strokeStyle=`rgba(96,165,250,${t.a})`,ctx.lineWidth=1.5,ctx.beginPath(),ctx.arc(t.x,t.y,t.r,0,2*Math.PI),ctx.stroke(),t.r+=5,t.a*=.88}))}function drawServerIcon(t,e,n,a){ctx.save(),ctx.strokeStyle="rgba(255,255,255,0.75)",ctx.lineWidth=1.2;const o=(a-4)/3;for(let a=0;a<3;a++){const s=e+a*(o+2);ctx.fillStyle="rgba(255,255,255,0.08)",ctx.beginPath(),roundRect(ctx,t,s,n,o,2),ctx.fill(),ctx.stroke(),ctx.fillStyle=0===a?"#34d399":1===a?"#fbbf24":"rgba(255,255,255,0.2)",ctx.beginPath(),ctx.arc(t+n-5,s+o/2,2,0,2*Math.PI),ctx.fill()}ctx.restore()}function drawDBIcon(t,e,n,a){ctx.save(),ctx.strokeStyle="rgba(255,255,255,0.55)",ctx.lineWidth=1;const o=n,s=.28*a,l=t+o/2;ctx.fillStyle="rgba(255,255,255,0.08)",ctx.beginPath(),ctx.ellipse(l,e+s/2,o/2,s/2,0,0,2*Math.PI),ctx.fill(),ctx.stroke(),ctx.beginPath(),ctx.moveTo(t,e+s/2),ctx.lineTo(t,e+a-s/2),ctx.moveTo(t+o,e+s/2),ctx.lineTo(t+o,e+a-s/2),ctx.stroke(),ctx.fillStyle="rgba(255,255,255,0.05)",ctx.beginPath(),ctx.ellipse(l,e+a-s/2,o/2,s/2,0,0,2*Math.PI),ctx.fill(),ctx.stroke(),[.42,.65].forEach((t=>{ctx.beginPath(),ctx.ellipse(l,e+a*t,o/2,s/2,0,Math.PI,2*Math.PI),ctx.stroke()})),ctx.restore()}function drawUniqueIcon(t,e,n,a){ctx.save(),ctx.strokeStyle="#60a5fa",ctx.lineWidth=1.2,ctx.beginPath(),ctx.moveTo(t+2,e),ctx.lineTo(t+2,e+.6*a),ctx.quadraticCurveTo(t+2,e+a,t+n/2,e+a),ctx.quadraticCurveTo(t+n-2,e+a,t+n-2,e+.6*a),ctx.lineTo(t+n-2,e),ctx.stroke(),ctx.restore()}function drawLockIcon(t,e,n,a){ctx.save(),ctx.strokeStyle="#fbbf24",ctx.lineWidth=1,ctx.beginPath(),ctx.arc(t+n/2,e+.38*a,.32*n,Math.PI,0),ctx.stroke(),ctx.fillStyle="rgba(251,191,36,0.12)",roundRectFill(ctx,t+.12*n,e+.46*a,.76*n,.54*a,2),ctx.beginPath(),roundRect(ctx,t+.12*n,e+.46*a,.76*n,.54*a,2),ctx.stroke(),ctx.restore()}function toWorld(t,e){return{x:(t-S.panX)/S.zoom,y:(e-S.panY)/S.zoom}}function hitTable(t,e){const n=Object.keys(S.tables);for(let a=n.length-1;a>=0;a--){const o=S.tables[n[a]];if(t>=o.x&&t<=o.x+o.w&&e>=o.y&&e<=o.y+o.h)return n[a]}return null}function hitRow(t,e,n){const a=S.tables[n];if(!a)return-1;const o=e-a.y-L.TABLE_HDR_H-L.TABLE_FOOT_PAD/2;if(o<0)return-1;const s=Math.floor(o/L.TABLE_ROW_H);return s>=0&&s<a.columns.length?s:-1}function zoomBy(t){S.zoom=Math.max(.1,Math.min(10,S.zoom*t)),updateZoomLabel(),scheduleRender()}function updateTooltip(t,e,n,a){const o=document.getElementById("tooltip");if(!e)return void(o.style.display="none");const s=S.tables[e],l=hitRow(n,a,e);if(l<0)return void(o.style.display="none");const c=s.columns[l];if(!c)return void(o.style.display="none");const r=c.validations,i=c.conversions;let x=`<div class="tt-name">${c.name}</div>`;x+=`<div class="tt-type">${c.type}${c.isPK?" · 🔑 Primary Key":""}${c.isFK?" · 🔗 Foreign Key":""}${c.isVirtual?' · <span style="color:#f59e0b">V Virtual</span>':""}</div>`;const d=[];r.required&&d.push('<span class="tt-badge req">required</span>'),r.unique&&d.push('<span class="tt-badge uniq">unique</span>'),r.email&&d.push('<span class="tt-badge email">email</span>'),void 0!==r.min&&d.push(`<span class="tt-badge">min: ${r.min}</span>`),void 0!==r.max&&d.push(`<span class="tt-badge">max: ${r.max}</span>`),void 0!==r.minLength&&d.push(`<span class="tt-badge">minLen: ${r.minLength}</span>`),void 0!==r.maxLength&&d.push(`<span class="tt-badge">maxLen: ${r.maxLength}</span>`),r.enum&&d.push(`<span class="tt-badge enum">[${r.enum.join(", ")}]</span>`),d.length&&(x+=`<div class="tt-section"><strong>Validations</strong><br>${d.join("")}</div>`);const u=[];if(i.trim&&u.push('<span class="tt-badge">trim</span>'),i.trimStart&&u.push('<span class="tt-badge">trimStart</span>'),i.trimEnd&&u.push('<span class="tt-badge">trimEnd</span>'),i.toLowerCase&&u.push('<span class="tt-badge">toLowerCase</span>'),i.toUpperCase&&u.push('<span class="tt-badge">toUpperCase</span>'),i.encryption&&u.push('<span class="tt-badge enc">encrypted</span>'),i.hashing&&u.push('<span class="tt-badge hash">hashed</span>'),i.defaults?.defaultFun&&u.push('<span class="tt-badge def">defaultFun</span>'),i.defaults?.defaultValue&&u.push(`<span class="tt-badge def">default: ${JSON.stringify(i.defaults.defaultValue)}</span>`),u.length&&(x+=`<div class="tt-section"><strong>Conversions</strong><br>${u.join("")}</div>`),c.isFK&&c.targetColl){x+=`<div class="tt-section tt-fk">→ ${[c.targetInst,c.targetDb,c.targetColl].filter(Boolean).join(".")}${c.targetCol?"."+c.targetCol:""}</div>`}o.innerHTML=x,o.style.display="block";const h=t.clientX+18,f=t.clientY-10;o.style.left=h+"px",o.style.top=f+"px",requestAnimationFrame((()=>{const t=o.getBoundingClientRect();t.right>window.innerWidth-8&&(o.style.left=h-t.width-28+"px"),t.bottom>window.innerHeight-8&&(o.style.top=f-t.height+10+"px")}))}function exportPNG(){let t=1/0,e=1/0,n=-1/0,a=-1/0;Object.keys(S.instances).forEach((o=>{const s=instBounds(o);t=Math.min(t,s.x),e=Math.min(e,s.y),n=Math.max(n,s.x+s.w),a=Math.max(a,s.y+s.h)}));const o=n-t+120,s=a-e+120,l=document.createElement("canvas");l.width=2*o,l.height=2*s;l.getContext("2d").setTransform(2,0,0,2,0,0);const c=S.panX,r=S.panY,i=S.zoom;S.panX=60-t,S.panY=60-e,S.zoom=1,canvas.width=2*o,canvas.height=2*s,canvas.style.width=o+"px",canvas.style.height=s+"px",ctx.setTransform(2,0,0,2,0,0),S.vw=o,S.vh=s,render();const x=document.createElement("a");x.download="entity-diagram.png",x.href=canvas.toDataURL("image/png"),x.click(),S.panX=c,S.panY=r,S.zoom=i,resize(),scheduleRender()}function exportSVG(){let t=1/0,e=1/0,n=-1/0,a=-1/0;Object.keys(S.instances).forEach((o=>{const s=instBounds(o);t=Math.min(t,s.x),e=Math.min(e,s.y),n=Math.max(n,s.x+s.w),a=Math.max(a,s.y+s.h)}));const o=n-t+120,s=a-e+120,l=60-t,c=60-e,r=[];r.push(`<svg xmlns="http://www.w3.org/2000/svg" width="${o}" height="${s}" viewBox="0 0 ${o} ${s}">`),r.push("<style>text{font-family:'JetBrains Mono',monospace;}</style>"),r.push(`<rect width="${o}" height="${s}" fill="${C.bg}"/>`);const i=L.GRID_SIZE;for(let t=i;t<o;t+=i)for(let e=i;e<s;e+=i)r.push(`<circle cx="${t}" cy="${e}" r="0.7" fill="rgba(48,54,61,0.6)"/>`);Object.keys(S.instances).forEach((t=>{const e=instBounds(t),n=e.x+l,a=e.y+c;r.push(`<rect x="${n}" y="${a}" width="${e.w}" height="${e.h}" rx="${L.INST_R}" fill="#0d1423" stroke="#3b82f6" stroke-width="1.5" opacity="0.95"/>`),r.push(`<rect x="${n}" y="${a}" width="${e.w}" height="${L.INST_HDR_H}" rx="${L.INST_R}" fill="#1d4ed8"/>`),r.push(`<text x="${n+14}" y="${a+L.INST_HDR_H/2+5}" fill="white" font-weight="bold" font-size="13">${t.toUpperCase()}</text>`)})),Object.keys(S.databases).forEach((t=>{const e=dbBounds(t),n=S.databases[t],a=e.x+l,o=e.y+c;r.push(`<rect x="${a}" y="${o}" width="${e.w}" height="${e.h}" rx="${L.DB_R}" fill="#161b22" stroke="#2d333b"/>`),r.push(`<rect x="${a}" y="${o}" width="${e.w}" height="${L.DB_HDR_H}" rx="${L.DB_R}" fill="#1c2128"/>`),r.push(`<text x="${a+14}" y="${o+L.DB_HDR_H/2+5}" fill="#c9d1d9" font-weight="bold" font-size="11">${n.name}</text>`)})),S.relations.forEach((t=>{const e=S.tables[t.fromTable],n=S.tables[t.toTable];if(!e||!n)return;const{p1:a,p2:o}=connPoints(e,n,t.fromCol,t.toCol),s=`g_${t.idx}`;r.push(`<defs><linearGradient id="${s}" x1="0%" y1="0%" x2="100%" y2="0%"><stop offset="0%" stop-color="${t.color[0]}"/><stop offset="100%" stop-color="${t.color[1]}"/></linearGradient></defs>`),r.push(`<circle cx="${a.x+l}" cy="${a.y+c}" r="3.5" fill="${t.color[0]}" opacity="0.6"/>`),r.push(`<path d="M${a.x+l} ${a.y+c} L${o.x+l} ${o.y+c}" stroke="url(#${s})" stroke-width="1.5" fill="none" opacity="0.6"/>`)})),Object.values(S.tables).forEach((t=>{const e=t.x+l,n=t.y+c;r.push(`<rect x="${e}" y="${n}" width="${t.w}" height="${t.h}" rx="${L.TABLE_R}" fill="#161b22" stroke="#30363d"/>`),r.push(`<rect x="${e}" y="${n}" width="${t.w}" height="${L.TABLE_HDR_H}" rx="${L.TABLE_R}" fill="#1f2937"/>`),r.push(`<rect x="${e+L.TABLE_R}" y="${n}" width="${t.w-2*L.TABLE_R}" height="2" fill="#3b82f6"/>`),r.push(`<text x="${e+12}" y="${n+26}" fill="#f0f6fc" font-weight="bold" font-size="12">${esc(t.coll)}</text>`),r.push(`<line x1="${e}" y1="${n+L.TABLE_HDR_H}" x2="${e+t.w}" y2="${n+L.TABLE_HDR_H}" stroke="#21262d"/>`),t.columns.forEach(((a,o)=>{const s=n+L.TABLE_HDR_H+o*L.TABLE_ROW_H+L.TABLE_ROW_H/2+4,l=10*(a.depth||0),c=a.name.includes(".")?a.name.split(".").pop():a.name,i=a.isGroupHeader?c+(a.isArray?" []":" {}"):c+(a.isArray?" []":""),x=a.isGroupHeader?"#8b949e":a.isPK?C.pk:a.isFK?C.fk:a.isVirtual?C.virtual:"#e6edf3";if(r.push(`<text x="${e+14+l}" y="${s}" fill="${x}" font-size="${a.isGroupHeader?9:10}">${esc(i)}</text>`),!a.isGroupHeader){const n=C.types[a.type]||"#94a3b8";r.push(`<text x="${e+t.w-8}" y="${s}" fill="${n}" font-size="9" text-anchor="end">${esc(a.type||"?")}</text>`),a.isVirtual&&r.push(`<text x="${e+t.w-8-6*(a.type||"?").length-10}" y="${s}" fill="#f59e0b" font-size="9" text-anchor="end" font-weight="bold">V</text>`)}}))})),r.push("</svg>");const x=new Blob([r.join("\n")],{type:"image/svg+xml"}),d=document.createElement("a");d.download="entity-diagram.svg",d.href=URL.createObjectURL(x),d.click()}function exportJSON(){const t={};Object.entries(S.tables).forEach((([e,n])=>{t[e]={x:n.x,y:n.y,w:n.w,h:n.h}}));const e={entities:S.rawData,relations:S.relations.map((t=>({from:`${t.fromTable} . ${t.fromCol}`,to:`${t.toTable} . ${t.toCol}`}))),layout:t,camera:{panX:S.panX,panY:S.panY,zoom:S.zoom}},n=new Blob([JSON.stringify(e,null,2)],{type:"application/json"}),a=document.createElement("a");a.download="entity-diagram.json",a.href=URL.createObjectURL(n),a.click()}function esc(t){return String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function roundRect(t,e,n,a,o,s){const[l,c,r,i]=Array.isArray(s)?s:[s,s,s,s];t.beginPath(),t.moveTo(e+l,n),t.arcTo(e+a,n,e+a,n+o,c),t.arcTo(e+a,n+o,e,n+o,r),t.arcTo(e,n+o,e,n,i),t.arcTo(e,n,e+a,n,l),t.closePath()}function roundRectFill(t,e,n,a,o,s,l){void 0!==l&&(t.fillStyle=l),roundRect(t,e,n,a,o,s),t.fill()}function updateZoomLabel(){document.getElementById("zoom-display").textContent=Math.round(100*S.zoom)+"%"}function loadDiagramData(t){window.DIAGRAM_DATA=t,parseData(t),scheduleRender();const e=document.getElementById("loader");e&&(e.classList.add("hidden"),setTimeout((()=>e.remove()),500)),window.onDiagramLoaded?.()}async function init(){resize(),window.addEventListener("message",(function(t){t.data&&"LOAD_DIAGRAM_DATA"===t.data.type&&loadDiagramData(t.data.schemas),t.data&&"SET_THEME"===t.data.type&&applyDiagramTheme(t.data.payload&&"LIGHT"===t.data.payload.theme)}));try{applyDiagramTheme("LIGHT"===localStorage.getItem("THEME"))}catch(t){}if(window.parent&&window.parent!==window){window.parent.postMessage({type:"DIAGRAM_READY"},"*");const t=document.getElementById("loader");t&&t.remove()}else try{const t=await fetch("./data.json");if(!t.ok)throw new Error(`HTTP ${t.status}`);loadDiagramData(await t.json())}catch(t){console.error("Failed to load data.json:",t),document.querySelector(".loader-text").textContent=`Error: ${t.message}`}}canvas.addEventListener("mousedown",(t=>{t.preventDefault();const{x:e,y:n}=toWorld(t.clientX,t.clientY-40),a=hitConnHandle(e,n);if(a){const{rel:o,segIdx:s}=a,l=o._wpts,c=Math.abs(l[s].y-l[s+1].y)<1;return S.connDrag={rel:o,segIdx:s,isHoriz:c,startWx:e,startWy:n,origPts:l.map((t=>({x:t.x,y:t.y})))},S.mouseDown=!0,S.mouseStartX=t.clientX,S.mouseStartY=t.clientY,S.mouseMoved=!1,S.mouseTarget=null,void(canvas.style.cursor=c?"ns-resize":"ew-resize")}const o=hitTable(e,n);if(S.mouseDown=!0,S.mouseStartX=t.clientX,S.mouseStartY=t.clientY,S.mouseMoved=!1,S.mouseTarget=o,o){const e=S.tables[o];S.dragging={type:"table",key:o,ox:e.x,oy:e.y,sx:t.clientX,sy:t.clientY},canvas.style.cursor="grabbing"}else S.dragging={type:"canvas",opx:S.panX,opy:S.panY,sx:t.clientX,sy:t.clientY},canvas.style.cursor="grabbing"})),canvas.addEventListener("mousemove",(t=>{const{x:e,y:n}=toWorld(t.clientX,t.clientY-40);if(!S.mouseDown){const a=hitTable(e,n),o=a?hitRow(e,n,a):-1,s=a!==S.hovTable||o!==S.hovRow;S.hovTable=a,S.hovRow=o;const l=a?null:hitConnHandle(e,n),c=S.hovConn;S.hovConn=l||null;const r=S.hovConn?.rel!==c?.rel||S.hovConn?.segIdx!==c?.segIdx;if((s||r)&&scheduleRender(),a)canvas.style.cursor="pointer";else if(l){const t=l.rel._wpts,e=Math.abs(t[l.segIdx].y-t[l.segIdx+1].y)<1;canvas.style.cursor=e?"ns-resize":"ew-resize"}else canvas.style.cursor="default";return void updateTooltip(t,a,e,n)}const a=t.clientX-S.mouseStartX,o=t.clientY-S.mouseStartY;if(!S.mouseMoved&&(Math.abs(a)>3||Math.abs(o)>3)&&(S.mouseMoved=!0),S.mouseMoved)if(S.connDrag){const{rel:t,segIdx:a,isHoriz:o,startWx:s,startWy:l,origPts:c}=S.connDrag,r=c.map((t=>({x:t.x,y:t.y})));if(o){const t=c[a].y+(n-l);r[a].y=t,r[a+1].y=t}else{const t=c[a].x+(e-s);r[a].x=t,r[a+1].x=t}t._pinWpts=r,t._wpts=r,scheduleRender()}else if("table"===S.dragging?.type){const e=S.dragging;S.tables[e.key].x=e.ox+(t.clientX-e.sx)/S.zoom,S.tables[e.key].y=e.oy+(t.clientY-e.sy)/S.zoom,S.relations.forEach((t=>{t.fromTable!==e.key&&t.toTable!==e.key||(t._pinWpts=null)})),invalidateLayout(),scheduleRender()}else if("canvas"===S.dragging?.type){const e=S.dragging;S.panX=e.opx+(t.clientX-e.sx),S.panY=e.opy+(t.clientY-e.sy),scheduleRender()}})),canvas.addEventListener("mouseup",(t=>{if(S.connDrag)return S.connDrag=null,S.mouseDown=!1,void(canvas.style.cursor=S.hovConn?S.hovConn.rel._wpts&&Math.abs(S.hovConn.rel._wpts[S.hovConn.segIdx]?.y-S.hovConn.rel._wpts[S.hovConn.segIdx+1]?.y)<1?"ns-resize":"ew-resize":S.hovTable?"pointer":"default");if(!S.mouseMoved&&S.mouseTarget){const t=S.mouseTarget,e=S.selected;if(S.selected=e===t?null:t,S._gradCache.clear(),S.selected){const e=S.tables[t],n=e.x+e.w/2,a=e.y+e.h/2;for(let t=0;t<4;t++)setTimeout((()=>{S.ripples.push({x:n,y:a,r:8+22*t,a:.9-.18*t})}),90*t)}startAnimation()}S.mouseDown=!1,S.dragging=null,canvas.style.cursor=S.hovTable?"pointer":"default"})),canvas.addEventListener("mouseleave",(()=>{S.hovTable=null,S.hovRow=-1,S.hovConn=null,S.connDrag=null,document.getElementById("tooltip").style.display="none",scheduleRender()})),canvas.addEventListener("dblclick",(t=>{const{x:e,y:n}=toWorld(t.clientX,t.clientY-40),a=hitConnSeg(e,n);a&&(a._pinWpts=null,scheduleRender())})),canvas.addEventListener("wheel",(t=>{if(t.preventDefault(),t.metaKey||t.ctrlKey){const e=t.deltaY<0?1.1:1/1.1,n=Math.max(.1,Math.min(10,S.zoom*e)),a=t.clientX,o=t.clientY-40;S.panX=a-(a-S.panX)*(n/S.zoom),S.panY=o-(o-S.panY)*(n/S.zoom),S.zoom=n,updateZoomLabel(),scheduleRender()}else S.panX-=t.deltaX,S.panY-=t.deltaY,scheduleRender()}),{passive:!1}),document.addEventListener("keydown",(t=>{"Escape"===t.key&&(S.selected=null,scheduleRender()),(t.metaKey||t.ctrlKey)&&"="===t.key&&(t.preventDefault(),zoomBy(1.2)),(t.metaKey||t.ctrlKey)&&"-"===t.key&&(t.preventDefault(),zoomBy(1/1.2)),(t.metaKey||t.ctrlKey)&&"0"===t.key&&(t.preventDefault(),centerView(),scheduleRender())})),document.getElementById("btn-fit").addEventListener("click",(()=>{centerView(),scheduleRender()})),document.getElementById("btn-zoom-in").addEventListener("click",(()=>{zoomBy(1.2)})),document.getElementById("btn-zoom-out").addEventListener("click",(()=>{zoomBy(1/1.2)})),document.getElementById("btn-deselect").addEventListener("click",(()=>{S.selected=null,scheduleRender()})),document.getElementById("btn-arrange").addEventListener("click",(()=>{autoArrange(),invalidateLayout(),scheduleRender()})),document.getElementById("btn-png").addEventListener("click",(()=>{exportPNG(),document.getElementById("export-menu").classList.remove("open")})),document.getElementById("btn-svg").addEventListener("click",(()=>{exportSVG(),document.getElementById("export-menu").classList.remove("open")})),window.loadFromGlobal=function(){window.DIAGRAM_DATA&&loadDiagramData(window.DIAGRAM_DATA)},init();
@@ -9,7 +9,11 @@
9
9
  src: url('../../styles/fonts/JetBrainsMonoRegular.ttf');
10
10
  }
11
11
  * { margin: 0; padding: 0; box-sizing: border-box; }
12
- html, body { width: 100%; height: 100%; overflow: hidden; background: #1e1e1e; }
12
+ /* Loading background = the editor's own brand surface, so the blank
13
+ area shown while Monaco loads matches the editor that replaces it
14
+ (no flash). Dark = brand navy #0E1726, light = cool-white #FBFCFE. */
15
+ html, body { width: 100%; height: 100%; overflow: hidden; background: #0E1726; }
16
+ html.light, html.light body { background: #FBFCFE; }
13
17
  #editor { width: 100%; height: 100%; }
14
18
  .highlightMatchedWord {
15
19
  background: #ff0 !important;
@@ -24,24 +28,116 @@
24
28
  top: 50px;
25
29
  right: 9px;
26
30
  z-index: 1000;
27
- background: #2d2d2d;
28
- border: 1px solid #555;
29
- color: #dfdfdf;
31
+ /* Brand navy surface + brand-blue hairline (matches the app's
32
+ panels / zoom button), instead of a flat grey. */
33
+ background: #141c29;
34
+ border: 1px solid rgba(97, 175, 239, 0.28);
35
+ color: #d6e2f2;
30
36
  font-size: 12px;
31
- padding: 2px 7px;
37
+ padding: 2px 8px;
32
38
  border-radius: 3px;
33
39
  cursor: pointer;
34
40
  opacity: 0;
35
41
  pointer-events: none;
36
- font-family: monospace;
37
42
  line-height: 1.5;
38
- transition: opacity 0.2s;
43
+ transition: opacity 0.2s, background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
39
44
  user-select: none;
40
45
  }
41
- #reload-types-btn.visible { opacity: 0.55; pointer-events: auto; }
42
- #reload-types-btn.visible:hover { opacity: 1; background: #3c3c3c; }
46
+ #reload-types-btn.visible { opacity: 0.7; pointer-events: auto; }
47
+ #reload-types-btn.visible:hover {
48
+ opacity: 1;
49
+ background: rgba(12, 83, 164, 0.55);
50
+ border-color: rgba(97, 175, 239, 0.5);
51
+ color: #ffffff;
52
+ }
43
53
  #reload-types-btn.loading { opacity: 0.3; pointer-events: none; cursor: default; }
54
+ /* LIGHT mode: reload-types + zoom controls */
55
+ html.light #reload-types-btn { background: #eef3fa; border-color: rgba(12,83,164,0.3); color: #1b2330; }
56
+ html.light #reload-types-btn.visible:hover { background: #e3eaf3; }
57
+
58
+ /* Context-menu selected / hovered item — brand blue -> green diagonal
59
+ gradient, matching the app's active nav/menu item. Monaco moves the
60
+ `.focused` class onto the row under the mouse, but we also match
61
+ `.action-item:hover` directly so a plain mouse-over is covered even
62
+ when focus hasn't synced. Monaco's theme colors only accept flat hex,
63
+ so the gradient is applied here as real CSS; the menu.* theme keys in
64
+ index.js still drive the surface/unselected text colors. */
65
+ .monaco-menu .action-item.focused > .action-menu-item,
66
+ .monaco-menu .action-item > .action-menu-item:hover,
67
+ .monaco-menu .action-item:hover > .action-menu-item {
68
+ background: linear-gradient(135deg, #0C53A4 0%, #0A6A5F 55%, #00893F 100%) !important;
69
+ color: #ffffff !important;
70
+ }
71
+ /* Keep all text / icons / shortcut labels in the active row white. */
72
+ .monaco-menu .action-item.focused > .action-menu-item .action-label,
73
+ .monaco-menu .action-item.focused > .action-menu-item .keybinding,
74
+ .monaco-menu .action-item.focused > .action-menu-item .menu-item-check,
75
+ .monaco-menu .action-item.focused > .action-menu-item .submenu-indicator,
76
+ .monaco-menu .action-item:hover > .action-menu-item .action-label,
77
+ .monaco-menu .action-item:hover > .action-menu-item .keybinding,
78
+ .monaco-menu .action-item:hover > .action-menu-item .menu-item-check,
79
+ .monaco-menu .action-item:hover > .action-menu-item .submenu-indicator,
80
+ .monaco-menu .action-item > .action-menu-item:hover .action-label,
81
+ .monaco-menu .action-item > .action-menu-item:hover .keybinding,
82
+ .monaco-menu .action-item > .action-menu-item:hover .menu-item-check,
83
+ .monaco-menu .action-item > .action-menu-item:hover .submenu-indicator {
84
+ color: #ffffff !important;
85
+ }
86
+
87
+ /* Scrollbar — brand blue -> green gradient thumb, matching the app's
88
+ brand scrollbar standard (colors-dark.scss / colors-light.scss).
89
+ Monaco renders its own scrollbar DOM (not native ::-webkit-scrollbar)
90
+ and its theme colors only accept flat hex, so the gradient is applied
91
+ here as real CSS on the slider element. */
92
+ .monaco-scrollable-element > .scrollbar > .slider {
93
+ background: linear-gradient(180deg, #1b3a5c 0%, #18424a 55%, #1a4d33 100%) !important;
94
+ border-radius: 6px !important;
95
+ }
96
+ .monaco-scrollable-element > .scrollbar > .slider:hover,
97
+ .monaco-scrollable-element > .scrollbar > .slider.active {
98
+ background: linear-gradient(180deg, #245f9f 0%, #1d6a5a 55%, #1d6d44 100%) !important;
99
+ }
100
+ /* LIGHT mode scrollbar thumb — soft brand gradient on light track. */
101
+ html.light .monaco-scrollable-element > .scrollbar > .slider {
102
+ background: linear-gradient(180deg, #aebfd6 0%, #9ec3b5 100%) !important;
103
+ }
104
+ html.light .monaco-scrollable-element > .scrollbar > .slider:hover,
105
+ html.light .monaco-scrollable-element > .scrollbar > .slider.active {
106
+ background: linear-gradient(180deg, #6f96c4 0%, #5fa98c 100%) !important;
107
+ }
108
+
109
+ /* Autocomplete (suggest) widget — hovered (non-selected) row. Monaco
110
+ exposes the selected row via the editorSuggestWidget.* theme keys,
111
+ but the plain mouse-hover background is only a CSS class, so brand it
112
+ here. The selected/focused row keeps its brand-blue theme color. */
113
+ .monaco-editor .suggest-widget .monaco-list .monaco-list-row:hover:not(.focused) {
114
+ background: rgba(12, 83, 164, 0.18) !important;
115
+ }
116
+ html.light .monaco-editor .suggest-widget .monaco-list .monaco-list-row:hover:not(.focused) {
117
+ background: rgba(12, 83, 164, 0.10) !important;
118
+ }
119
+
120
+ /* Command palette / quick-open — hovered (non-focused) row. The focused
121
+ row is driven by the quickInputList.* theme keys; only the plain
122
+ mouse-hover background is a CSS class, so brand it to match. */
123
+ .quick-input-widget .monaco-list .monaco-list-row:hover:not(.focused) {
124
+ background: rgba(12, 83, 164, 0.18) !important;
125
+ }
126
+ html.light .quick-input-widget .monaco-list .monaco-list-row:hover:not(.focused) {
127
+ background: rgba(12, 83, 164, 0.10) !important;
128
+ }
44
129
  </style>
130
+ <script>
131
+ // Apply app theme (shared localStorage, same origin); live-switch via SET_THEME message.
132
+ (function () {
133
+ function apply(t) { document.documentElement.classList.toggle('light', t === 'LIGHT'); }
134
+ try { apply(localStorage.getItem('THEME')); } catch (e) {}
135
+ window.addEventListener('message', function (ev) {
136
+ var m = ev.data;
137
+ if (m && m.type === 'SET_THEME') apply(m.payload && m.payload.theme);
138
+ });
139
+ }());
140
+ </script>
45
141
  <script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.52.2/min/vs/loader.min.js"></script>
46
142
  <script>
47
143
  require.config({