@sava-info-systems/api-maker-with-extensions 2.2.1 → 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 (244) 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/data.json +1886 -0
  148. package/dist/assets/public/diagram-viewer/index.html +1228 -0
  149. package/dist/assets/public/diagram-viewer/index.js +1 -0
  150. package/dist/assets/public/monaco-editor/index.html +105 -9
  151. package/dist/assets/public/monaco-editor/index.js +1 -1
  152. package/dist/assets/public/table-viewer/index.html +738 -0
  153. package/dist/assets/public/table-viewer/index.js +1 -0
  154. package/dist/assets/schema-types/InterfaceProps.ts +71 -13
  155. package/dist/assets/schema-types/store-types.ts +2 -0
  156. package/dist/assets/schema-types/types.ts +65 -17
  157. package/dist/assets/styles/bootstrap-overrides.scss +170 -7
  158. package/dist/assets/styles/colors-dark.scss +320 -33
  159. package/dist/assets/styles/colors-light.scss +453 -9
  160. package/dist/assets/styles/out/styles.css.map +1 -1
  161. package/dist/assets/styles/styles.scss +286 -15
  162. package/dist/assets/styles/variables.scss +2 -2
  163. package/dist/bootstrap-overrides.css +1 -0
  164. package/dist/colors-dark.css +1 -0
  165. package/dist/colors-light.css +1 -0
  166. package/dist/{common.912ad7a22e5de63a.js → common.86c514055f788290.js} +1 -1
  167. package/dist/index.html +111 -27
  168. package/dist/main.0ac98a72f1b9c1dd.js +1 -0
  169. package/dist/runtime.0e9fc303007ced09.js +1 -0
  170. package/dist/styles-global.css +1 -0
  171. package/dist/{styles.242217e145c3f7ab.css → styles.c1032bc4f4d7829b.css} +1 -1
  172. package/main.js +1 -1
  173. package/package-sandbox.json +1 -1
  174. package/package.json +9 -9
  175. package/pnpm-workspace.yaml +15 -0
  176. package/sandbox.js +1 -1
  177. package/src/assets/schema-types/store-types.ts +2 -0
  178. package/src/assets/schema-types/types.ts +65 -17
  179. package/src/schema-types/store-types.js +2 -0
  180. package/src/schema-types/types.js +1 -0
  181. package/src/tools/.github/copilot-instructions.md +17 -9
  182. package/src/tools/local-client.js +1 -1
  183. package/dist/1025.a0db9347453600d0.js +0 -1
  184. package/dist/1097.d0d6fd13031aa1f0.js +0 -1
  185. package/dist/148.1dc7d9e0cfe45510.js +0 -1
  186. package/dist/1558.0aae3e5c538d3815.js +0 -1
  187. package/dist/1678.ed1e4a269e0f42bc.js +0 -1
  188. package/dist/1754.691f142aa1eabe28.js +0 -1
  189. package/dist/1928.2b376f7cf66ed038.js +0 -1
  190. package/dist/1957.ab7fda21d9978597.js +0 -1
  191. package/dist/2140.1ce150285aebe3cc.js +0 -1
  192. package/dist/2295.4fa1d4b5ff482f3a.js +0 -1
  193. package/dist/238.891233aa670bdd2b.js +0 -1
  194. package/dist/2473.4b2bb1ad5b1cc99d.js +0 -1
  195. package/dist/2579.6558b67b2d98f116.js +0 -1
  196. package/dist/2714.0d6939336a02dd0a.js +0 -1
  197. package/dist/2735.86437a8639a0c1bb.js +0 -1
  198. package/dist/292.c7c794ff29abbe37.js +0 -1
  199. package/dist/3139.0921102d181c220d.js +0 -1
  200. package/dist/3450.9b5cfc83039b02b5.js +0 -1
  201. package/dist/3664.4a1dd7f16f8b57fb.js +0 -1
  202. package/dist/4110.d108e786ecbdfd1b.js +0 -1
  203. package/dist/4186.ac3e8af0efeeac14.js +0 -1
  204. package/dist/4211.1bfe72ce0b7b2f76.js +0 -1
  205. package/dist/443.7e3946651373e09a.js +0 -1
  206. package/dist/4575.6ad36e53bd72fb6f.js +0 -1
  207. package/dist/4842.e7649d81606ad80d.js +0 -1
  208. package/dist/485.b546656b466cb8a4.js +0 -1
  209. package/dist/5334.72c1c6d89fdf136e.js +0 -1
  210. package/dist/5421.2067001c10533303.js +0 -1
  211. package/dist/5679.4e22239895c7f520.js +0 -1
  212. package/dist/5761.331ca41ab64cca92.js +0 -1
  213. package/dist/5765.2b640be5d177d794.js +0 -1
  214. package/dist/6236.6c0fc469f0c96549.js +0 -1
  215. package/dist/6393.173ecdfc3b022973.js +0 -1
  216. package/dist/6401.374823002788afe7.js +0 -1
  217. package/dist/6650.1d18ee25901d68be.js +0 -1
  218. package/dist/7368.9ebf62d518f8ad7a.js +0 -1
  219. package/dist/7436.640bf5fe48814cbe.js +0 -1
  220. package/dist/7470.b1ce7eedb9cedb2d.js +0 -1
  221. package/dist/7649.ab4744cfe713eec0.js +0 -1
  222. package/dist/7975.578a00ebd1160a1e.js +0 -1
  223. package/dist/7977.afeded970bac8b66.js +0 -1
  224. package/dist/7987.5958ad5b81873058.js +0 -1
  225. package/dist/8125.b2f908096882ed45.js +0 -1
  226. package/dist/8266.aca59a0a04590ab1.js +0 -1
  227. package/dist/8296.a387c28f5702c5e8.js +0 -1
  228. package/dist/885.be44d92efca1aaf6.js +0 -1
  229. package/dist/891.5e08c81e5200413e.js +0 -1
  230. package/dist/9243.08630ad94e1fddba.js +0 -1
  231. package/dist/9575.30ac91b65602c0ca.js +0 -1
  232. package/dist/9939.4b0d758462bcc042.js +0 -1
  233. package/dist/assets/images/logo/API Maker Logo 2021 black.svg +0 -47
  234. package/dist/assets/images/logo/API Maker Logo 2021.svg +0 -49
  235. package/dist/assets/images/logo/logo.svg +0 -41
  236. package/dist/assets/styles/out/bootstrap-overrides.css +0 -2
  237. package/dist/assets/styles/out/colors-dark.css +0 -2
  238. package/dist/assets/styles/out/colors-light.css +0 -2
  239. package/dist/assets/styles/out/monaco-editor-overrides.css +0 -2
  240. package/dist/assets/styles/out/styles.css +0 -2
  241. package/dist/assets/styles/out/variables.css +0 -2
  242. package/dist/favicon.ico +0 -0
  243. package/dist/main.d3c81428418939dc.js +0 -1
  244. package/dist/runtime.2f0f434d4763c970.js +0 -1
@@ -0,0 +1,1228 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8"/>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
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>
18
+ <link rel="preconnect" href="https://fonts.googleapis.com"/>
19
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
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"/>
21
+ <style>
22
+ *, *::before, *::after {
23
+ box-sizing: border-box;
24
+ margin: 0;
25
+ padding: 0;
26
+ }
27
+
28
+ :root {
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;
41
+ --radius: 10px;
42
+ --toolbar-h: 40px;
43
+ }
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
+
71
+ body {
72
+ background: var(--bg);
73
+ color: var(--text);
74
+ font-family: 'Inter', system-ui, sans-serif;
75
+ overflow: hidden;
76
+ height: 100vh;
77
+ width: 100vw;
78
+ }
79
+
80
+ /* ── Toolbar ─────────────────────────────────── */
81
+ #toolbar {
82
+ position: fixed;
83
+ top: 0;
84
+ left: 0;
85
+ right: 0;
86
+ height: var(--toolbar-h);
87
+ background: rgba(14, 23, 38, 0.92);
88
+ backdrop-filter: blur(12px);
89
+ border-bottom: 1px solid var(--border);
90
+ display: flex;
91
+ align-items: center;
92
+ gap: 4px;
93
+ padding: 0 16px;
94
+ z-index: 100;
95
+ user-select: none;
96
+ }
97
+
98
+ #toolbar .logo {
99
+ display: flex;
100
+ align-items: center;
101
+ gap: 10px;
102
+ margin-right: 12px;
103
+ }
104
+
105
+ #toolbar .logo svg {
106
+ flex-shrink: 0;
107
+ }
108
+
109
+ #toolbar .logo-text {
110
+ font-family: 'JetBrains Mono', monospace;
111
+ font-weight: 700;
112
+ font-size: 14px;
113
+ color: var(--text);
114
+ letter-spacing: 0.04em;
115
+ }
116
+
117
+ #toolbar .logo-sub {
118
+ font-size: 10px;
119
+ color: var(--text-muted);
120
+ letter-spacing: 0.08em;
121
+ }
122
+
123
+ .tb-sep {
124
+ width: 1px;
125
+ height: 18px;
126
+ background: var(--border);
127
+ margin: 0 6px;
128
+ flex-shrink: 0;
129
+ }
130
+
131
+ .tb-group {
132
+ display: flex;
133
+ align-items: center;
134
+ gap: 2px;
135
+ }
136
+
137
+ .tb-btn {
138
+ display: flex;
139
+ align-items: center;
140
+ gap: 6px;
141
+ padding: 4px 8px;
142
+ border: 1px solid transparent;
143
+ border-radius: 4px;
144
+ background: transparent;
145
+ color: var(--text-muted);
146
+ font-family: 'Inter', sans-serif;
147
+ font-size: 12px;
148
+ font-weight: 500;
149
+ cursor: pointer;
150
+ transition: all 0.15s;
151
+ white-space: nowrap;
152
+ }
153
+
154
+ .tb-btn:hover {
155
+ background: var(--surface);
156
+ border-color: var(--border);
157
+ color: var(--text);
158
+ }
159
+
160
+ .tb-btn:active {
161
+ transform: scale(0.96);
162
+ }
163
+
164
+ .tb-btn svg {
165
+ flex-shrink: 0;
166
+ }
167
+
168
+ .tb-btn.primary {
169
+ background: linear-gradient(135deg, #0C53A4 0%, #00893F 100%);
170
+ border-color: rgba(97, 175, 239, 0.4);
171
+ color: #fff;
172
+ }
173
+
174
+ .tb-btn.primary:hover {
175
+ background: linear-gradient(135deg, #1565c9 0%, #00a04a 100%);
176
+ }
177
+
178
+ .tb-btn.export-png {
179
+ color: #34d399;
180
+ }
181
+
182
+ .tb-btn.export-svg {
183
+ color: #fb923c;
184
+ }
185
+
186
+ .tb-btn.export-json {
187
+ color: #a78bfa;
188
+ }
189
+
190
+ .tb-btn.export-png:hover {
191
+ border-color: #34d399;
192
+ background: rgba(52, 211, 153, .1);
193
+ }
194
+
195
+ .tb-btn.export-svg:hover {
196
+ border-color: #fb923c;
197
+ background: rgba(251, 146, 60, .1);
198
+ }
199
+
200
+ .tb-btn.export-json:hover {
201
+ border-color: #a78bfa;
202
+ background: rgba(167, 139, 250, .1);
203
+ }
204
+
205
+ #zoom-display {
206
+ font-family: 'JetBrains Mono', monospace;
207
+ font-size: 12px;
208
+ color: var(--text-muted);
209
+ min-width: 44px;
210
+ text-align: center;
211
+ }
212
+
213
+ .tb-spacer {
214
+ flex: 1;
215
+ }
216
+
217
+ /* ── Search trigger ──────────────────────────── */
218
+ #btn-search {
219
+ display: flex;
220
+ align-items: center;
221
+ gap: 8px;
222
+ padding: 4px 10px 4px 8px;
223
+ border: 1px solid var(--border);
224
+ border-radius: 7px;
225
+ background: var(--surface);
226
+ color: var(--text-dim);
227
+ font-size: 12px;
228
+ cursor: pointer;
229
+ transition: all 0.15s;
230
+ min-width: 160px;
231
+ font-family: 'Inter', sans-serif;
232
+ }
233
+
234
+ #btn-search:hover {
235
+ border-color: rgba(59, 130, 246, 0.4);
236
+ color: var(--text-muted);
237
+ }
238
+
239
+ #btn-search .search-hint {
240
+ margin-left: auto;
241
+ font-family: 'JetBrains Mono', monospace;
242
+ font-size: 10px;
243
+ background: rgba(255, 255, 255, 0.05);
244
+ border: 1px solid rgba(255, 255, 255, 0.08);
245
+ border-radius: 4px;
246
+ padding: 1px 5px;
247
+ color: var(--text-dim);
248
+ }
249
+
250
+ /* ── Command Palette ─────────────────────────── */
251
+ #cmd-overlay {
252
+ position: fixed;
253
+ inset: 0;
254
+ background: rgba(0, 0, 0, 0.55);
255
+ backdrop-filter: blur(6px);
256
+ z-index: 500;
257
+ display: none;
258
+ align-items: flex-start;
259
+ justify-content: center;
260
+ padding-top: 72px;
261
+ }
262
+
263
+ #cmd-overlay.open {
264
+ display: flex;
265
+ }
266
+
267
+ #cmd-palette {
268
+ width: 580px;
269
+ max-width: calc(100vw - 32px);
270
+ background: rgba(20, 28, 41, 0.98);
271
+ border: 1px solid rgba(59, 130, 246, 0.25);
272
+ border-radius: 14px;
273
+ box-shadow: 0 32px 80px rgba(0, 0, 0, 0.75),
274
+ 0 0 0 1px rgba(255, 255, 255, 0.03) inset,
275
+ 0 0 40px rgba(59, 130, 246, 0.06);
276
+ overflow: hidden;
277
+ animation: cmd-in 0.2s cubic-bezier(0.16, 1, 0.3, 1) both;
278
+ }
279
+
280
+ @keyframes cmd-in {
281
+ from { opacity: 0; transform: scale(0.96) translateY(-10px); }
282
+ to { opacity: 1; transform: scale(1) translateY(0); }
283
+ }
284
+
285
+ #cmd-input-wrap {
286
+ display: flex;
287
+ align-items: center;
288
+ gap: 10px;
289
+ padding: 12px 14px;
290
+ border-bottom: 1px solid rgba(48, 54, 61, 0.7);
291
+ }
292
+
293
+ #cmd-input-wrap .cmd-search-icon { color: #484f58; flex-shrink: 0; }
294
+
295
+ #cmd-input {
296
+ flex: 1;
297
+ background: transparent;
298
+ border: none;
299
+ outline: none;
300
+ font-family: 'Inter', sans-serif;
301
+ font-size: 14px;
302
+ color: var(--text);
303
+ caret-color: #60a5fa;
304
+ }
305
+
306
+ #cmd-input::placeholder { color: #484f58; }
307
+
308
+ #cmd-results {
309
+ max-height: 340px;
310
+ overflow-y: auto;
311
+ padding: 4px 6px;
312
+ scrollbar-width: thin;
313
+ scrollbar-color: rgba(255,255,255,0.08) transparent;
314
+ }
315
+
316
+ #cmd-results::-webkit-scrollbar { width: 4px; }
317
+ #cmd-results::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.08); border-radius: 2px; }
318
+
319
+ .cmd-section-label {
320
+ padding: 8px 8px 3px;
321
+ font-size: 10px;
322
+ font-weight: 600;
323
+ letter-spacing: 0.1em;
324
+ color: #484f58;
325
+ text-transform: uppercase;
326
+ font-family: 'JetBrains Mono', monospace;
327
+ }
328
+
329
+ .cmd-item {
330
+ display: flex;
331
+ align-items: center;
332
+ gap: 10px;
333
+ padding: 7px 8px;
334
+ border-radius: 8px;
335
+ cursor: pointer;
336
+ transition: background 0.1s;
337
+ }
338
+
339
+ .cmd-item:hover { background: rgba(59, 130, 246, 0.1); }
340
+
341
+ .cmd-item.cmd-active {
342
+ background: rgba(59, 130, 246, 0.16);
343
+ outline: 1px solid rgba(59, 130, 246, 0.25);
344
+ }
345
+
346
+ .cmd-icon {
347
+ width: 28px;
348
+ height: 28px;
349
+ border-radius: 7px;
350
+ display: flex;
351
+ align-items: center;
352
+ justify-content: center;
353
+ flex-shrink: 0;
354
+ }
355
+
356
+ .cmd-icon.t-table { background: rgba(59,130,246,0.14); color: #60a5fa; }
357
+ .cmd-icon.t-column { background: rgba(167,139,250,0.14); color: #a78bfa; }
358
+ .cmd-icon.t-database { background: rgba(34,211,238,0.14); color: #22d3ee; }
359
+ .cmd-icon.t-instance { background: rgba(251,191,36,0.14); color: #fbbf24; }
360
+
361
+ .cmd-info { flex: 1; min-width: 0; }
362
+
363
+ .cmd-name {
364
+ font-size: 13px;
365
+ font-weight: 500;
366
+ color: var(--text);
367
+ white-space: nowrap;
368
+ overflow: hidden;
369
+ text-overflow: ellipsis;
370
+ }
371
+
372
+ .cmd-name mark {
373
+ background: transparent;
374
+ color: #60a5fa;
375
+ font-weight: 700;
376
+ }
377
+
378
+ .cmd-sub {
379
+ font-size: 11px;
380
+ color: #484f58;
381
+ white-space: nowrap;
382
+ overflow: hidden;
383
+ text-overflow: ellipsis;
384
+ margin-top: 1px;
385
+ font-family: 'JetBrains Mono', monospace;
386
+ }
387
+
388
+ .cmd-meta {
389
+ display: flex;
390
+ gap: 4px;
391
+ flex-shrink: 0;
392
+ }
393
+
394
+ .cmd-badge {
395
+ font-family: 'JetBrains Mono', monospace;
396
+ font-size: 9px;
397
+ padding: 2px 5px;
398
+ border-radius: 4px;
399
+ }
400
+
401
+ .cmd-badge.pk { background: rgba(251,191,36,0.13); color: #fbbf24; border: 1px solid rgba(251,191,36,0.25); }
402
+ .cmd-badge.fk { background: rgba(34,211,238,0.13); color: #22d3ee; border: 1px solid rgba(34,211,238,0.25); }
403
+ .cmd-badge.col-type { background: rgba(139,148,158,0.08); color: #6e7681; border: 1px solid rgba(139,148,158,0.15); }
404
+
405
+ #cmd-empty {
406
+ display: none;
407
+ flex-direction: column;
408
+ align-items: center;
409
+ justify-content: center;
410
+ padding: 40px 20px;
411
+ color: #484f58;
412
+ font-size: 13px;
413
+ gap: 12px;
414
+ text-align: center;
415
+ }
416
+
417
+ #cmd-empty svg { opacity: 0.25; }
418
+
419
+ #cmd-empty p { line-height: 1.5; }
420
+ #cmd-empty strong { color: var(--text-muted); }
421
+
422
+ #cmd-footer {
423
+ display: flex;
424
+ align-items: center;
425
+ gap: 14px;
426
+ padding: 7px 14px;
427
+ border-top: 1px solid rgba(48, 54, 61, 0.6);
428
+ font-size: 11px;
429
+ color: #484f58;
430
+ }
431
+
432
+ .cmd-key {
433
+ display: inline-flex;
434
+ align-items: center;
435
+ background: rgba(255, 255, 255, 0.05);
436
+ border: 1px solid rgba(255, 255, 255, 0.07);
437
+ border-radius: 3px;
438
+ padding: 1px 5px;
439
+ font-family: 'JetBrains Mono', monospace;
440
+ font-size: 10px;
441
+ color: #6e7681;
442
+ margin-right: 3px;
443
+ }
444
+
445
+ /* ── Canvas ──────────────────────────────────── */
446
+ #canvas {
447
+ position: fixed;
448
+ top: var(--toolbar-h);
449
+ left: 0;
450
+ cursor: default;
451
+ display: block;
452
+ }
453
+
454
+ /* ── Tooltip ─────────────────────────────────── */
455
+ #tooltip {
456
+ position: fixed;
457
+ display: none;
458
+ background: rgba(20, 28, 41, 0.97);
459
+ border: 1px solid var(--border);
460
+ border-radius: var(--radius);
461
+ padding: 10px 14px;
462
+ max-width: 280px;
463
+ pointer-events: none;
464
+ z-index: 200;
465
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
466
+ backdrop-filter: blur(8px);
467
+ }
468
+
469
+ .tt-name {
470
+ font-family: 'JetBrains Mono', monospace;
471
+ font-weight: 700;
472
+ font-size: 13px;
473
+ color: var(--text);
474
+ margin-bottom: 3px;
475
+ }
476
+
477
+ .tt-type {
478
+ font-family: 'JetBrains Mono', monospace;
479
+ font-size: 11px;
480
+ color: #60a5fa;
481
+ margin-bottom: 8px;
482
+ }
483
+
484
+ .tt-section {
485
+ font-size: 10px;
486
+ color: var(--text-muted);
487
+ margin-top: 6px;
488
+ line-height: 1.6;
489
+ }
490
+
491
+ .tt-section.tt-fk {
492
+ color: #22d3ee;
493
+ font-family: 'JetBrains Mono', monospace;
494
+ font-size: 11px;
495
+ }
496
+
497
+ .tt-badge {
498
+ display: inline-block;
499
+ padding: 1px 6px;
500
+ border-radius: 4px;
501
+ font-size: 10px;
502
+ background: var(--surface2);
503
+ border: 1px solid var(--border);
504
+ color: var(--text);
505
+ margin: 1px 2px;
506
+ font-family: 'JetBrains Mono', monospace;
507
+ }
508
+
509
+ .tt-badge.req {
510
+ background: rgba(248, 113, 113, .15);
511
+ border-color: #f87171;
512
+ color: #f87171;
513
+ }
514
+
515
+ .tt-badge.uniq {
516
+ background: rgba(96, 165, 250, .15);
517
+ border-color: #60a5fa;
518
+ color: #60a5fa;
519
+ }
520
+
521
+ .tt-badge.email {
522
+ background: rgba(96, 165, 250, .15);
523
+ border-color: #60a5fa;
524
+ color: #60a5fa;
525
+ }
526
+
527
+ .tt-badge.enum {
528
+ background: rgba(167, 139, 250, .15);
529
+ border-color: #a78bfa;
530
+ color: #a78bfa;
531
+ }
532
+
533
+ .tt-badge.enc {
534
+ background: rgba(251, 191, 36, .15);
535
+ border-color: #fbbf24;
536
+ color: #fbbf24;
537
+ }
538
+
539
+ .tt-badge.hash {
540
+ background: rgba(245, 158, 11, .15);
541
+ border-color: #f59e0b;
542
+ color: #f59e0b;
543
+ }
544
+
545
+ .tt-badge.def {
546
+ background: rgba(110, 231, 183, .15);
547
+ border-color: #6ee7b7;
548
+ color: #6ee7b7;
549
+ }
550
+
551
+ /* ── Export Dropdown ─────────────────────────── */
552
+ .tb-dropdown {
553
+ position: relative;
554
+ }
555
+
556
+ .tb-dropdown-menu {
557
+ display: none;
558
+ position: absolute;
559
+ top: calc(100% + 6px);
560
+ right: 0;
561
+ background: rgba(20, 28, 41, 0.97);
562
+ border: 1px solid var(--border);
563
+ border-radius: 8px;
564
+ padding: 4px;
565
+ min-width: 130px;
566
+ z-index: 200;
567
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
568
+ backdrop-filter: blur(12px);
569
+ }
570
+
571
+ .tb-dropdown-menu.open {
572
+ display: block;
573
+ }
574
+
575
+ .tb-dropdown-item {
576
+ display: flex;
577
+ align-items: center;
578
+ gap: 8px;
579
+ padding: 6px 10px;
580
+ border-radius: 5px;
581
+ font-size: 12px;
582
+ font-weight: 500;
583
+ cursor: pointer;
584
+ border: none;
585
+ background: transparent;
586
+ width: 100%;
587
+ text-align: left;
588
+ font-family: 'Inter', sans-serif;
589
+ transition: background 0.12s;
590
+ }
591
+
592
+ .tb-dropdown-item.export-png { color: #34d399; }
593
+ .tb-dropdown-item.export-svg { color: #fb923c; }
594
+
595
+ .tb-dropdown-item:hover {
596
+ background: var(--surface2);
597
+ }
598
+
599
+ .tb-dropdown-caret {
600
+ margin-left: 2px;
601
+ opacity: 0.6;
602
+ }
603
+
604
+ /* ── Legend ──────────────────────────────────── */
605
+ #legend {
606
+ display: none;
607
+ position: fixed;
608
+ bottom: 20px;
609
+ left: 20px;
610
+ background: rgba(20, 28, 41, 0.92);
611
+ border: 1px solid var(--border);
612
+ border-radius: var(--radius);
613
+ padding: 12px 16px;
614
+ z-index: 100;
615
+ backdrop-filter: blur(8px);
616
+ min-width: 190px;
617
+ }
618
+
619
+ .legend-title {
620
+ font-size: 10px;
621
+ font-weight: 600;
622
+ letter-spacing: 0.1em;
623
+ color: var(--text-dim);
624
+ text-transform: uppercase;
625
+ margin-bottom: 8px;
626
+ }
627
+
628
+ .legend-row {
629
+ display: flex;
630
+ align-items: center;
631
+ gap: 8px;
632
+ margin-bottom: 5px;
633
+ font-size: 11px;
634
+ color: var(--text-muted);
635
+ }
636
+
637
+ .legend-swatch {
638
+ width: 12px;
639
+ height: 12px;
640
+ border-radius: 3px;
641
+ flex-shrink: 0;
642
+ }
643
+
644
+ .legend-dot {
645
+ width: 8px;
646
+ height: 8px;
647
+ border-radius: 50%;
648
+ flex-shrink: 0;
649
+ }
650
+
651
+ .legend-line {
652
+ width: 20px;
653
+ height: 2px;
654
+ border-radius: 1px;
655
+ flex-shrink: 0;
656
+ }
657
+
658
+ /* ── Hints ───────────────────────────────────── */
659
+ #hints {
660
+ display: none;
661
+ position: fixed;
662
+ bottom: 20px;
663
+ right: 20px;
664
+ background: rgba(20, 28, 41, 0.92);
665
+ border: 1px solid var(--border);
666
+ border-radius: var(--radius);
667
+ padding: 10px 14px;
668
+ z-index: 100;
669
+ backdrop-filter: blur(8px);
670
+ font-size: 11px;
671
+ color: var(--text-muted);
672
+ line-height: 1.8;
673
+ }
674
+
675
+ .hint-key {
676
+ display: inline-block;
677
+ background: var(--surface2);
678
+ border: 1px solid var(--border);
679
+ border-radius: 4px;
680
+ padding: 0 5px;
681
+ font-family: 'JetBrains Mono', monospace;
682
+ font-size: 10px;
683
+ color: var(--text);
684
+ }
685
+
686
+ /* ── Loader ──────────────────────────────────── */
687
+ #loader {
688
+ position: fixed;
689
+ inset: 0;
690
+ display: flex;
691
+ flex-direction: column;
692
+ align-items: center;
693
+ justify-content: center;
694
+ background: var(--bg);
695
+ z-index: 999;
696
+ gap: 16px;
697
+ transition: opacity 0.4s;
698
+ }
699
+
700
+ #loader.hidden {
701
+ opacity: 0;
702
+ pointer-events: none;
703
+ }
704
+
705
+ .loader-spinner {
706
+ width: 40px;
707
+ height: 40px;
708
+ border: 3px solid var(--border);
709
+ border-top-color: var(--accent);
710
+ border-radius: 50%;
711
+ animation: spin 0.8s linear infinite;
712
+ }
713
+
714
+ .loader-text {
715
+ font-family: 'JetBrains Mono', monospace;
716
+ font-size: 13px;
717
+ color: var(--text-muted);
718
+ }
719
+
720
+ @keyframes spin {
721
+ to {
722
+ transform: rotate(360deg);
723
+ }
724
+ }
725
+ </style>
726
+ </head>
727
+ <body>
728
+
729
+ <!-- Loader -->
730
+ <div id="loader">
731
+ <div class="loader-spinner"></div>
732
+ <div class="loader-text">Loading diagram…</div>
733
+ </div>
734
+
735
+ <!-- Toolbar -->
736
+ <div id="toolbar">
737
+ <!--<div class="logo">
738
+ <svg width="28" height="28" viewBox="0 0 28 28" fill="none">
739
+ <rect width="28" height="28" rx="7" fill="#1f6feb"/>
740
+ <rect x="5" y="7" width="18" height="4" rx="1.5" fill="white" opacity="0.9"/>
741
+ <rect x="5" y="13" width="18" height="4" rx="1.5" fill="white" opacity="0.7"/>
742
+ <rect x="5" y="19" width="10" height="4" rx="1.5" fill="white" opacity="0.5"/>
743
+ <circle cx="22" cy="21" r="3" fill="#34d399"/>
744
+ </svg>
745
+ <div>
746
+ <div class="logo-text">EntityViz</div>
747
+ <div class="logo-sub">API MAKER</div>
748
+ </div>
749
+ </div>
750
+
751
+ <div class="tb-sep"></div>-->
752
+
753
+ <div class="tb-group">
754
+ <button class="tb-btn primary" id="btn-arrange" title="Auto-arrange tables">
755
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
756
+ <rect x="1" y="1" width="4" height="4" rx="1" stroke="currentColor" stroke-width="1.3"/>
757
+ <rect x="9" y="1" width="4" height="4" rx="1" stroke="currentColor" stroke-width="1.3"/>
758
+ <rect x="1" y="9" width="4" height="4" rx="1" stroke="currentColor" stroke-width="1.3"/>
759
+ <rect x="9" y="9" width="4" height="4" rx="1" stroke="currentColor" stroke-width="1.3"/>
760
+ <line x1="3" y1="5" x2="3" y2="9" stroke="currentColor" stroke-width="1.3"/>
761
+ <line x1="11" y1="5" x2="11" y2="9" stroke="currentColor" stroke-width="1.3"/>
762
+ <line x1="5" y1="3" x2="9" y2="3" stroke="currentColor" stroke-width="1.3"/>
763
+ <line x1="5" y1="11" x2="9" y2="11" stroke="currentColor" stroke-width="1.3"/>
764
+ </svg>
765
+ Auto-Arrange
766
+ </button>
767
+ </div>
768
+
769
+ <div class="tb-sep"></div>
770
+
771
+ <div class="tb-group">
772
+ <button class="tb-btn" id="btn-zoom-out" title="Zoom out">
773
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
774
+ <circle cx="6" cy="6" r="4.5" stroke="currentColor" stroke-width="1.3"/>
775
+ <line x1="3.5" y1="6" x2="8.5" y2="6" stroke="currentColor" stroke-width="1.3"/>
776
+ <line x1="10" y1="10" x2="13" y2="13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
777
+ </svg>
778
+ </button>
779
+ <span id="zoom-display">100%</span>
780
+ <button class="tb-btn" id="btn-zoom-in" title="Zoom in">
781
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
782
+ <circle cx="6" cy="6" r="4.5" stroke="currentColor" stroke-width="1.3"/>
783
+ <line x1="6" y1="3.5" x2="6" y2="8.5" stroke="currentColor" stroke-width="1.3"/>
784
+ <line x1="3.5" y1="6" x2="8.5" y2="6" stroke="currentColor" stroke-width="1.3"/>
785
+ <line x1="10" y1="10" x2="13" y2="13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
786
+ </svg>
787
+ </button>
788
+ <button class="tb-btn" id="btn-fit" title="Fit to screen">
789
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
790
+ <circle cx="7" cy="7" r="4" stroke="currentColor" stroke-width="1.3"/>
791
+ <circle cx="7" cy="7" r="1" fill="currentColor"/>
792
+ <line x1="7" y1="1" x2="7" y2="3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
793
+ <line x1="7" y1="11" x2="7" y2="13" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
794
+ <line x1="1" y1="7" x2="3" y2="7" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
795
+ <line x1="11" y1="7" x2="13" y2="7" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
796
+ </svg>
797
+ Fit
798
+ </button>
799
+ </div>
800
+
801
+ <div class="tb-sep"></div>
802
+
803
+ <div class="tb-group">
804
+ <button class="tb-btn" id="btn-deselect" title="Deselect all (Esc)">
805
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
806
+ <circle cx="7" cy="7" r="5.5" stroke="currentColor" stroke-width="1.3"/>
807
+ <line x1="4" y1="4" x2="10" y2="10" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
808
+ </svg>
809
+ Clear
810
+ </button>
811
+ </div>
812
+
813
+ <div class="tb-sep"></div>
814
+
815
+ <button id="btn-search" title="Search (⌘K)">
816
+ <svg width="13" height="13" viewBox="0 0 14 14" fill="none" class="cmd-search-icon">
817
+ <circle cx="6" cy="6" r="4.5" stroke="currentColor" stroke-width="1.3"/>
818
+ <line x1="9.5" y1="9.5" x2="13" y2="13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
819
+ </svg>
820
+ Search…
821
+ <span class="search-hint">⌘K</span>
822
+ </button>
823
+
824
+ <div class="tb-spacer"></div>
825
+
826
+ <div class="tb-group">
827
+ <button class="tb-btn" id="btn-legend" title="Toggle legend">
828
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
829
+ <rect x="1" y="3" width="5" height="5" rx="1.5" stroke="currentColor" stroke-width="1.3"/>
830
+ <line x1="8" y1="5.5" x2="13" y2="5.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
831
+ <line x1="8" y1="8.5" x2="11" y2="8.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
832
+ </svg>
833
+ Legend
834
+ </button>
835
+ </div>
836
+
837
+ <div class="tb-sep"></div>
838
+
839
+ <div class="tb-group">
840
+ <button class="tb-btn" id="btn-fullscreen" title="Toggle fullscreen">
841
+ <svg id="icon-expand" width="14" height="14" viewBox="0 0 14 14" fill="none">
842
+ <polyline points="1,4 1,1 4,1" stroke="currentColor" stroke-width="1.3" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
843
+ <polyline points="10,1 13,1 13,4" stroke="currentColor" stroke-width="1.3" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
844
+ <polyline points="13,10 13,13 10,13" stroke="currentColor" stroke-width="1.3" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
845
+ <polyline points="4,13 1,13 1,10" stroke="currentColor" stroke-width="1.3" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
846
+ </svg>
847
+ <svg id="icon-compress" width="14" height="14" viewBox="0 0 14 14" fill="none" style="display:none">
848
+ <polyline points="4,1 4,4 1,4" stroke="currentColor" stroke-width="1.3" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
849
+ <polyline points="10,4 13,4 13,1" stroke="currentColor" stroke-width="1.3" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
850
+ <polyline points="13,10 10,10 10,13" stroke="currentColor" stroke-width="1.3" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
851
+ <polyline points="4,10 1,10 1,13" stroke="currentColor" stroke-width="1.3" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
852
+ </svg>
853
+ </button>
854
+ </div>
855
+
856
+ <div class="tb-sep"></div>
857
+
858
+ <div class="tb-group tb-dropdown">
859
+ <button class="tb-btn" id="btn-export" title="Export diagram">
860
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
861
+ <path d="M7 1v8M4 6l3 3 3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
862
+ <path d="M2 10v1.5A1.5 1.5 0 003.5 13h7a1.5 1.5 0 001.5-1.5V10" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
863
+ </svg>
864
+ Export
865
+ <svg class="tb-dropdown-caret" width="10" height="10" viewBox="0 0 10 10" fill="none">
866
+ <path d="M2.5 3.5L5 6.5L7.5 3.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
867
+ </svg>
868
+ </button>
869
+ <div class="tb-dropdown-menu" id="export-menu">
870
+ <button class="tb-dropdown-item export-png" id="btn-png">
871
+ <svg width="13" height="13" viewBox="0 0 14 14" fill="none">
872
+ <rect x="1" y="1" width="12" height="12" rx="2" stroke="currentColor" stroke-width="1.3"/>
873
+ <circle cx="5" cy="5" r="1.5" stroke="currentColor" stroke-width="1.1"/>
874
+ <path d="M1 10l3-3 2 2 3-3.5 4 4.5" stroke="currentColor" stroke-width="1.1" stroke-linejoin="round"/>
875
+ </svg>
876
+ Export PNG
877
+ </button>
878
+ <button class="tb-dropdown-item export-svg" id="btn-svg">
879
+ <svg width="13" height="13" viewBox="0 0 14 14" fill="none">
880
+ <path d="M2 4C2 2.9 2.9 2 4 2h6l2 2v8c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V4z" stroke="currentColor" stroke-width="1.3"/>
881
+ <path d="M8 2v3h4" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
882
+ <text x="4" y="10" font-size="4" fill="currentColor" font-family="monospace">SVG</text>
883
+ </svg>
884
+ Export SVG
885
+ </button>
886
+ </div>
887
+ </div>
888
+ </div>
889
+
890
+ <!-- Command Palette -->
891
+ <div id="cmd-overlay">
892
+ <div id="cmd-palette" role="dialog" aria-label="Search">
893
+ <div id="cmd-input-wrap">
894
+ <svg class="cmd-search-icon" width="16" height="16" viewBox="0 0 14 14" fill="none">
895
+ <circle cx="6" cy="6" r="4.5" stroke="currentColor" stroke-width="1.4"/>
896
+ <line x1="9.5" y1="9.5" x2="13" y2="13" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/>
897
+ </svg>
898
+ <input id="cmd-input" placeholder="Search tables, columns, databases…" autocomplete="off" spellcheck="false"/>
899
+ </div>
900
+ <div id="cmd-results"></div>
901
+ <div id="cmd-empty">
902
+ <svg width="40" height="40" viewBox="0 0 40 40" fill="none">
903
+ <circle cx="18" cy="18" r="12" stroke="#484f58" stroke-width="2"/>
904
+ <line x1="27" y1="27" x2="37" y2="37" stroke="#484f58" stroke-width="2.5" stroke-linecap="round"/>
905
+ <line x1="13" y1="18" x2="23" y2="18" stroke="#484f58" stroke-width="2" stroke-linecap="round"/>
906
+ <line x1="18" y1="13" x2="18" y2="23" stroke="#484f58" stroke-width="2" stroke-linecap="round"/>
907
+ </svg>
908
+ <p>No results for <strong id="cmd-empty-query"></strong></p>
909
+ </div>
910
+ <div id="cmd-footer">
911
+ <span><span class="cmd-key">↑↓</span> navigate</span>
912
+ <span><span class="cmd-key">↵</span> select</span>
913
+ <span><span class="cmd-key">esc</span> close</span>
914
+ <span style="margin-left:auto;font-size:10px;opacity:0.5" id="cmd-count"></span>
915
+ </div>
916
+ </div>
917
+ </div>
918
+
919
+ <!-- Canvas -->
920
+ <canvas id="canvas"></canvas>
921
+
922
+ <!-- Tooltip -->
923
+ <div id="tooltip"></div>
924
+
925
+ <!-- Legend -->
926
+ <div id="legend">
927
+ <div class="legend-title">Legend</div>
928
+ <div class="legend-row">
929
+ <div class="legend-dot" style="background:#fbbf24"></div>
930
+ <span>Primary Key</span>
931
+ </div>
932
+ <div class="legend-row">
933
+ <div class="legend-dot" style="background:#22d3ee"></div>
934
+ <span>Foreign Key</span>
935
+ </div>
936
+ <div class="legend-row">
937
+ <div class="legend-dot" style="background:#f87171"></div>
938
+ <span>Required field</span>
939
+ </div>
940
+ <div class="legend-row">
941
+ <div class="legend-line" style="background:linear-gradient(90deg,#60a5fa,#22d3ee)"></div>
942
+ <span>Relation</span>
943
+ </div>
944
+ <div class="legend-row">
945
+ <div class="legend-swatch" style="background:rgba(88,166,255,.15);border:1px solid #58a6ff"></div>
946
+ <span>Instance</span>
947
+ </div>
948
+ <div class="legend-row">
949
+ <div class="legend-swatch" style="background:rgba(61,68,77,.4);border:1px solid #3d444d"></div>
950
+ <span>Database</span>
951
+ </div>
952
+ </div>
953
+
954
+ <!-- Hints -->
955
+ <div id="hints">
956
+ <div><span class="hint-key">⌘/Ctrl</span> + Scroll — Zoom</div>
957
+ <div><span class="hint-key">Drag</span> — Pan canvas or move table</div>
958
+ <div><span class="hint-key">Click</span> — Select &amp; highlight relations</div>
959
+ <div><span class="hint-key">Esc</span> — Deselect</div>
960
+ </div>
961
+
962
+ <script src="index.js"></script>
963
+ <script>
964
+ // ── Fullscreen ──────────────────────────────────────────────────
965
+ (function () {
966
+ let fullscreen = false;
967
+
968
+ function setFullscreen(value) {
969
+ fullscreen = value;
970
+ document.getElementById('icon-expand').style.display = fullscreen ? 'none' : '';
971
+ document.getElementById('icon-compress').style.display = fullscreen ? '' : 'none';
972
+ }
973
+
974
+ document.getElementById('btn-fullscreen').addEventListener('click', () => {
975
+ setFullscreen(!fullscreen);
976
+ window.parent.postMessage({ type: 'DIAGRAM_FULLSCREEN', value: fullscreen }, '*');
977
+ });
978
+
979
+ window.addEventListener('message', e => {
980
+ if (e.data?.type === 'DIAGRAM_FULLSCREEN_ACK') {
981
+ setFullscreen(e.data.value);
982
+ // Let the browser apply the new iframe dimensions before refitting
983
+ setTimeout(() => { resize(); centerView(); scheduleRender(); }, 60);
984
+ }
985
+ });
986
+ })();
987
+
988
+ // ── Export dropdown ─────────────────────────────────────────────
989
+ const exportBtn = document.getElementById('btn-export');
990
+ const exportMenu = document.getElementById('export-menu');
991
+ exportBtn.addEventListener('click', e => { e.stopPropagation(); exportMenu.classList.toggle('open'); });
992
+ document.addEventListener('click', () => exportMenu.classList.remove('open'));
993
+ exportMenu.addEventListener('click', e => e.stopPropagation());
994
+
995
+ // ── Legend + Hints toggle ────────────────────────────────────────
996
+ document.getElementById('btn-legend').addEventListener('click', () => {
997
+ const visible = document.getElementById('legend').style.display === 'block';
998
+ document.getElementById('legend').style.display = visible ? 'none' : 'block';
999
+ document.getElementById('hints').style.display = visible ? 'none' : 'block';
1000
+ });
1001
+
1002
+ // ── Command Palette Search ───────────────────────────────────────
1003
+ (function () {
1004
+ let index = []; // flat search entries
1005
+ let results = []; // current filtered results
1006
+ let cursor = -1; // keyboard-selected row index
1007
+
1008
+ const overlay = document.getElementById('cmd-overlay');
1009
+ const input = document.getElementById('cmd-input');
1010
+ const resultsEl = document.getElementById('cmd-results');
1011
+ const emptyEl = document.getElementById('cmd-empty');
1012
+ const emptyQ = document.getElementById('cmd-empty-query');
1013
+ const countEl = document.getElementById('cmd-count');
1014
+
1015
+ // ── Icons ────────────────────────────────────────────────
1016
+ const ICONS = {
1017
+ table: `<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
1018
+ <rect x="1" y="1" width="12" height="12" rx="2" stroke="currentColor" stroke-width="1.3"/>
1019
+ <line x1="1" y1="5" x2="13" y2="5" stroke="currentColor" stroke-width="1.1"/>
1020
+ <line x1="5" y1="5" x2="5" y2="13" stroke="currentColor" stroke-width="1.1"/>
1021
+ </svg>`,
1022
+ column: `<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
1023
+ <line x1="2" y1="4" x2="12" y2="4" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
1024
+ <line x1="2" y1="7" x2="9" y2="7" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
1025
+ <line x1="2" y1="10" x2="11" y2="10" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
1026
+ </svg>`,
1027
+ database: `<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
1028
+ <ellipse cx="7" cy="4" rx="5" ry="2" stroke="currentColor" stroke-width="1.3"/>
1029
+ <path d="M2 4v6c0 1.1 2.2 2 5 2s5-.9 5-2V4" stroke="currentColor" stroke-width="1.3"/>
1030
+ <path d="M2 7c0 1.1 2.2 2 5 2s5-.9 5-2" stroke="currentColor" stroke-width="1.3"/>
1031
+ </svg>`,
1032
+ instance: `<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
1033
+ <rect x="1" y="3" width="12" height="8" rx="2" stroke="currentColor" stroke-width="1.3"/>
1034
+ <line x1="4" y1="3" x2="4" y2="11" stroke="currentColor" stroke-width="1.1"/>
1035
+ <circle cx="2.5" cy="6" r="0.8" fill="currentColor"/>
1036
+ </svg>`,
1037
+ };
1038
+
1039
+ // ── Build search index after diagram data loads ──────────
1040
+ window.onDiagramLoaded = function () {
1041
+ index = [];
1042
+ Object.values(S.tables).forEach(t => {
1043
+ index.push({ type: 'table', label: t.coll, sub: t.db + ' · ' + t.inst, tableKey: t.key });
1044
+ t.columns.forEach(col => {
1045
+ if (col.isGroupHeader) return;
1046
+ index.push({
1047
+ type: 'column',
1048
+ label: col.name,
1049
+ sub: t.coll,
1050
+ tableKey: t.key,
1051
+ colType: col.type || '?',
1052
+ isPK: col.isPK,
1053
+ isFK: col.isFK,
1054
+ });
1055
+ });
1056
+ });
1057
+ Object.values(S.databases).forEach(db => {
1058
+ index.push({ type: 'database', label: db.name, sub: db.inst, dbKey: db.key });
1059
+ });
1060
+ Object.values(S.instances).forEach(inst => {
1061
+ index.push({ type: 'instance', label: inst.name, sub: inst.databases.length + ' database(s)', instName: inst.name });
1062
+ });
1063
+ };
1064
+
1065
+ // ── Open / close ─────────────────────────────────────────
1066
+ function open() {
1067
+ overlay.classList.add('open');
1068
+ input.value = '';
1069
+ cursor = -1;
1070
+ renderResults('');
1071
+ requestAnimationFrame(() => input.focus());
1072
+ }
1073
+
1074
+ function close() {
1075
+ overlay.classList.remove('open');
1076
+ input.blur();
1077
+ }
1078
+
1079
+ document.getElementById('btn-search').addEventListener('click', open);
1080
+
1081
+ document.addEventListener('keydown', e => {
1082
+ const hotkey = (e.metaKey || e.ctrlKey) && e.key === 'k';
1083
+ if (hotkey) { e.preventDefault(); overlay.classList.contains('open') ? close() : open(); return; }
1084
+ if (!overlay.classList.contains('open')) return;
1085
+ if (e.key === 'Escape') { close(); return; }
1086
+ if (e.key === 'ArrowDown') { e.preventDefault(); moveCursor(1); return; }
1087
+ if (e.key === 'ArrowUp') { e.preventDefault(); moveCursor(-1); return; }
1088
+ if (e.key === 'Enter') { e.preventDefault(); selectCurrent(); return; }
1089
+ });
1090
+
1091
+ overlay.addEventListener('click', e => { if (e.target === overlay) close(); });
1092
+ input.addEventListener('input', () => { cursor = -1; renderResults(input.value.trim()); });
1093
+
1094
+ // ── Helpers ──────────────────────────────────────────────
1095
+ function escHtml(s) {
1096
+ return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
1097
+ }
1098
+
1099
+ function highlight(text, q) {
1100
+ if (!q) return escHtml(text);
1101
+ const i = text.toLowerCase().indexOf(q.toLowerCase());
1102
+ if (i === -1) return escHtml(text);
1103
+ return escHtml(text.slice(0, i)) +
1104
+ '<mark>' + escHtml(text.slice(i, i + q.length)) + '</mark>' +
1105
+ escHtml(text.slice(i + q.length));
1106
+ }
1107
+
1108
+ function score(entry, q) {
1109
+ const lq = q.toLowerCase();
1110
+ const ll = entry.label.toLowerCase();
1111
+ const ls = (entry.sub || '').toLowerCase();
1112
+ if (ll === lq) return 100;
1113
+ if (ll.startsWith(lq)) return 80;
1114
+ if (ll.includes(lq)) return 60;
1115
+ if (ls.includes(lq)) return 30;
1116
+ return -1;
1117
+ }
1118
+
1119
+ // ── Render results list ───────────────────────────────────
1120
+ function renderResults(q) {
1121
+ const typeOrder = { table: 0, column: 1, database: 2, instance: 3 };
1122
+
1123
+ if (!q) {
1124
+ // Default: show all tables up to 8
1125
+ results = Object.values(S.tables).slice(0, 8).map(t => ({
1126
+ type: 'table', label: t.coll, sub: t.db + ' · ' + t.inst, tableKey: t.key,
1127
+ }));
1128
+ } else {
1129
+ results = index
1130
+ .map(e => ({ ...e, _score: score(e, q) }))
1131
+ .filter(e => e._score > 0)
1132
+ .sort((a, b) => b._score - a._score || typeOrder[a.type] - typeOrder[b.type])
1133
+ .slice(0, 10);
1134
+ }
1135
+
1136
+ emptyEl.style.display = 'none';
1137
+ resultsEl.innerHTML = '';
1138
+
1139
+ if (results.length === 0) {
1140
+ emptyQ.textContent = '"' + q + '"';
1141
+ emptyEl.style.display = 'flex';
1142
+ countEl.textContent = '';
1143
+ return;
1144
+ }
1145
+
1146
+ countEl.textContent = results.length + ' result' + (results.length !== 1 ? 's' : '');
1147
+
1148
+ // Group by type
1149
+ const groups = {};
1150
+ results.forEach((r, i) => { (groups[r.type] = groups[r.type] || []).push({ ...r, _i: i }); });
1151
+
1152
+ const groupLabels = { table: 'Tables', column: 'Columns', database: 'Databases', instance: 'Instances' };
1153
+
1154
+ Object.entries(groups).forEach(([type, items]) => {
1155
+ const sec = document.createElement('div');
1156
+ sec.className = 'cmd-section-label';
1157
+ sec.textContent = groupLabels[type];
1158
+ resultsEl.appendChild(sec);
1159
+
1160
+ items.forEach(r => {
1161
+ const el = document.createElement('div');
1162
+ el.className = 'cmd-item';
1163
+ el.dataset.idx = r._i;
1164
+
1165
+ let meta = '';
1166
+ if (r.type === 'column') {
1167
+ if (r.isPK) meta += `<span class="cmd-badge pk">PK</span>`;
1168
+ if (r.isFK) meta += `<span class="cmd-badge fk">FK</span>`;
1169
+ meta += `<span class="cmd-badge col-type">${escHtml(r.colType)}</span>`;
1170
+ }
1171
+
1172
+ el.innerHTML = `
1173
+ <div class="cmd-icon t-${r.type}">${ICONS[r.type]}</div>
1174
+ <div class="cmd-info">
1175
+ <div class="cmd-name">${highlight(r.label, q)}</div>
1176
+ <div class="cmd-sub">${escHtml(r.sub || '')}</div>
1177
+ </div>
1178
+ <div class="cmd-meta">${meta}</div>`;
1179
+
1180
+ el.addEventListener('click', () => { cursor = r._i; selectCurrent(); });
1181
+ el.addEventListener('mouseenter', () => { cursor = r._i; updateCursorUI(); });
1182
+ resultsEl.appendChild(el);
1183
+ });
1184
+ });
1185
+
1186
+ updateCursorUI();
1187
+ }
1188
+
1189
+ // ── Keyboard cursor ──────────────────────────────────────
1190
+ function moveCursor(dir) {
1191
+ cursor = Math.max(0, Math.min(results.length - 1, cursor + dir));
1192
+ updateCursorUI(true);
1193
+ }
1194
+
1195
+ function updateCursorUI(scroll) {
1196
+ resultsEl.querySelectorAll('.cmd-item').forEach(el => {
1197
+ const active = parseInt(el.dataset.idx) === cursor;
1198
+ el.classList.toggle('cmd-active', active);
1199
+ if (active && scroll) el.scrollIntoView({ block: 'nearest' });
1200
+ });
1201
+ }
1202
+
1203
+ // ── Navigate to table on canvas ──────────────────────────
1204
+ function selectCurrent() {
1205
+ const r = results[cursor];
1206
+ if (!r) return;
1207
+ close();
1208
+
1209
+ const tableKey = r.tableKey;
1210
+ if (!tableKey || !S.tables[tableKey]) return;
1211
+
1212
+ const t = S.tables[tableKey];
1213
+ S.selected = tableKey;
1214
+
1215
+ const cx = t.x + t.w / 2;
1216
+ const cy = t.y + t.h / 2;
1217
+ const targetZoom = Math.min(Math.max(S.zoom, 0.75), 1.5);
1218
+ S.zoom = targetZoom;
1219
+ S.panX = S.vw / 2 - cx * S.zoom;
1220
+ S.panY = S.vh / 2 - cy * S.zoom;
1221
+ S.ripples.push({ x: cx, y: cy, r: 0, a: 1 });
1222
+ scheduleRender();
1223
+ }
1224
+
1225
+ })();
1226
+ </script>
1227
+ </body>
1228
+ </html>