rails-schema 0.1.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.
@@ -0,0 +1,473 @@
1
+ :root {
2
+ --bg-primary: #ffffff;
3
+ --bg-secondary: #f8f9fa;
4
+ --bg-tertiary: #e9ecef;
5
+ --text-primary: #212529;
6
+ --text-secondary: #6c757d;
7
+ --text-muted: #adb5bd;
8
+ --border-color: #dee2e6;
9
+ --accent: #4361ee;
10
+ --accent-light: #e8ecff;
11
+ --node-bg: #ffffff;
12
+ --node-border: #dee2e6;
13
+ --node-header-bg: #4361ee;
14
+ --node-header-text: #ffffff;
15
+ --edge-color: #adb5bd;
16
+ --edge-belongs-to: #4361ee;
17
+ --edge-has-many: #2ec4b6;
18
+ --edge-has-one: #ff6b6b;
19
+ --edge-habtm: #ffd166;
20
+ --shadow: 0 2px 8px rgba(0,0,0,0.1);
21
+ --sidebar-width: 280px;
22
+ --detail-width: 320px;
23
+ --toolbar-height: 48px;
24
+ }
25
+
26
+ .dark {
27
+ --bg-primary: #1a1a2e;
28
+ --bg-secondary: #16213e;
29
+ --bg-tertiary: #0f3460;
30
+ --text-primary: #e6e6e6;
31
+ --text-secondary: #a0a0b0;
32
+ --text-muted: #666680;
33
+ --border-color: #2a2a4a;
34
+ --accent: #6c83f7;
35
+ --accent-light: #252545;
36
+ --node-bg: #16213e;
37
+ --node-border: #2a2a4a;
38
+ --node-header-bg: #6c83f7;
39
+ --node-header-text: #ffffff;
40
+ --edge-color: #444460;
41
+ --edge-belongs-to: #6c83f7;
42
+ --edge-has-many: #2ec4b6;
43
+ --edge-has-one: #ff6b6b;
44
+ --edge-habtm: #ffd166;
45
+ --shadow: 0 2px 8px rgba(0,0,0,0.3);
46
+ }
47
+
48
+ @media (prefers-color-scheme: dark) {
49
+ :root:not(.light) {
50
+ --bg-primary: #1a1a2e;
51
+ --bg-secondary: #16213e;
52
+ --bg-tertiary: #0f3460;
53
+ --text-primary: #e6e6e6;
54
+ --text-secondary: #a0a0b0;
55
+ --text-muted: #666680;
56
+ --border-color: #2a2a4a;
57
+ --accent: #6c83f7;
58
+ --accent-light: #252545;
59
+ --node-bg: #16213e;
60
+ --node-border: #2a2a4a;
61
+ --node-header-bg: #6c83f7;
62
+ --node-header-text: #ffffff;
63
+ --edge-color: #444460;
64
+ --edge-belongs-to: #6c83f7;
65
+ --edge-has-many: #2ec4b6;
66
+ --edge-has-one: #ff6b6b;
67
+ --edge-habtm: #ffd166;
68
+ --shadow: 0 2px 8px rgba(0,0,0,0.3);
69
+ }
70
+ }
71
+
72
+ * { margin: 0; padding: 0; box-sizing: border-box; }
73
+
74
+ body {
75
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
76
+ background: var(--bg-primary);
77
+ color: var(--text-primary);
78
+ overflow: hidden;
79
+ height: 100vh;
80
+ width: 100vw;
81
+ }
82
+
83
+ #app {
84
+ display: flex;
85
+ height: 100vh;
86
+ width: 100vw;
87
+ }
88
+
89
+ /* Toolbar */
90
+ #toolbar {
91
+ position: fixed;
92
+ top: 0;
93
+ left: var(--sidebar-width);
94
+ right: 0;
95
+ height: var(--toolbar-height);
96
+ background: var(--bg-secondary);
97
+ border-bottom: 1px solid var(--border-color);
98
+ display: flex;
99
+ align-items: center;
100
+ padding: 0 16px;
101
+ gap: 8px;
102
+ z-index: 100;
103
+ }
104
+
105
+ #toolbar button {
106
+ padding: 6px 12px;
107
+ border: 1px solid var(--border-color);
108
+ border-radius: 6px;
109
+ background: var(--bg-primary);
110
+ color: var(--text-primary);
111
+ cursor: pointer;
112
+ font-size: 13px;
113
+ transition: background 0.15s;
114
+ }
115
+
116
+ #toolbar button:hover {
117
+ background: var(--bg-tertiary);
118
+ }
119
+
120
+ #toolbar .spacer { flex: 1; }
121
+
122
+ #toolbar .zoom-info {
123
+ font-size: 12px;
124
+ color: var(--text-secondary);
125
+ min-width: 48px;
126
+ text-align: center;
127
+ }
128
+
129
+ #toolbar .shortcuts-hint {
130
+ font-size: 11px;
131
+ color: var(--text-muted);
132
+ }
133
+
134
+ /* Sidebar */
135
+ #sidebar {
136
+ width: var(--sidebar-width);
137
+ min-width: var(--sidebar-width);
138
+ background: var(--bg-secondary);
139
+ border-right: 1px solid var(--border-color);
140
+ display: flex;
141
+ flex-direction: column;
142
+ height: 100vh;
143
+ z-index: 101;
144
+ }
145
+
146
+ #sidebar-header {
147
+ padding: 16px;
148
+ border-bottom: 1px solid var(--border-color);
149
+ }
150
+
151
+ #sidebar-header h1 {
152
+ font-size: 16px;
153
+ font-weight: 700;
154
+ margin-bottom: 12px;
155
+ }
156
+
157
+ #search-input {
158
+ width: 100%;
159
+ padding: 8px 12px;
160
+ border: 1px solid var(--border-color);
161
+ border-radius: 6px;
162
+ background: var(--bg-primary);
163
+ color: var(--text-primary);
164
+ font-size: 13px;
165
+ outline: none;
166
+ }
167
+
168
+ #search-input:focus {
169
+ border-color: var(--accent);
170
+ box-shadow: 0 0 0 2px var(--accent-light);
171
+ }
172
+
173
+ #sidebar-actions {
174
+ padding: 8px 16px;
175
+ display: flex;
176
+ gap: 8px;
177
+ border-bottom: 1px solid var(--border-color);
178
+ }
179
+
180
+ #sidebar-actions button {
181
+ padding: 4px 8px;
182
+ border: 1px solid var(--border-color);
183
+ border-radius: 4px;
184
+ background: var(--bg-primary);
185
+ color: var(--text-secondary);
186
+ cursor: pointer;
187
+ font-size: 11px;
188
+ }
189
+
190
+ #sidebar-actions button:hover {
191
+ background: var(--bg-tertiary);
192
+ }
193
+
194
+ #model-list {
195
+ flex: 1;
196
+ overflow-y: auto;
197
+ padding: 8px 0;
198
+ }
199
+
200
+ .model-item {
201
+ display: flex;
202
+ align-items: center;
203
+ padding: 6px 16px;
204
+ cursor: pointer;
205
+ transition: background 0.1s;
206
+ font-size: 13px;
207
+ }
208
+
209
+ .model-item:hover {
210
+ background: var(--bg-tertiary);
211
+ }
212
+
213
+ .model-item.active {
214
+ background: var(--accent-light);
215
+ color: var(--accent);
216
+ font-weight: 600;
217
+ }
218
+
219
+ .model-item input[type="checkbox"] {
220
+ margin-right: 8px;
221
+ accent-color: var(--accent);
222
+ }
223
+
224
+ .model-item .model-name {
225
+ flex: 1;
226
+ overflow: hidden;
227
+ text-overflow: ellipsis;
228
+ white-space: nowrap;
229
+ }
230
+
231
+ .model-item .assoc-count {
232
+ font-size: 11px;
233
+ color: var(--text-muted);
234
+ margin-left: 4px;
235
+ }
236
+
237
+ /* Canvas area */
238
+ #canvas-container {
239
+ flex: 1;
240
+ position: relative;
241
+ margin-top: var(--toolbar-height);
242
+ overflow: hidden;
243
+ }
244
+
245
+ #canvas-container svg {
246
+ width: 100%;
247
+ height: 100%;
248
+ }
249
+
250
+ /* SVG nodes */
251
+ .node-group { cursor: grab; }
252
+ .node-group:active { cursor: grabbing; }
253
+
254
+ .node-rect {
255
+ fill: var(--node-bg);
256
+ stroke: var(--node-border);
257
+ stroke-width: 1.5;
258
+ rx: 8;
259
+ ry: 8;
260
+ filter: drop-shadow(0 1px 3px rgba(0,0,0,0.1));
261
+ }
262
+
263
+ .node-group.highlighted .node-rect {
264
+ stroke: var(--accent);
265
+ stroke-width: 2.5;
266
+ }
267
+
268
+ .node-header-rect {
269
+ fill: var(--node-header-bg);
270
+ rx: 8;
271
+ ry: 8;
272
+ }
273
+
274
+ .node-header-cover {
275
+ fill: var(--node-header-bg);
276
+ }
277
+
278
+ .node-header-text {
279
+ fill: var(--node-header-text);
280
+ font-size: 13px;
281
+ font-weight: 700;
282
+ font-family: inherit;
283
+ }
284
+
285
+ .node-table-text {
286
+ fill: var(--node-header-text);
287
+ font-size: 10px;
288
+ opacity: 0.8;
289
+ font-family: inherit;
290
+ }
291
+
292
+ .node-column-text {
293
+ fill: var(--text-primary);
294
+ font-size: 11px;
295
+ font-family: "SF Mono", "Fira Code", "Cascadia Code", monospace;
296
+ }
297
+
298
+ .node-column-type {
299
+ fill: var(--text-muted);
300
+ font-size: 10px;
301
+ font-family: "SF Mono", "Fira Code", "Cascadia Code", monospace;
302
+ }
303
+
304
+ .node-column-pk {
305
+ fill: var(--accent);
306
+ font-weight: 600;
307
+ }
308
+
309
+ /* Edges */
310
+ .edge-line {
311
+ fill: none;
312
+ stroke-width: 1.5;
313
+ }
314
+
315
+ .edge-line.belongs_to { stroke: var(--edge-belongs-to); }
316
+ .edge-line.has_many { stroke: var(--edge-has-many); }
317
+ .edge-line.has_one { stroke: var(--edge-has-one); }
318
+ .edge-line.has_and_belongs_to_many { stroke: var(--edge-habtm); }
319
+
320
+ .edge-line.through {
321
+ stroke-dasharray: 6 3;
322
+ }
323
+
324
+ .edge-line.polymorphic-edge {
325
+ stroke-dasharray: 3 3;
326
+ }
327
+
328
+ .edge-label {
329
+ font-size: 10px;
330
+ fill: var(--text-muted);
331
+ font-family: inherit;
332
+ pointer-events: none;
333
+ }
334
+
335
+ /* Faded state for non-focused elements */
336
+ .faded { opacity: 0.15; transition: opacity 0.3s; }
337
+ .highlighted { transition: opacity 0.3s; }
338
+
339
+ /* Detail panel */
340
+ #detail-panel {
341
+ width: 0;
342
+ min-width: 0;
343
+ background: var(--bg-secondary);
344
+ border-left: 1px solid var(--border-color);
345
+ overflow-y: auto;
346
+ overflow-x: hidden;
347
+ transition: width 0.2s, min-width 0.2s;
348
+ }
349
+
350
+ #detail-panel.open {
351
+ width: var(--detail-width);
352
+ min-width: var(--detail-width);
353
+ }
354
+
355
+ #detail-content {
356
+ padding: 16px;
357
+ width: var(--detail-width);
358
+ }
359
+
360
+ #detail-content h2 {
361
+ font-size: 18px;
362
+ font-weight: 700;
363
+ margin-bottom: 4px;
364
+ }
365
+
366
+ #detail-content .detail-table {
367
+ font-size: 12px;
368
+ color: var(--text-secondary);
369
+ margin-bottom: 16px;
370
+ }
371
+
372
+ #detail-content h3 {
373
+ font-size: 13px;
374
+ font-weight: 600;
375
+ color: var(--text-secondary);
376
+ text-transform: uppercase;
377
+ letter-spacing: 0.5px;
378
+ margin: 16px 0 8px 0;
379
+ }
380
+
381
+ .column-list {
382
+ list-style: none;
383
+ }
384
+
385
+ .column-list li {
386
+ padding: 4px 0;
387
+ font-size: 12px;
388
+ display: flex;
389
+ justify-content: space-between;
390
+ border-bottom: 1px solid var(--border-color);
391
+ }
392
+
393
+ .column-list .col-name {
394
+ font-family: "SF Mono", "Fira Code", monospace;
395
+ font-weight: 500;
396
+ }
397
+
398
+ .column-list .col-name.pk {
399
+ color: var(--accent);
400
+ }
401
+
402
+ .column-list .col-type {
403
+ color: var(--text-muted);
404
+ font-family: "SF Mono", "Fira Code", monospace;
405
+ font-size: 11px;
406
+ }
407
+
408
+ .assoc-list {
409
+ list-style: none;
410
+ }
411
+
412
+ .assoc-list li {
413
+ padding: 4px 0;
414
+ font-size: 12px;
415
+ border-bottom: 1px solid var(--border-color);
416
+ }
417
+
418
+ .assoc-list .assoc-type {
419
+ font-size: 10px;
420
+ padding: 1px 6px;
421
+ border-radius: 3px;
422
+ background: var(--bg-tertiary);
423
+ color: var(--text-secondary);
424
+ margin-right: 6px;
425
+ }
426
+
427
+ .assoc-list .assoc-target {
428
+ font-weight: 500;
429
+ cursor: pointer;
430
+ color: var(--accent);
431
+ }
432
+
433
+ .assoc-list .assoc-target:hover {
434
+ text-decoration: underline;
435
+ }
436
+
437
+ #detail-close {
438
+ position: absolute;
439
+ top: 12px;
440
+ right: 12px;
441
+ background: none;
442
+ border: none;
443
+ color: var(--text-secondary);
444
+ cursor: pointer;
445
+ font-size: 18px;
446
+ padding: 4px;
447
+ }
448
+
449
+ /* Legend */
450
+ .legend {
451
+ display: flex;
452
+ gap: 16px;
453
+ align-items: center;
454
+ font-size: 11px;
455
+ color: var(--text-secondary);
456
+ }
457
+
458
+ .legend-item {
459
+ display: flex;
460
+ align-items: center;
461
+ gap: 4px;
462
+ }
463
+
464
+ .legend-line {
465
+ width: 20px;
466
+ height: 2px;
467
+ display: inline-block;
468
+ }
469
+
470
+ .legend-line.belongs_to { background: var(--edge-belongs-to); }
471
+ .legend-line.has_many { background: var(--edge-has-many); }
472
+ .legend-line.has_one { background: var(--edge-has-one); }
473
+ .legend-line.habtm { background: var(--edge-habtm); }
@@ -0,0 +1,53 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="<%= theme_class %>">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title><%= title %></title>
7
+ <style><%= css_content %></style>
8
+ </head>
9
+ <body>
10
+ <div id="app">
11
+ <div id="sidebar">
12
+ <div id="sidebar-header">
13
+ <h1><%= title %></h1>
14
+ <input type="text" id="search-input" placeholder="Search models... ( / )" autocomplete="off">
15
+ </div>
16
+ <div id="sidebar-actions">
17
+ <button id="select-all-btn">Select All</button>
18
+ <button id="deselect-all-btn">Deselect All</button>
19
+ </div>
20
+ <div id="model-list"></div>
21
+ </div>
22
+
23
+ <div id="toolbar">
24
+ <div class="legend">
25
+ <span class="legend-item"><span class="legend-line belongs_to"></span> belongs_to</span>
26
+ <span class="legend-item"><span class="legend-line has_many"></span> has_many</span>
27
+ <span class="legend-item"><span class="legend-line has_one"></span> has_one</span>
28
+ <span class="legend-item"><span class="legend-line habtm"></span> habtm</span>
29
+ </div>
30
+ <div class="spacer"></div>
31
+ <span class="zoom-info" id="zoom-info">100%</span>
32
+ <button id="zoom-in-btn" title="Zoom in (+)">+</button>
33
+ <button id="zoom-out-btn" title="Zoom out (-)">−</button>
34
+ <button id="fit-btn" title="Fit to screen (F)">Fit</button>
35
+ <button id="theme-btn" title="Toggle theme">Theme</button>
36
+ <span class="shortcuts-hint">/ search · Esc deselect · F fit</span>
37
+ </div>
38
+
39
+ <div id="canvas-container">
40
+ <svg id="schema-svg"></svg>
41
+ </div>
42
+
43
+ <div id="detail-panel">
44
+ <div id="detail-content"></div>
45
+ </div>
46
+ </div>
47
+
48
+ <script>window.__SCHEMA_DATA__ = <%= graph_json %>;</script>
49
+ <script>window.__SCHEMA_CONFIG__ = <%= config_json %>;</script>
50
+ <script><%= d3_js_content %></script>
51
+ <script><%= app_js_content %></script>
52
+ </body>
53
+ </html>