quickdbd-cli 0.1.14__tar.gz → 0.1.15__tar.gz
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.
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/PKG-INFO +1 -1
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/pyproject.toml +1 -1
- quickdbd_cli-0.1.15/src/quickdbd_cli/__init__.py +1 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/src/quickdbd_cli/assets/render.js +9 -9
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/src/quickdbd_cli/assets/style.css +91 -17
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/src/quickdbd_cli/assets/viewer.js +20 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/src/quickdbd_cli/html.py +12 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/tests/test_pipeline.py +10 -0
- quickdbd_cli-0.1.14/src/quickdbd_cli/__init__.py +0 -1
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/.gitignore +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/LICENSE +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/NOTICE +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/README.md +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/examples/quickdbd-sample.txt +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/examples/synthetic/finance.txt +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/examples/synthetic/hr.txt +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/examples/synthetic/inventory.txt +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/examples/synthetic/marketing.txt +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/examples/synthetic/qdbd.layout.json +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/examples/synthetic/sales.txt +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/scripts/build_gallery.py +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/scripts/gen_font_metrics.py +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/scripts/gen_schema.py +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/scripts/gen_thumbnails.py +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/scripts/release.sh +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/src/quickdbd_cli/__main__.py +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/src/quickdbd_cli/assets/Inter-Bold.ttf +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/src/quickdbd_cli/assets/Inter-Bold.woff2 +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/src/quickdbd_cli/assets/Inter-LICENSE.txt +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/src/quickdbd_cli/assets/Inter-Regular.ttf +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/src/quickdbd_cli/assets/Inter-Regular.woff2 +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/src/quickdbd_cli/assets/editor.js +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/src/quickdbd_cli/assets/elk-LICENSE.md +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/src/quickdbd_cli/assets/elk.bundled.js +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/src/quickdbd_cli/assets/font-metrics.json +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/src/quickdbd_cli/cli.py +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/src/quickdbd_cli/dbt.py +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/src/quickdbd_cli/diff.py +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/src/quickdbd_cli/export.py +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/src/quickdbd_cli/jsrt.py +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/src/quickdbd_cli/layout.py +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/src/quickdbd_cli/measure.py +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/src/quickdbd_cli/model.py +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/src/quickdbd_cli/parser.py +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/src/quickdbd_cli/project.py +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/src/quickdbd_cli/serve.py +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/src/quickdbd_cli/share.py +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/src/quickdbd_cli/tags.py +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/tests/fixtures/jaffle/LICENSE +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/tests/fixtures/jaffle/README.md +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/tests/fixtures/jaffle/catalog.json +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/tests/fixtures/jaffle/manifest.json +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/tests/test_dbt.py +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/tests/test_diff.py +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/tests/test_gallery.py +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/tests/test_parser.py +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/tests/test_path.py +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/tests/test_select.py +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/tests/test_serve.py +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/tests/test_share.py +0 -0
- {quickdbd_cli-0.1.14 → quickdbd_cli-0.1.15}/tests/test_tags.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: quickdbd-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.15
|
|
4
4
|
Summary: Compile QuickDBD-syntax schema files into interactive HTML ER diagrams, with no table limit.
|
|
5
5
|
Project-URL: Homepage, https://github.com/MarkPhamm/quickdbd-cli
|
|
6
6
|
Author: Minh (Mark) Pham
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "quickdbd-cli"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.15"
|
|
8
8
|
description = "Compile QuickDBD-syntax schema files into interactive HTML ER diagrams, with no table limit."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.15"
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
function keyIcon(x, cy) {
|
|
66
|
-
return '<g stroke="' + C.key + '" stroke-width="1.5" fill="none">' +
|
|
66
|
+
return '<g class="ki" stroke="' + C.key + '" stroke-width="1.5" fill="none">' +
|
|
67
67
|
'<circle cx="' + n(x + 3.5) + '" cy="' + n(cy) + '" r="3"/>' +
|
|
68
68
|
'<path d="M' + n(x + 6.5) + " " + n(cy) + "h7m-2 0v3m-2.5-3v2" + '"/></g>';
|
|
69
69
|
}
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
out.push('<text x="' + (w - st.padX) + '" y="' + n(st.headerH / 2 + 4) + '" text-anchor="end" fill="#fff" font-family="' + FONT +
|
|
85
85
|
'" font-size="10" font-weight="bold">' + (state === "added" ? "NEW" : "CHANGED") + "</text>");
|
|
86
86
|
}
|
|
87
|
-
out.push('<text x="' + st.padX + '" y="' + n(st.headerH / 2 + st.headerSize * 0.36) + '" fill="' + C.headerText +
|
|
87
|
+
out.push('<text class="tn" x="' + st.padX + '" y="' + n(st.headerH / 2 + st.headerSize * 0.36) + '" fill="' + C.headerText +
|
|
88
88
|
'" font-family="' + FONT + '" font-size="' + st.headerSize + '">' + esc(t.name) + "</text>");
|
|
89
89
|
if (docMarks && t.doc) {
|
|
90
90
|
out.push(docMark(st.padX, st.headerH / 2 + st.headerSize * 0.36 + 3, t.nameW, "rgba(255,255,255,.7)"));
|
|
@@ -92,24 +92,24 @@
|
|
|
92
92
|
g.cols.forEach(function (c, i) {
|
|
93
93
|
var y = st.headerH + i * st.rowH, cy = y + st.rowH / 2, base = n(cy + st.rowSize * 0.36);
|
|
94
94
|
var weight = c.pk || c.fk ? ' font-weight="bold"' : "";
|
|
95
|
-
var color = c.pk ? C.pk : c.fk ? C.fk : C.text;
|
|
95
|
+
var color = c.pk ? C.pk : c.fk ? C.fk : C.text, rn = c.pk ? "rn rpk" : c.fk ? "rn rfk" : "rn";
|
|
96
96
|
out.push('<g class="r" data-c="' + esc(c.id) + '">');
|
|
97
97
|
out.push('<rect class="rb" y="' + y + '" width="' + w + '" height="' + st.rowH + '" fill="' + C.body + '" fill-opacity="0"/>');
|
|
98
98
|
var cstate = t.diff && t.diff.cols && t.diff.cols[c.id];
|
|
99
99
|
if (cstate) out.push('<rect y="' + (y + 3) + '" width="3" height="' + (st.rowH - 6) + '" fill="' + C[cstate] + '"/>');
|
|
100
|
-
out.push('<text x="' + st.padX + '" y="' + base + '" fill="' + color + '" font-family="' + FONT + '" font-size="' +
|
|
100
|
+
out.push('<text class="' + rn + '" x="' + st.padX + '" y="' + base + '" fill="' + color + '" font-family="' + FONT + '" font-size="' +
|
|
101
101
|
st.rowSize + '"' + weight + ">" + esc(c.name) + "</text>");
|
|
102
102
|
if (docMarks && c.doc) out.push(docMark(st.padX, cy + st.rowSize * 0.36 + 2.5, c.nameW, color));
|
|
103
103
|
var bx = st.padX + c.nameW + 6;
|
|
104
104
|
c.badges.forEach(function (b) {
|
|
105
105
|
var bh = st.badgeSize + 5;
|
|
106
|
-
out.push('<rect x="' + n(bx) + '" y="' + n(cy - bh / 2) + '" width="' + n(b.w) + '" height="' + bh + '" rx="3" fill="' + C.badgeBg + '"/>');
|
|
107
|
-
out.push('<text x="' + n(bx + st.badgePad) + '" y="' + n(cy + st.badgeSize * 0.36) + '" fill="' + C.badgeText +
|
|
106
|
+
out.push('<rect class="bb" x="' + n(bx) + '" y="' + n(cy - bh / 2) + '" width="' + n(b.w) + '" height="' + bh + '" rx="3" fill="' + C.badgeBg + '"/>');
|
|
107
|
+
out.push('<text class="bt" x="' + n(bx + st.badgePad) + '" y="' + n(cy + st.badgeSize * 0.36) + '" fill="' + C.badgeText +
|
|
108
108
|
'" font-family="' + FONT + '" font-size="' + st.badgeSize + '">' + esc(b.t) + "</text>");
|
|
109
109
|
bx += b.w + st.badgeGap;
|
|
110
110
|
});
|
|
111
111
|
if (c.pk) out.push(keyIcon(w - st.padX - c.typeW - st.keyW, cy));
|
|
112
|
-
out.push('<text x="' + (w - st.padX) + '" y="' + base + '" text-anchor="end" fill="' + C.type + '" font-family="' + FONT +
|
|
112
|
+
out.push('<text class="rt" x="' + (w - st.padX) + '" y="' + base + '" text-anchor="end" fill="' + C.type + '" font-family="' + FONT +
|
|
113
113
|
'" font-size="' + st.rowSize + '">' + esc(c.type) + "</text>");
|
|
114
114
|
out.push("</g>");
|
|
115
115
|
});
|
|
@@ -150,7 +150,7 @@
|
|
|
150
150
|
s.push('<path d="M' + n(x + dir * 11) + " " + n(y) + "L" + n(x) + " " + n(y - 6) + "M" + n(x + dir * 11) + " " + n(y) + "L" + n(x) + " " + n(y + 6) + '"' + st + "/>");
|
|
151
151
|
}
|
|
152
152
|
function circle(o) {
|
|
153
|
-
s.push('<circle cx="' + n(x + dir * o) + '" cy="' + n(y) + '" r="3.5" fill="' + C.bg + '" stroke="' + C.edge + '" stroke-width="1.2"/>');
|
|
153
|
+
s.push('<circle class="mc" cx="' + n(x + dir * o) + '" cy="' + n(y) + '" r="3.5" fill="' + C.bg + '" stroke="' + C.edge + '" stroke-width="1.2"/>');
|
|
154
154
|
}
|
|
155
155
|
if (card === "one") { bar(7); }
|
|
156
156
|
else if (card === "many") { crow(); }
|
|
@@ -252,7 +252,7 @@
|
|
|
252
252
|
var x0 = b[0] - pad - edgePad, y0 = b[1] - pad, w = b[2] - b[0] + 2 * (pad + edgePad), h = b[3] - b[1] + 2 * pad;
|
|
253
253
|
var style = opts.fontCss ? "<style>" + opts.fontCss + "</style>" : "";
|
|
254
254
|
return '<svg xmlns="http://www.w3.org/2000/svg" width="' + n(w) + '" height="' + n(h) + '" viewBox="' + n(x0) + " " + n(y0) + " " + n(w) + " " + n(h) + '">' +
|
|
255
|
-
style + '<rect x="' + n(x0) + '" y="' + n(y0) + '" width="' + n(w) + '" height="' + n(h) + '" fill="' + C.bg + '"/>' +
|
|
255
|
+
style + '<rect class="bgr" x="' + n(x0) + '" y="' + n(y0) + '" width="' + n(w) + '" height="' + n(h) + '" fill="' + C.bg + '"/>' +
|
|
256
256
|
renderInner(m, pos, mode, { regions: opts.regions }) + "</svg>";
|
|
257
257
|
}
|
|
258
258
|
|
|
@@ -10,10 +10,71 @@
|
|
|
10
10
|
--sel: #eaf0f8;
|
|
11
11
|
--amber: #f0a020;
|
|
12
12
|
--shadow: 0 1px 2px rgba(16, 24, 40, .05), 0 8px 24px rgba(16, 24, 40, .07);
|
|
13
|
+
--on-ink: #fff;
|
|
14
|
+
--pick: #fde3b0;
|
|
15
|
+
--scroll: #cfd4da;
|
|
16
|
+
--chip-hover: #b8c2d0;
|
|
17
|
+
--tag-bg: #eef1f5;
|
|
18
|
+
--tag-ink: #6b7a8c;
|
|
19
|
+
--tag-pk-bg: #dfe8f6;
|
|
20
|
+
--tag-fk-bg: #ece6f7;
|
|
21
|
+
--tag-fk: #6a4fa3;
|
|
22
|
+
--mini-bg: rgba(255, 255, 255, .94);
|
|
23
|
+
--tip-bg: #15181c;
|
|
24
|
+
/* diagram: the same colours render.js writes as SVG attributes (exports keep those) */
|
|
25
|
+
--t-body: #ffffff;
|
|
26
|
+
--t-border: #dfe3e8;
|
|
27
|
+
--t-text: #333333;
|
|
28
|
+
--t-pk: #2b5797;
|
|
29
|
+
--t-fk: #222222;
|
|
30
|
+
--t-type: #9a9a9a;
|
|
31
|
+
--t-key: #b0b0b0;
|
|
32
|
+
--t-badge-bg: #eef1f5;
|
|
33
|
+
--t-badge-ink: #7a8899;
|
|
34
|
+
--t-edge: #b8bcc2;
|
|
35
|
+
--row-hl: #dbe6f6;
|
|
36
|
+
--row-hover: #eef3fa;
|
|
13
37
|
}
|
|
14
|
-
|
|
38
|
+
/* Dark theme. viewer.js sets data-theme from the toggle, or from prefers-color-scheme when it's on auto. */
|
|
39
|
+
:root[data-theme="dark"] {
|
|
40
|
+
color-scheme: dark;
|
|
41
|
+
--bg: #121418;
|
|
42
|
+
--blue: #7aa7ff;
|
|
43
|
+
--ink: #e6e8eb;
|
|
44
|
+
--ink-2: #b4bac3;
|
|
45
|
+
--muted: #8a929e;
|
|
46
|
+
--panel: #1b1e23;
|
|
47
|
+
--line: #2c313a;
|
|
48
|
+
--hover: #252930;
|
|
49
|
+
--sel: #23324a;
|
|
50
|
+
--shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 8px 24px rgba(0, 0, 0, .35);
|
|
51
|
+
--on-ink: #121418;
|
|
52
|
+
--pick: #5a4210;
|
|
53
|
+
--scroll: #3a404a;
|
|
54
|
+
--chip-hover: #4a5260;
|
|
55
|
+
--tag-bg: #2a2f37;
|
|
56
|
+
--tag-ink: #9aa5b3;
|
|
57
|
+
--tag-pk-bg: #22324a;
|
|
58
|
+
--tag-fk-bg: #2f2840;
|
|
59
|
+
--tag-fk: #b7a2e6;
|
|
60
|
+
--mini-bg: rgba(27, 30, 35, .94);
|
|
61
|
+
--tip-bg: #2a2f37;
|
|
62
|
+
--t-body: #1e2228;
|
|
63
|
+
--t-border: #333944;
|
|
64
|
+
--t-text: #d5d9df;
|
|
65
|
+
--t-pk: #8fb4ff;
|
|
66
|
+
--t-fk: #eceef1;
|
|
67
|
+
--t-type: #7d8693;
|
|
68
|
+
--t-key: #6b7380;
|
|
69
|
+
--t-badge-bg: #2a2f37;
|
|
70
|
+
--t-badge-ink: #9aa5b3;
|
|
71
|
+
--t-edge: #5b6472;
|
|
72
|
+
--row-hl: #24344d;
|
|
73
|
+
--row-hover: #23282f;
|
|
74
|
+
}
|
|
75
|
+
::selection { background: var(--pick); color: var(--ink); }
|
|
15
76
|
:focus-visible { outline: 2px solid var(--amber); outline-offset: 1px; }
|
|
16
|
-
* { scrollbar-width: thin; scrollbar-color:
|
|
77
|
+
* { scrollbar-width: thin; scrollbar-color: var(--scroll) transparent; }
|
|
17
78
|
* { box-sizing: border-box; }
|
|
18
79
|
html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
|
|
19
80
|
font-family: Inter, "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 13px; overflow: hidden; }
|
|
@@ -22,13 +83,26 @@ html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
|
|
|
22
83
|
#canvas { width: 100%; height: 100%; display: block; cursor: grab; touch-action: none; user-select: none; }
|
|
23
84
|
#canvas.panning { cursor: grabbing; }
|
|
24
85
|
|
|
86
|
+
/* diagram colours: CSS beats the SVG presentation attributes, so the page can theme them */
|
|
87
|
+
.tb { fill: var(--t-body); stroke: var(--t-border); }
|
|
88
|
+
.rn { fill: var(--t-text); }
|
|
89
|
+
.rn.rpk { fill: var(--t-pk); }
|
|
90
|
+
.rn.rfk { fill: var(--t-fk); }
|
|
91
|
+
.r .dm { stroke: var(--t-text); }
|
|
92
|
+
.rt { fill: var(--t-type); }
|
|
93
|
+
.ki { stroke: var(--t-key); }
|
|
94
|
+
.bb { fill: var(--t-badge-bg); }
|
|
95
|
+
.bt { fill: var(--t-badge-ink); }
|
|
96
|
+
.e path { stroke: var(--t-edge); }
|
|
97
|
+
.e .mc { stroke: var(--t-edge); fill: var(--bg); }
|
|
98
|
+
|
|
25
99
|
/* states */
|
|
26
100
|
.t, .e { transition: opacity .15s; }
|
|
27
101
|
.focus .t.dim, .focus .e.dim { opacity: .12; }
|
|
28
102
|
.e.hl .ep { stroke: var(--blue); stroke-width: 2.2; }
|
|
29
103
|
.e.hl path, .e.hl circle { stroke: var(--blue); }
|
|
30
|
-
.r.hl .rb { fill:
|
|
31
|
-
.r:hover .rb { fill:
|
|
104
|
+
.r.hl .rb { fill: var(--row-hl); fill-opacity: 1; }
|
|
105
|
+
.r:hover .rb { fill: var(--row-hover); fill-opacity: 1; }
|
|
32
106
|
.t.flash .tb { stroke: #f0a020; stroke-width: 3; }
|
|
33
107
|
.t { cursor: grab; }
|
|
34
108
|
#canvas.panning .t { cursor: grabbing; }
|
|
@@ -39,13 +113,13 @@ html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
|
|
|
39
113
|
.bar button, .seg button { border: 0; background: transparent; color: var(--ink-2); font: inherit; font-weight: 500;
|
|
40
114
|
cursor: pointer; padding: 5px 9px; border-radius: 6px; transition: background .12s, color .12s; }
|
|
41
115
|
.bar button:hover, .seg button:hover { background: var(--hover); color: var(--ink); }
|
|
42
|
-
.bar button.on { background: var(--ink); color:
|
|
116
|
+
.bar button.on { background: var(--ink); color: var(--on-ink); }
|
|
43
117
|
.zoomval { min-width: 46px; text-align: center; color: var(--muted); font-variant-numeric: tabular-nums; }
|
|
44
118
|
.seg { display: flex; background: var(--hover); border-radius: 7px; padding: 2px; }
|
|
45
119
|
.seg button.on { background: var(--panel); color: var(--ink); box-shadow: 0 1px 2px rgba(16, 24, 40, .12); }
|
|
46
120
|
.sep { width: 1px; height: 18px; background: var(--line); margin: 0 4px; }
|
|
47
121
|
.bar button svg, .exhead button svg, .details .close svg { display: block; }
|
|
48
|
-
#zin, #zout, #zfit { padding: 5px 6px; }
|
|
122
|
+
#zin, #zout, #zfit, #xtheme { padding: 5px 6px; }
|
|
49
123
|
|
|
50
124
|
/* explorer (left): search + table tree grouped by file/folder */
|
|
51
125
|
.explorer { position: absolute; top: 14px; left: 16px; width: 270px; max-height: calc(100% - 60px); display: flex;
|
|
@@ -62,7 +136,7 @@ html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
|
|
|
62
136
|
.exfilter { padding: 6px 6px 4px; border-bottom: 1px solid var(--line); }
|
|
63
137
|
.explorer.collapsed .exfilter { display: none; }
|
|
64
138
|
.exfilter input { width: 100%; padding: 5px 8px; border: 1px solid var(--line); border-radius: 7px; font: 12px ui-monospace, Menlo, Consolas, monospace;
|
|
65
|
-
color: var(--ink); outline: none; }
|
|
139
|
+
color: var(--ink); background: var(--panel); outline: none; }
|
|
66
140
|
.exfilter input:focus { border-color: var(--ink-2); }
|
|
67
141
|
.exfilter input.bad { border-color: #e57373; }
|
|
68
142
|
.fstat { font-size: 11px; color: var(--muted); padding: 3px 2px 0; min-height: 14px; }
|
|
@@ -70,10 +144,10 @@ html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
|
|
|
70
144
|
.fstat a { color: var(--blue); cursor: pointer; margin-left: 6px; }
|
|
71
145
|
.tagbar { display: flex; flex-wrap: wrap; gap: 4px; padding: 4px 0 2px; }
|
|
72
146
|
.tchip { display: inline-flex; align-items: center; gap: 4px; font: 11px/1.6 inherit; padding: 0 7px 0 5px; border: 1px solid var(--line);
|
|
73
|
-
border-radius: 10px; background:
|
|
147
|
+
border-radius: 10px; background: var(--panel); color: var(--ink); cursor: pointer; }
|
|
74
148
|
.tchip i { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
|
|
75
149
|
.tchip small { color: var(--muted); }
|
|
76
|
-
.tchip:hover { border-color:
|
|
150
|
+
.tchip:hover { border-color: var(--chip-hover); }
|
|
77
151
|
.tchip.on { border-color: var(--ink-2); background: var(--sel); }
|
|
78
152
|
.tdot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; margin: 0 6px 1px -13px; vertical-align: middle; }
|
|
79
153
|
.tree.flat .tdot { margin-left: 0; }
|
|
@@ -117,10 +191,10 @@ html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
|
|
|
117
191
|
.details td.cn { font-weight: 600; word-break: break-word; }
|
|
118
192
|
.details td.ct { color: var(--muted); text-align: right; white-space: nowrap; }
|
|
119
193
|
.details .cd { color: var(--ink-2); font-weight: normal; margin-top: 2px; white-space: pre-wrap; }
|
|
120
|
-
.details .tag { display: inline-block; font-size: 10px; padding: 0 5px; border-radius: 3px; background:
|
|
121
|
-
color:
|
|
122
|
-
.details .tag.pk { background:
|
|
123
|
-
.details .tag.fk { background:
|
|
194
|
+
.details .tag { display: inline-block; font-size: 10px; padding: 0 5px; border-radius: 3px; background: var(--tag-bg);
|
|
195
|
+
color: var(--tag-ink); margin: 0 3px 0 0; font-weight: normal; }
|
|
196
|
+
.details .tag.pk { background: var(--tag-pk-bg); color: var(--blue); }
|
|
197
|
+
.details .tag.fk { background: var(--tag-fk-bg); color: var(--tag-fk); }
|
|
124
198
|
.details ul { list-style: none; margin: 0; padding: 0; }
|
|
125
199
|
.details li { padding: 6px 0; border-top: 1px solid var(--line); }
|
|
126
200
|
.details a { color: var(--blue); text-decoration: none; cursor: pointer; }
|
|
@@ -130,19 +204,19 @@ html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
|
|
|
130
204
|
.details .hidden { color: var(--muted); }
|
|
131
205
|
|
|
132
206
|
/* minimap (bottom right) */
|
|
133
|
-
.minimap { position: absolute; right: 16px; bottom: 16px; width: 200px; height: 140px; background:
|
|
207
|
+
.minimap { position: absolute; right: 16px; bottom: 16px; width: 200px; height: 140px; background: var(--mini-bg);
|
|
134
208
|
border: 1px solid var(--line); border-radius: 10px; box-shadow: var(--shadow); overflow: hidden; cursor: pointer; }
|
|
135
209
|
.minimap svg { width: 100%; height: 100%; display: block; }
|
|
136
210
|
.minimap .vpr { fill: rgba(43,87,151,.12); stroke: var(--blue); stroke-width: 1.5; vector-effect: non-scaling-stroke; }
|
|
137
211
|
|
|
138
|
-
.toast { position: absolute; left: 50%; bottom: 24px; transform: translateX(-50%); background: var(--
|
|
212
|
+
.toast { position: absolute; left: 50%; bottom: 24px; transform: translateX(-50%); background: var(--tip-bg); color: #fff;
|
|
139
213
|
padding: 8px 14px; border-radius: 8px; font-size: 12px; opacity: 0; transition: opacity .2s; pointer-events: none; z-index: 6; }
|
|
140
214
|
.toast.open { opacity: 1; }
|
|
141
215
|
|
|
142
216
|
/* path finder (in the details panel) */
|
|
143
217
|
.details .pathbox { display: flex; gap: 6px; margin: 10px 0 4px; }
|
|
144
218
|
.details .pathbox input { flex: 1; min-width: 0; padding: 6px 9px; border: 1px solid var(--line); border-radius: 7px; font: inherit;
|
|
145
|
-
color: var(--ink); outline: none; }
|
|
219
|
+
color: var(--ink); background: var(--panel); outline: none; }
|
|
146
220
|
.details .pathbox input:focus { border-color: var(--ink-2); }
|
|
147
221
|
.details .pathres ol { margin: 6px 0 8px 18px; padding: 0; }
|
|
148
222
|
.details .pathres li { border: 0; padding: 2px 0; }
|
|
@@ -173,7 +247,7 @@ html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
|
|
|
173
247
|
.details .state.changed { background: #d48806; }
|
|
174
248
|
|
|
175
249
|
/* doc-comment tooltip */
|
|
176
|
-
.tip { position: absolute; display: none; max-width: 340px; background: var(--
|
|
250
|
+
.tip { position: absolute; display: none; max-width: 340px; background: var(--tip-bg); color: #eef0f2; border-radius: 8px;
|
|
177
251
|
padding: 8px 10px; font-size: 12px; line-height: 1.45; pointer-events: none; box-shadow: 0 4px 14px rgba(0,0,0,.25);
|
|
178
252
|
z-index: 5; }
|
|
179
253
|
.tip.open { display: block; }
|
|
@@ -287,6 +287,26 @@
|
|
|
287
287
|
}
|
|
288
288
|
regionBtn.onclick = function () { setRegions(!S.regions); };
|
|
289
289
|
|
|
290
|
+
/* Theme: auto (follow the OS) -> dark -> light. A viewer preference, so localStorage, not the URL hash.
|
|
291
|
+
* The <head> script applies it before paint; exports always use render.js's light colours. */
|
|
292
|
+
var themeBtn = document.getElementById("xtheme"), darkQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
293
|
+
function themePref() {
|
|
294
|
+
try { var t = localStorage.getItem("qdbd-theme"); return t === "light" || t === "dark" ? t : "auto"; } catch (e) { return "auto"; }
|
|
295
|
+
}
|
|
296
|
+
function applyTheme() {
|
|
297
|
+
var pref = themePref(), t = pref === "auto" ? (darkQuery.matches ? "dark" : "light") : pref;
|
|
298
|
+
document.documentElement.setAttribute("data-theme", t);
|
|
299
|
+
themeBtn.title = "Theme: " + pref;
|
|
300
|
+
}
|
|
301
|
+
themeBtn.onclick = function () {
|
|
302
|
+
var next = { auto: "dark", dark: "light", light: "auto" }[themePref()];
|
|
303
|
+
try { if (next === "auto") localStorage.removeItem("qdbd-theme"); else localStorage.setItem("qdbd-theme", next); } catch (e) { /* private mode */ }
|
|
304
|
+
applyTheme();
|
|
305
|
+
showToast("Theme: " + next);
|
|
306
|
+
};
|
|
307
|
+
if (darkQuery.addEventListener) darkQuery.addEventListener("change", applyTheme);
|
|
308
|
+
applyTheme();
|
|
309
|
+
|
|
290
310
|
function setMode(mode) {
|
|
291
311
|
S.mode = ["full", "keys", "header"].indexOf(mode) >= 0 ? mode : "full";
|
|
292
312
|
document.querySelectorAll("#modes button").forEach(function (o) { o.classList.toggle("on", o.getAttribute("data-mode") === S.mode); });
|
|
@@ -39,6 +39,7 @@ ICONS = {
|
|
|
39
39
|
"icon_minus": _icon("M5 12h14"),
|
|
40
40
|
"icon_plus": _icon("M12 5v14M5 12h14"),
|
|
41
41
|
"icon_fit": _icon("M4 9V4h5M15 4h5v5M20 15v5h-5M9 20H4v-5"),
|
|
42
|
+
"icon_theme": _icon("M12 3a9 9 0 1 0 0 18a9 9 0 1 0 0-18zM12 3v18M12 8h4.5M12 12h6M12 16h4.5"),
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
|
|
@@ -82,6 +83,8 @@ BODY = """
|
|
|
82
83
|
<span class="zoomval" id="zoomval">100%</span>
|
|
83
84
|
<button id="zin" title="Zoom in" aria-label="Zoom in">{icon_plus}</button>
|
|
84
85
|
<button id="zfit" title="Fit to screen" aria-label="Fit to screen">{icon_fit}</button>
|
|
86
|
+
<div class="sep"></div>
|
|
87
|
+
<button id="xtheme" title="Theme: auto" aria-label="Switch light / dark theme">{icon_theme}</button>
|
|
85
88
|
</div>
|
|
86
89
|
<div class="minimap">
|
|
87
90
|
<svg id="mini" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet"></svg>
|
|
@@ -111,6 +114,14 @@ FILTER_HELP = (
|
|
|
111
114
|
)
|
|
112
115
|
|
|
113
116
|
|
|
117
|
+
# Runs in <head> so the page paints in the right theme. viewer.js owns the toggle; keep the key in sync.
|
|
118
|
+
THEME_JS = (
|
|
119
|
+
"(function(){var t;try{t=localStorage.getItem('qdbd-theme');}catch(e){}"
|
|
120
|
+
"if(t!=='light'&&t!=='dark')t=matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light';"
|
|
121
|
+
"document.documentElement.setAttribute('data-theme',t);})();"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
114
125
|
# Read-only live view: refetch the model whenever the server's version changes. Relative URLs, so it
|
|
115
126
|
# also works behind a tunnel or a path prefix.
|
|
116
127
|
LIVE_JS = (
|
|
@@ -151,6 +162,7 @@ def render_page(model: dict, positions: dict, title: str, serve: bool = False, l
|
|
|
151
162
|
'<meta name="viewport" content="width=device-width, initial-scale=1">'
|
|
152
163
|
'<meta name="generator" content="qdbd">'
|
|
153
164
|
f"<title>{_escape(title)}</title>"
|
|
165
|
+
f"<script>{THEME_JS}</script>"
|
|
154
166
|
# The font CSS lives in its own <style> so the viewer can reuse it for SVG/PNG export
|
|
155
167
|
# instead of embedding the fonts a second time.
|
|
156
168
|
f'<style id="qdbd-font">{font_css()}</style>'
|
|
@@ -281,6 +281,16 @@ def test_page_has_share_link_controls():
|
|
|
281
281
|
assert 'id="xlink"' in html and 'id="toast"' in html
|
|
282
282
|
|
|
283
283
|
|
|
284
|
+
def test_page_has_theme_toggle_and_exports_stay_light():
|
|
285
|
+
m = model_json(compile_text(SMALL))
|
|
286
|
+
pos = {"Customer": [0, 0], "Order": [300, 0]}
|
|
287
|
+
html = render_page(m, pos, "t")
|
|
288
|
+
assert 'id="xtheme"' in html and ':root[data-theme="dark"]' in html
|
|
289
|
+
assert html.index("qdbd-theme") < html.index("</head>") # theme applied before first paint
|
|
290
|
+
svg = export.render_svg(m, pos, embed_font=False)
|
|
291
|
+
assert "data-theme" not in svg and 'fill="#ffffff"' in svg and "#1e2228" not in svg
|
|
292
|
+
|
|
293
|
+
|
|
284
294
|
def test_drawio_export(tmp_path):
|
|
285
295
|
import xml.etree.ElementTree as ET
|
|
286
296
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.14"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|