@minonl/dia 0.0.14 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/cli.js +1594 -33
- package/docs/cli.md +3 -2
- package/docs/design/graph-prio-dag.md +85 -0
- package/docs/design/graph-ui-m1.md +106 -0
- package/docs/design/graph-web-m1.md +113 -0
- package/docs/design/graph-web-m2.md +92 -0
- package/docs/design/graph-web-mockup.html +667 -133
- package/docs/design/graph-web.md +147 -43
- package/docs/design/seat-scheduling-m2.md +138 -0
- package/docs/index.md +8 -1
- package/docs/install-guard-design.md +148 -0
- package/docs/kit-install.md +7 -0
- package/docs/kit-layer.md +78 -0
- package/docs/migrations/data-root-2026-11.md +3 -2
- package/docs/status.md +3 -0
- package/kits/git/dia.json +10 -0
- package/kits/git/knowledge/hooks.md +30 -0
- package/kits/git/knowledge/worktree.md +34 -0
- package/kits/node/dia.json +10 -0
- package/kits/node/knowledge/hook-managers.md +17 -0
- package/kits/node/knowledge/pnpm.md +34 -0
- package/package.json +1 -1
- package/packages/wt/bin/dia-wt +15 -0
- package/packages/wt/tests/adopt.test.sh +8 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
-
<title>dia graph ·
|
|
6
|
+
<title>dia graph · 视觉设计稿 v0.3</title>
|
|
7
7
|
<style>
|
|
8
8
|
:root{
|
|
9
9
|
--bg:#f6f7f9; --panel:#fff; --ink:#111827; --muted:#6b7280; --line:#e5e7eb;
|
|
@@ -16,36 +16,75 @@
|
|
|
16
16
|
}
|
|
17
17
|
*{box-sizing:border-box}
|
|
18
18
|
body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.5 ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif}
|
|
19
|
-
header{display:flex;align-items:center;gap:16px;padding:
|
|
19
|
+
header{display:flex;align-items:center;gap:16px;padding:14px 22px;border-bottom:1px solid var(--line);background:var(--panel);flex-wrap:wrap}
|
|
20
20
|
header h1{font-size:17px;margin:0;letter-spacing:.2px}
|
|
21
21
|
header .sub{color:var(--muted);font-size:12px}
|
|
22
22
|
header .spacer{flex:1}
|
|
23
|
+
.tabs{display:flex;border:1px solid var(--line);border-radius:9px;overflow:hidden}
|
|
24
|
+
.tabs button{border:0;background:transparent;color:var(--muted);padding:6px 12px;font:inherit;font-size:12px;cursor:pointer}
|
|
25
|
+
.tabs button.on{background:var(--blue);color:#fff}
|
|
23
26
|
.pill{border:1px solid var(--line);border-radius:999px;padding:4px 10px;font-size:12px;color:var(--muted);background:var(--bg)}
|
|
24
|
-
main{display:grid;grid-template-columns:minmax(0,1fr)
|
|
27
|
+
main{display:grid;grid-template-columns:minmax(0,1fr) 320px;gap:18px;padding:18px;max-width:1480px;margin:0 auto}
|
|
25
28
|
.panel{background:var(--panel);border:1px solid var(--line);border-radius:14px;box-shadow:var(--shadow)}
|
|
26
29
|
.toolbar{display:flex;gap:8px;align-items:center;padding:12px 14px;border-bottom:1px solid var(--line);flex-wrap:wrap}
|
|
27
30
|
.seg{display:flex;border:1px solid var(--line);border-radius:9px;overflow:hidden}
|
|
28
31
|
.seg button{border:0;background:transparent;color:var(--muted);padding:6px 10px;font:inherit;font-size:12px;cursor:pointer}
|
|
29
32
|
.seg button.on{background:var(--blue);color:#fff}
|
|
33
|
+
.seg button:disabled{opacity:.35;cursor:not-allowed}
|
|
34
|
+
.win-select{border:1px solid var(--line);background:var(--bg);color:var(--ink);border-radius:8px;padding:6px 8px;font:inherit;font-size:12px}
|
|
35
|
+
.search{position:relative;display:flex;align-items:center;gap:4px;flex-wrap:wrap;border:1px solid var(--line);background:var(--bg);border-radius:8px;padding:4px 8px;min-width:240px;max-width:420px;cursor:text}
|
|
36
|
+
.search input{border:0;background:transparent;color:var(--ink);padding:2px 0;font:inherit;font-size:12px;flex:1;min-width:80px;outline:none}
|
|
37
|
+
.token{display:inline-flex;align-items:center;gap:4px;background:color-mix(in srgb,var(--blue) 22%,transparent);color:var(--blue);border-radius:999px;padding:2px 4px 2px 8px;font-size:11px;white-space:nowrap}
|
|
38
|
+
.token button{border:0;background:transparent;color:inherit;cursor:pointer;font:inherit;font-size:12px;line-height:1;padding:0 2px}
|
|
39
|
+
.search .q-list{position:absolute;left:0;top:calc(100% + 4px);z-index:20;width:300px;max-height:250px;overflow:auto;background:var(--panel);border:1px solid var(--line);border-radius:10px;box-shadow:var(--shadow);display:none}
|
|
40
|
+
.search .q-list.open{display:block}
|
|
41
|
+
.q-item{padding:6px 10px;font-size:12px;cursor:pointer;display:flex;justify-content:space-between;gap:10px}
|
|
42
|
+
.q-item:hover,.q-item.active{background:color-mix(in srgb,var(--blue) 18%,transparent)}
|
|
43
|
+
.q-item .k{color:var(--muted)}
|
|
44
|
+
.q-item .q-prio{font-style:normal;font-size:10px;color:var(--muted);margin-left:6px}
|
|
45
|
+
.q-item.sel{color:var(--blue)}
|
|
46
|
+
.q-item.sel .k{color:var(--blue)}
|
|
47
|
+
.q-head{padding:6px 10px;font-size:10px;color:var(--muted);border-bottom:1px solid var(--line)}
|
|
48
|
+
.q-empty{padding:10px;font-size:12px;color:var(--muted)}
|
|
49
|
+
.node.hit{outline:2px solid var(--blue);outline-offset:1px}
|
|
50
|
+
.node.dim{opacity:.22}
|
|
51
|
+
.focus-chip{display:none;align-items:center;gap:4px;border:1px solid var(--blue);color:var(--blue);border-radius:999px;padding:3px 4px 3px 10px;font-size:11px}
|
|
52
|
+
.focus-chip.on{display:inline-flex}
|
|
53
|
+
.focus-chip button{border:0;background:transparent;color:var(--blue);cursor:pointer;font:inherit;font-size:11px;padding:0 6px}
|
|
54
|
+
.focus-chip .fd{border:0;background:transparent;color:var(--blue);font:inherit;font-size:11px}
|
|
30
55
|
.toolbar .hint{margin-left:auto;color:var(--muted);font-size:12px}
|
|
31
|
-
.canvas{position:relative;height:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
svg.edges
|
|
35
|
-
|
|
36
|
-
svg.edges line.claims{stroke:var(--cyan);stroke-dasharray:5 4}
|
|
37
|
-
.node{position:absolute;width:206px;border:1px solid var(--line);border-left:4px solid var(--muted);border-radius:10px;background:var(--panel);padding:9px 11px;box-shadow:var(--shadow);cursor:pointer;transition:transform .12s ease,box-shadow .12s ease}
|
|
38
|
-
.node:hover{transform:translateY(-1px);box-shadow:0 10px 28px rgba(17,24,39,.12)}
|
|
39
|
-
.node.task.doing{border-left-color:var(--blue)}
|
|
40
|
-
.node.task.blocked{border-left-color:var(--red)}
|
|
41
|
-
.node.task.review{border-left-color:var(--amber)}
|
|
42
|
-
.node.task.done,.node.task.accepted{border-left-color:var(--green)}
|
|
56
|
+
.canvas{position:relative;height:620px;overflow:hidden}
|
|
57
|
+
.canvas-scroll{position:absolute;inset:0;overflow:auto;padding:10px}
|
|
58
|
+
.lane-rail{position:absolute;left:0;top:0;bottom:0;width:110px;z-index:6;pointer-events:none;background:linear-gradient(90deg,var(--panel) 86%,transparent)}
|
|
59
|
+
svg.edges{position:absolute;inset:0;pointer-events:none;overflow:visible}
|
|
60
|
+
.node{position:absolute;width:190px;border:1px solid var(--line);border-left:1px solid var(--line);border-radius:10px;background:var(--panel);padding:8px 10px;box-shadow:var(--shadow);cursor:pointer}
|
|
43
61
|
.node.path{border-left-color:var(--violet)}
|
|
44
62
|
.node.session{border-left-color:var(--cyan)}
|
|
45
|
-
.node b{display:block;font-size:13px;line-height:1.
|
|
63
|
+
.node b{display:block;font-size:13px;line-height:1.3;margin-bottom:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
46
64
|
.node .meta{color:var(--muted);font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
47
|
-
.
|
|
48
|
-
.
|
|
65
|
+
.progress{height:4px;border-radius:99px;background:var(--line);margin-top:6px;overflow:hidden}
|
|
66
|
+
.progress i{display:block;height:100%;background:var(--blue);background-image:linear-gradient(45deg,rgba(255,255,255,.28) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.28) 50%,rgba(255,255,255,.28) 75%,transparent 75%);background-size:12px 12px;animation:flow 1s linear infinite}
|
|
67
|
+
.progress i.blocked{background-color:var(--red);animation-play-state:paused;opacity:.65}
|
|
68
|
+
.progress i.review{background-color:var(--amber)}
|
|
69
|
+
.progress i.done,.progress i.accepted{background-color:var(--muted);background-image:none;animation:none}
|
|
70
|
+
.progress i.todo{background-color:var(--muted);background-image:none;animation:none}
|
|
71
|
+
@keyframes flow{to{background-position:12px 0}}
|
|
72
|
+
.st{font-weight:500}
|
|
73
|
+
.st.doing{color:var(--ink)} .st.blocked{color:var(--red)} .st.review{color:var(--amber)}
|
|
74
|
+
.st.done,.st.accepted{color:var(--muted)} .st.todo{color:var(--muted)}
|
|
75
|
+
.prio{float:right;font-size:10px;font-weight:600;border-radius:5px;padding:0 6px;color:#fff;margin-left:4px}
|
|
76
|
+
.prio{background:transparent;border:1px solid var(--line)}
|
|
77
|
+
.prio.p0{background:var(--red);border-color:var(--red);color:#fff}
|
|
78
|
+
.prio.p1{border-color:var(--amber);color:var(--amber)}
|
|
79
|
+
.prio.p2{border-color:var(--line);color:var(--muted)}
|
|
80
|
+
.pgroup{position:absolute;left:0;pointer-events:none;border-bottom:1px dashed var(--line)}
|
|
81
|
+
.plane-label{position:absolute;left:20px;width:78px;z-index:6;pointer-events:none;padding:2px 6px 2px 0;background:linear-gradient(90deg,var(--panel) 72%,transparent);transform:translateY(-50%);font-size:13px;font-weight:700;letter-spacing:.1em}
|
|
82
|
+
.plane-label small{display:block;font-size:9px;font-weight:400;letter-spacing:0;opacity:.7;margin-top:2px}
|
|
83
|
+
.legend-overlay{position:absolute;right:12px;bottom:12px;z-index:5;background:color-mix(in srgb,var(--panel) 92%,transparent);border:1px solid var(--line);border-radius:10px;padding:8px 10px;box-shadow:var(--shadow);font-size:11px;color:var(--muted);display:grid;gap:5px;min-width:190px}
|
|
84
|
+
.legend-overlay .row{display:flex;align-items:center;gap:8px;white-space:nowrap}
|
|
85
|
+
.legend-overlay .sample{width:38px;height:10px;flex:0 0 38px}
|
|
86
|
+
.legend-overlay b{color:var(--ink);font-weight:600}
|
|
87
|
+
.canvas.simple .legend-overlay .detail-row{display:none}
|
|
49
88
|
aside{display:flex;flex-direction:column;gap:14px}
|
|
50
89
|
.side-title{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;border-bottom:1px solid var(--line)}
|
|
51
90
|
.side-title h2{font-size:13px;margin:0}
|
|
@@ -54,21 +93,68 @@
|
|
|
54
93
|
.item{display:flex;justify-content:space-between;gap:10px;padding:8px 0;border-bottom:1px dashed var(--line);font-size:12px}
|
|
55
94
|
.item:last-child{border-bottom:0}
|
|
56
95
|
.item .k{color:var(--muted)}
|
|
57
|
-
.dot{display:inline-block;width:7px;height:7px;border-radius:99px;background:var(--muted);margin-right:6px;vertical-align:1px}
|
|
58
|
-
.dot.blue{background:var(--blue)} .dot.red{background:var(--red)} .dot.green{background:var(--green)} .dot.amber{background:var(--amber)}
|
|
59
|
-
.legend{padding:10px 14px 14px;color:var(--muted);font-size:12px;display:grid;gap:6px}
|
|
60
|
-
.legend i{display:inline-block;width:18px;height:2px;margin-right:8px;vertical-align:3px}
|
|
61
96
|
.attention{padding:8px 14px 14px}
|
|
62
97
|
.attention .row{display:flex;gap:8px;align-items:flex-start;padding:8px 0;border-bottom:1px dashed var(--line);font-size:12px}
|
|
63
98
|
.attention .row:last-child{border-bottom:0}
|
|
64
99
|
.tag{font-size:10px;border-radius:6px;padding:1px 6px;background:rgba(220,38,38,.12);color:var(--red);white-space:nowrap}
|
|
65
100
|
.tag.amber{background:rgba(217,119,6,.12);color:var(--amber)}
|
|
66
|
-
.
|
|
67
|
-
.
|
|
68
|
-
.
|
|
69
|
-
.
|
|
70
|
-
.
|
|
101
|
+
.attention .row{cursor:pointer}
|
|
102
|
+
.quick{display:flex;gap:6px;margin-top:6px}
|
|
103
|
+
.quick button,.btn{border:1px solid var(--line);background:var(--bg);color:var(--ink);border-radius:7px;padding:3px 8px;font:inherit;font-size:11px;cursor:pointer}
|
|
104
|
+
.quick button.primary,.btn.primary{background:var(--blue);border-color:var(--blue);color:#fff}
|
|
105
|
+
.backdrop{position:fixed;inset:0;background:rgba(0,0,0,.35);opacity:0;pointer-events:none;transition:opacity .18s;z-index:40}
|
|
106
|
+
.backdrop.show{opacity:1;pointer-events:auto}
|
|
107
|
+
.drawer{position:fixed;top:0;right:0;bottom:0;width:440px;max-width:92vw;background:var(--panel);border-left:1px solid var(--line);box-shadow:-12px 0 40px rgba(0,0,0,.25);transform:translateX(100%);transition:transform .2s ease;z-index:50;display:flex;flex-direction:column}
|
|
108
|
+
.drawer.show{transform:none}
|
|
109
|
+
.drawer-head{display:flex;align-items:flex-start;gap:10px;padding:14px 16px;border-bottom:1px solid var(--line)}
|
|
110
|
+
.drawer-head h3{font-size:15px;margin:0 0 3px}
|
|
111
|
+
.drawer-head .meta{color:var(--muted);font-size:11px}
|
|
112
|
+
.drawer-head .close{margin-left:auto;border:0;background:transparent;color:var(--muted);font-size:18px;cursor:pointer}
|
|
113
|
+
.drawer-tabs{display:flex;gap:6px;padding:10px 16px;border-bottom:1px solid var(--line)}
|
|
114
|
+
.drawer-tabs button{border:1px solid var(--line);background:transparent;color:var(--muted);border-radius:7px;padding:4px 10px;font:inherit;font-size:12px;cursor:pointer}
|
|
115
|
+
.drawer-tabs button.on{background:var(--blue);border-color:var(--blue);color:#fff}
|
|
116
|
+
.drawer-body{padding:14px 16px 24px;overflow:auto}
|
|
117
|
+
.drawer-body h4{font-size:12px;margin:14px 0 6px;color:var(--muted);letter-spacing:.05em;text-transform:uppercase}
|
|
118
|
+
.kv{display:grid;grid-template-columns:70px 1fr;gap:4px 10px;font-size:12px}
|
|
71
119
|
.kv .k{color:var(--muted)}
|
|
120
|
+
.iv-seg{display:flex;border:1px solid var(--line);border-radius:9px;overflow:hidden;margin-bottom:12px}
|
|
121
|
+
.iv-seg button{flex:1;border:0;background:transparent;color:var(--muted);padding:7px 8px;font:inherit;font-size:12px;cursor:pointer}
|
|
122
|
+
.iv-seg button.on{background:var(--blue);color:#fff}
|
|
123
|
+
.iv-row{display:grid;gap:6px;margin-bottom:10px}
|
|
124
|
+
.iv-row label{font-size:11px;color:var(--muted)}
|
|
125
|
+
.iv-row select,.iv-row textarea{width:100%;border:1px solid var(--line);border-radius:8px;background:var(--bg);color:var(--ink);padding:8px;font:inherit;font-size:12px}
|
|
126
|
+
.iv-row textarea{min-height:96px;resize:vertical}
|
|
127
|
+
.iv-status{color:var(--green);font-size:11px;margin-left:8px}
|
|
128
|
+
.rel-list .item{display:flex;justify-content:space-between;gap:10px;padding:7px 0;border-bottom:1px dashed var(--line);font-size:12px}
|
|
129
|
+
.rel-list .item:last-child{border-bottom:0}
|
|
130
|
+
.rel-list .k{color:var(--muted)}
|
|
131
|
+
.kv{display:grid;grid-template-columns:70px 1fr;gap:4px 10px;font-size:12px}
|
|
132
|
+
.kv .k{color:var(--muted)}
|
|
133
|
+
/* timeline */
|
|
134
|
+
.timeline{padding:14px 16px 18px;overflow:hidden;position:relative}
|
|
135
|
+
.tl-grid{display:grid;grid-template-columns:190px minmax(0,1fr);gap:8px;align-items:start}
|
|
136
|
+
.tl-labels{overflow:hidden;position:relative}
|
|
137
|
+
.tl-label-head{height:34px;display:flex;align-items:flex-end;font-size:11px;color:var(--muted);padding-bottom:6px}
|
|
138
|
+
.tl-labels-body{will-change:transform}
|
|
139
|
+
.tl-label-row{height:38px;box-sizing:border-box;padding:6px 8px 4px 0;border-top:1px dashed var(--line);font-size:12px;overflow:hidden}
|
|
140
|
+
.tl-label-row b{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
141
|
+
.tl-label-row span{color:var(--muted);font-size:10px}
|
|
142
|
+
.tl-scroll{overflow:auto;max-height:620px;scrollbar-width:thin}
|
|
143
|
+
.scroll-hint{position:absolute;right:18px;top:10px;z-index:6;background:var(--panel);border:1px solid var(--line);border-radius:999px;padding:3px 9px;font-size:10px;color:var(--muted);box-shadow:var(--shadow)}
|
|
144
|
+
.tl-head{display:grid;grid-template-columns:var(--tlw,900px);gap:8px;align-items:end;color:var(--muted);font-size:11px;margin-bottom:8px;position:sticky;top:0;z-index:4;background:var(--panel)}
|
|
145
|
+
.tl-axis{position:relative;height:34px;width:var(--tlw,900px)}
|
|
146
|
+
.tl-axis span{position:absolute;transform:translateX(-50%);white-space:nowrap;line-height:1.2}
|
|
147
|
+
.tl-axis span small{display:block;font-size:9px;opacity:.55}
|
|
148
|
+
.tl-body{position:relative}
|
|
149
|
+
.tl-row{display:grid;grid-template-columns:var(--tlw,900px);gap:8px;align-items:start;min-height:38px;border-top:1px dashed var(--line)}
|
|
150
|
+
.tl-track{position:relative;height:28px;overflow:hidden;width:var(--tlw,900px);margin-top:4px}
|
|
151
|
+
.tl-bar{position:absolute;top:7px;height:14px;border-radius:5px;background:var(--blue);opacity:.9;cursor:pointer}
|
|
152
|
+
.tl-bar .fill{position:absolute;left:0;top:0;bottom:0;border-radius:5px;background:rgba(255,255,255,.45)}
|
|
153
|
+
.tl-bar.blocked{background:var(--red)} .tl-bar.done,.tl-bar.accepted,.tl-bar.todo{background:var(--muted)}
|
|
154
|
+
.tl-bar.review{background:var(--amber)}
|
|
155
|
+
.tl-bar.critical{outline:2px solid var(--amber);outline-offset:2px}
|
|
156
|
+
.tl-today{position:absolute;top:0;bottom:0;pointer-events:none;z-index:2;border-left:1px dashed var(--red);color:var(--red);font-size:10px;padding-left:4px}
|
|
157
|
+
.tl-caption{color:var(--muted);font-size:12px;padding:10px 0 2px}
|
|
72
158
|
footer{padding:0 22px 22px;color:var(--muted);font-size:11px}
|
|
73
159
|
@media (max-width:980px){ main{grid-template-columns:1fr} .canvas{height:560px} }
|
|
74
160
|
</style>
|
|
@@ -76,34 +162,80 @@
|
|
|
76
162
|
<body>
|
|
77
163
|
<header>
|
|
78
164
|
<h1>dia graph</h1>
|
|
79
|
-
<span class="sub">只读依赖图 · 视觉设计稿 v0.
|
|
80
|
-
<
|
|
165
|
+
<span class="sub">只读依赖图 + 时间线 · 视觉设计稿 v0.3</span>
|
|
166
|
+
<div class="tabs" id="tabs">
|
|
167
|
+
<button class="on" data-v="graph">关系图</button>
|
|
168
|
+
<button data-v="time">时间线</button>
|
|
169
|
+
</div>
|
|
81
170
|
<span class="spacer"></span>
|
|
82
|
-
<span class="pill"
|
|
171
|
+
<span class="pill">静态 mockup · 无网络</span>
|
|
172
|
+
<span class="pill" id="route">#/graph</span>
|
|
173
|
+
<span class="pill" id="updated">updated 18:42</span>
|
|
83
174
|
</header>
|
|
84
175
|
|
|
85
176
|
<main>
|
|
86
177
|
<section class="panel">
|
|
87
178
|
<div class="toolbar">
|
|
88
|
-
<div
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
179
|
+
<div id="graph-tools" style="display:flex;gap:8px;align-items:center">
|
|
180
|
+
<div class="seg" id="layout">
|
|
181
|
+
<button class="on" data-l="priority">优先级视图</button>
|
|
182
|
+
<button data-l="dep">依赖视图</button>
|
|
183
|
+
</div>
|
|
184
|
+
<div class="seg" id="mode">
|
|
185
|
+
<button class="on" data-m="simple">简约</button>
|
|
186
|
+
<button data-m="detail">详细</button>
|
|
187
|
+
</div>
|
|
188
|
+
<div class="search" id="search-box">
|
|
189
|
+
<span id="tokens" style="display:contents"></span>
|
|
190
|
+
<input id="q" type="search" placeholder="搜索任务,选中后变成 tag" autocomplete="off">
|
|
191
|
+
<div class="q-list" id="q-list"></div>
|
|
192
|
+
</div>
|
|
193
|
+
<span class="focus-chip" id="focus-chip"><select id="focus-fd" class="fd"><option value="1">1 层</option><option value="2" selected>2 层</option><option value="all">全链路</option></select><button id="focus-exit">清空</button></span>
|
|
194
|
+
</div>
|
|
195
|
+
<div id="time-tools" style="display:none;gap:8px;align-items:center">
|
|
196
|
+
<div class="seg" id="zoom">
|
|
197
|
+
<button data-z="-">-</button>
|
|
198
|
+
<button data-z="reset">100%</button>
|
|
199
|
+
<button data-z="+">+</button>
|
|
200
|
+
</div>
|
|
93
201
|
</div>
|
|
94
|
-
<
|
|
95
|
-
<
|
|
96
|
-
<
|
|
202
|
+
<select class="win-select" id="win" title="按任务开始时间过滤,结束时间不限(两个视图共用)">
|
|
203
|
+
<option value="7">近 7 天</option>
|
|
204
|
+
<option value="14">近 14 天</option>
|
|
205
|
+
<option value="30">近 30 天</option>
|
|
206
|
+
<option value="0">全部时间</option>
|
|
207
|
+
</select>
|
|
208
|
+
<span class="hint" id="hint">按优先级分行(P0 最高);行内从左到右 = 依赖顺序。简约只看任务和依赖,详细展开具体关联。</span>
|
|
209
|
+
</div>
|
|
210
|
+
<div id="view-graph">
|
|
211
|
+
<div class="canvas" id="canvas">
|
|
212
|
+
<div class="canvas-scroll" id="canvas-scroll">
|
|
213
|
+
<svg class="edges" id="edges"></svg>
|
|
214
|
+
</div>
|
|
215
|
+
<div class="lane-rail" id="lane-rail"></div>
|
|
216
|
+
<div class="legend-overlay" id="legend">
|
|
217
|
+
<div class="row"><svg class="sample" viewBox="0 0 38 10"><defs><marker id="lg-dep" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6" fill="#2563eb"/></marker></defs><line x1="1" y1="5" x2="30" y2="5" stroke="#2563eb" stroke-width="1.6" marker-end="url(#lg-dep)"/></svg><span><b>→ depends_on</b> 单向</span></div>
|
|
218
|
+
<div class="row detail-row"><svg class="sample" viewBox="0 0 38 10"><defs><marker id="lg-touch" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6" fill="#7c3aed"/></marker></defs><line x1="1" y1="5" x2="30" y2="5" stroke="#7c3aed" stroke-width="1.6" stroke-dasharray="6 4" marker-end="url(#lg-touch)"/></svg><span><b>→ touches</b> 单向</span></div>
|
|
219
|
+
<div class="row detail-row"><svg class="sample" viewBox="0 0 38 10"><defs><marker id="lg-claim" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6" fill="#0891b2"/></marker></defs><line x1="1" y1="5" x2="30" y2="5" stroke="#0891b2" stroke-width="1.6" stroke-dasharray="2 3" marker-end="url(#lg-claim)"/></svg><span><b>→ claims</b> 单向</span></div>
|
|
220
|
+
<div class="row detail-row"><svg class="sample" viewBox="0 0 38 10"><defs><marker id="lg-collab" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6" fill="#64748b"/></marker></defs><line x1="3" y1="5" x2="32" y2="5" stroke="#64748b" stroke-width="1.6" stroke-dasharray="1 3" marker-start="url(#lg-collab)" marker-end="url(#lg-collab)"/></svg><span><b>↔ collaborates_with</b> 双向(示例)</span></div>
|
|
221
|
+
</div>
|
|
97
222
|
</div>
|
|
98
|
-
<span class="hint">点击节点查看详情 · 边:依赖 / 触碰 / claim</span>
|
|
99
223
|
</div>
|
|
100
|
-
<div
|
|
101
|
-
<
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
224
|
+
<div id="view-time" style="display:none">
|
|
225
|
+
<div class="timeline">
|
|
226
|
+
<div class="scroll-hint">← 左右滚动 →</div>
|
|
227
|
+
<div class="tl-grid">
|
|
228
|
+
<div class="tl-labels">
|
|
229
|
+
<div class="tl-label-head">任务</div>
|
|
230
|
+
<div class="tl-labels-body" id="tl-labels-body"></div>
|
|
231
|
+
</div>
|
|
232
|
+
<div class="tl-scroll" id="tl-scroll">
|
|
233
|
+
<div class="tl-head"><div class="tl-axis" id="tl-axis"></div></div>
|
|
234
|
+
<div class="tl-body" id="tl-body"></div>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
<div class="tl-caption">橙色描边 = 关键路径(数据根迁移 → install.sh 拆分 → launchd);红色虚线 = 今天。</div>
|
|
238
|
+
</div>
|
|
107
239
|
</div>
|
|
108
240
|
</section>
|
|
109
241
|
|
|
@@ -111,110 +243,512 @@
|
|
|
111
243
|
<div class="panel">
|
|
112
244
|
<div class="side-title"><h2>概览</h2><span>live data</span></div>
|
|
113
245
|
<div class="list">
|
|
114
|
-
<div class="item"><span class="k"><i class="dot
|
|
115
|
-
<div class="item"><span class="k"><i class="dot red"></i>阻塞</span><b>
|
|
116
|
-
<div class="item"><span class="k"><i class="dot amber"></i>待评审</span><b>
|
|
117
|
-
<div class="item"><span class="k"><i class="dot
|
|
246
|
+
<div class="item"><span class="k"><i class="dot" style="background:var(--muted)"></i>任务</span><b id="stat-tasks">100</b></div>
|
|
247
|
+
<div class="item"><span class="k"><i class="dot" style="background:var(--red)"></i>阻塞</span><b id="stat-blocked">0</b></div>
|
|
248
|
+
<div class="item"><span class="k"><i class="dot" style="background:var(--amber)"></i>待评审</span><b id="stat-review">0</b></div>
|
|
249
|
+
<div class="item"><span class="k"><i class="dot" style="background:var(--muted)"></i>完成</span><b id="stat-done">0</b></div>
|
|
118
250
|
<div class="item"><span class="k">claims</span><b>3</b></div>
|
|
119
251
|
</div>
|
|
120
252
|
</div>
|
|
121
|
-
|
|
122
253
|
<div class="panel">
|
|
123
254
|
<div class="side-title"><h2>需要人</h2><span>attention</span></div>
|
|
124
255
|
<div class="attention">
|
|
125
|
-
<div class="row"><span class="tag">阻塞</span><div><b>
|
|
126
|
-
<div class="row"><span class="tag amber">claim</span><div><b>src/collab.ts</b><br><span class="k">session-a 持有中</span></div></div>
|
|
127
|
-
<div class="row"><span class="tag amber">评审</span><div><b>graph TUI</b><br><span class="k">等待独立 verifier</span></div></div>
|
|
256
|
+
<div class="row" data-task="t-3"><span class="tag">阻塞</span><div><b>launchd 安装脚本</b><br><span class="k">等待 install.sh 拆分</span><div class="quick"><button class="primary" onclick="event.stopPropagation();openDrawer('t-3','intervene')">介入</button><button onclick="event.stopPropagation();openDrawer('t-3','task')">查看</button></div></div></div>
|
|
257
|
+
<div class="row" data-task="t-4"><span class="tag amber">claim</span><div><b>src/collab.ts</b><br><span class="k">session-a 持有中</span><div class="quick"><button class="primary" onclick="event.stopPropagation();openDrawer('t-4','intervene')">联系持有者</button></div></div></div>
|
|
258
|
+
<div class="row" data-task="t-4"><span class="tag amber">评审</span><div><b>graph TUI</b><br><span class="k">等待独立 verifier</span><div class="quick"><button class="primary" onclick="event.stopPropagation();openDrawer('t-4','intervene')">开始评审</button></div></div></div>
|
|
128
259
|
</div>
|
|
129
260
|
</div>
|
|
130
|
-
|
|
131
|
-
<div class="panel">
|
|
132
|
-
<div class="side-title"><h2>图例</h2><span>legend</span></div>
|
|
133
|
-
<div class="legend">
|
|
134
|
-
<div><i style="background:var(--blue)"></i>depends_on(任务依赖)</div>
|
|
135
|
-
<div><i style="background:var(--violet)"></i>touches(触碰路径)</div>
|
|
136
|
-
<div><i style="background:var(--cyan)"></i>claims(会话声明)</div>
|
|
137
|
-
</div>
|
|
138
|
-
</div>
|
|
139
|
-
|
|
140
|
-
<div class="panel detail" id="detail">
|
|
141
|
-
<div class="side-title"><h2>节点详情</h2><button onclick="document.getElementById('detail').classList.remove('show')" style="border:0;background:transparent;color:var(--muted);cursor:pointer">关闭</button></div>
|
|
142
|
-
<div class="detail" style="display:block" id="detail-body"></div>
|
|
143
|
-
</div>
|
|
144
261
|
</aside>
|
|
145
262
|
</main>
|
|
146
|
-
<
|
|
263
|
+
<div class="backdrop" id="backdrop" onclick="closeDrawer()"></div>
|
|
264
|
+
<aside class="drawer" id="drawer" aria-hidden="true">
|
|
265
|
+
<div class="drawer-head">
|
|
266
|
+
<div><h3 id="drawer-title">任务详情</h3><div class="meta" id="drawer-sub"></div></div>
|
|
267
|
+
<button class="close" onclick="closeDrawer()">×</button>
|
|
268
|
+
</div>
|
|
269
|
+
<div class="drawer-tabs">
|
|
270
|
+
<button data-dtab="task" onclick="setDrawerTab('task')">任务</button>
|
|
271
|
+
<button data-dtab="intervene" onclick="setDrawerTab('intervene')">介入</button>
|
|
272
|
+
</div>
|
|
273
|
+
<div class="drawer-body" id="drawer-body"></div>
|
|
274
|
+
</aside>
|
|
275
|
+
<footer>颜色只表达警告/异常:红=阻塞/P0,橙=待评审/P1;其余状态用中性灰 + 单一蓝色进度。关系图按依赖深度分层,时间线独立展示执行情况。</footer>
|
|
147
276
|
|
|
148
277
|
<script>
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
{id:'
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
function
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
278
|
+
const paths = ['install.sh','src/collab.ts','docs/msg.md','tests/collab.test.ts','kits/dia-msg'];
|
|
279
|
+
const modules = ['collab','graph','claim','release','docs','review','persona','kit'];
|
|
280
|
+
const statusList = ['todo','doing','review','blocked','done','accepted'];
|
|
281
|
+
const tasks = [];
|
|
282
|
+
for (let i=0;i<100;i++){
|
|
283
|
+
const prio = i<6?'P0':i<24?'P1':i<62?'P2':'P3';
|
|
284
|
+
const status = statusList[(i*7)%statusList.length];
|
|
285
|
+
const progress = status==='done'||status==='accepted'?100:status==='todo'?0:(i*13)%95;
|
|
286
|
+
const startD = -15 + ((i*7)%24);
|
|
287
|
+
const endD = Math.min(15, startD + 1 + ((i*3)%8));
|
|
288
|
+
const id = 't-'+String(i+1).padStart(3,'0');
|
|
289
|
+
const back=(i%5)+1, deps = i-back>=0 ? ['t-'+String(i-back).padStart(3,'0')] : [];
|
|
290
|
+
tasks.push({id, label:modules[i%modules.length]+' '+String(i+1).padStart(3,'0'), status, prio, progress, start:startD, end:endD, owner:i%3===0?null:(i%2?'ses_a':'ses_b'), deps});
|
|
291
|
+
}
|
|
292
|
+
const pad=n=>'t-'+String(n+1).padStart(3,'0');
|
|
293
|
+
[[6,1],[7,2],[8,3],[24,7],[25,8],[62,25],[63,26]].forEach(([i,j])=>{ if(tasks[i]&&!tasks[i].deps.includes(pad(j))) tasks[i].deps.push(pad(j)); });
|
|
294
|
+
const taskEdges = [];
|
|
295
|
+
tasks.forEach(t=>t.deps.forEach(d=>taskEdges.push({from:t.id,to:d,kind:'depends_on'})));
|
|
296
|
+
for(let i=0;i<20;i++) taskEdges.push({from:tasks[i*5].id,to:paths[i%paths.length],kind:'touches'});
|
|
297
|
+
for(let i=0;i<12;i++) taskEdges.push({from:i%2?'ses_a':'ses_b',to:paths[i%paths.length],kind:'claims'});
|
|
298
|
+
taskEdges.push({from:'t-001',to:'t-002',kind:'collaborates_with'});
|
|
299
|
+
|
|
300
|
+
const state={view:'graph', layout:'priority', mode:'simple', range:'30d', zoom:28, window:7, q:'', sel:[], fd:2, from:null, to:null};
|
|
301
|
+
const canvas=document.getElementById('canvas'), layer=document.getElementById('canvas-scroll'), rail=document.getElementById('lane-rail'), svg=document.getElementById('edges');
|
|
302
|
+
const esc=s=>String(s??'').replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c]));
|
|
303
|
+
const stIcon={doing:'▶',blocked:'⏸',review:'◐',done:'✓',accepted:'✓',todo:'○'};
|
|
304
|
+
|
|
305
|
+
function focusSet(id,depth){
|
|
306
|
+
const by=new Map(tasks.map(t=>[t.id,t]));
|
|
307
|
+
const max=depth==='all'?Infinity:Math.max(1,Number(depth)||2);
|
|
308
|
+
const out=new Set([id]); let frontier=[id];
|
|
309
|
+
for(let step=0; step<max && frontier.length; step++){
|
|
310
|
+
const next=[];
|
|
311
|
+
frontier.forEach(cur=>{
|
|
312
|
+
const t=by.get(cur); if(!t) return;
|
|
313
|
+
(t.deps||[]).forEach(x=>{ if(by.has(x)&&!out.has(x)){ out.add(x); next.push(x); } });
|
|
314
|
+
tasks.forEach(o=>{ if((o.deps||[]).includes(cur)&&!out.has(o.id)){ out.add(o.id); next.push(o.id); } });
|
|
315
|
+
});
|
|
316
|
+
frontier=next;
|
|
317
|
+
}
|
|
318
|
+
return out;
|
|
319
|
+
}
|
|
320
|
+
function visibleTasks(){
|
|
321
|
+
if(state.sel.length){
|
|
322
|
+
const keep=new Set();
|
|
323
|
+
state.sel.forEach(id=>focusSet(id,state.fd).forEach(x=>keep.add(x)));
|
|
324
|
+
return tasks.filter(t=>keep.has(t.id));
|
|
325
|
+
}
|
|
326
|
+
return tasks.filter(t=>state.window<=0 || t.start>=-state.window);
|
|
327
|
+
}
|
|
328
|
+
function searchMatches(){
|
|
329
|
+
const q=(state.q||'').trim().toLowerCase();
|
|
330
|
+
if(!q) return null;
|
|
331
|
+
return tasks.filter(t=>t.id.toLowerCase().includes(q)||t.label.toLowerCase().includes(q)||String(t.owner||'').toLowerCase().includes(q));
|
|
332
|
+
}
|
|
333
|
+
function syncStats(){
|
|
334
|
+
const list=visibleTasks(), st=id=>document.getElementById(id);
|
|
335
|
+
if(!st('stat-tasks')) return;
|
|
336
|
+
st('stat-tasks').textContent = list.length===tasks.length ? String(tasks.length) : list.length+' / '+tasks.length;
|
|
337
|
+
st('stat-blocked').textContent=String(list.filter(t=>t.status==='blocked').length);
|
|
338
|
+
st('stat-review').textContent=String(list.filter(t=>t.status==='review').length);
|
|
339
|
+
st('stat-done').textContent=String(list.filter(t=>t.status==='done'||t.status==='accepted').length);
|
|
340
|
+
}
|
|
341
|
+
function updateHint(){
|
|
342
|
+
const h=document.getElementById('hint'); if(!h||state.view!=='graph') return;
|
|
343
|
+
const n=visibleTasks().length;
|
|
344
|
+
if(state.sel.length){
|
|
345
|
+
h.textContent='显示 '+n+' 个任务 · '+(state.fd==='all'?'全链路':state.fd+' 层');
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
h.textContent='显示 '+n+' / '+tasks.length+' 个任务 · '+(state.layout==='dep'?'从左到右 = 依赖深度':'按优先级分行(P0 最高)');
|
|
349
|
+
}
|
|
350
|
+
function depths(){
|
|
351
|
+
const list=visibleTasks();
|
|
352
|
+
const by=new Map(list.map(t=>[t.id,t])), memo=new Map();
|
|
353
|
+
const depth=t=>{
|
|
354
|
+
if(!t) return 0;
|
|
355
|
+
if(memo.has(t.id)) return memo.get(t.id);
|
|
356
|
+
memo.set(t.id,0);
|
|
357
|
+
const d=t.deps.length?Math.max(...t.deps.map(x=>{ const dt=by.get(x); return dt?depth(dt):-1; }))+1:0;
|
|
358
|
+
memo.set(t.id,d); return d;
|
|
359
|
+
};
|
|
360
|
+
list.forEach(depth); return memo;
|
|
361
|
+
}
|
|
362
|
+
function levels(){
|
|
363
|
+
return [...new Set(visibleTasks().map(t=>t.prio))].sort((a,b)=>{
|
|
364
|
+
const na=parseInt(String(a).replace(/^P/i,''),10), nb=parseInt(String(b).replace(/^P/i,''),10);
|
|
365
|
+
return (Number.isFinite(na)?na:99)-(Number.isFinite(nb)?nb:99)||String(a).localeCompare(String(b));
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
function rowsFor(){
|
|
369
|
+
const list=visibleTasks(), d=depths();
|
|
370
|
+
const gap=state.mode==='detail'?230:145;
|
|
371
|
+
const h=state.mode==='detail'?205:110;
|
|
372
|
+
return levels().map((prio,i)=>{
|
|
373
|
+
const rowTasks=list.filter(t=>t.prio===prio).sort((a,b)=>(d.get(a.id)-d.get(b.id))||a.label.localeCompare(b.label));
|
|
374
|
+
return {prio,y:36+i*gap,h,tasks:rowTasks};
|
|
375
|
+
}).filter(r=>r.tasks.length);
|
|
376
|
+
}
|
|
377
|
+
let cardMetrics={mid:52,h:58}, clickTimer=null;
|
|
378
|
+
function measureCard(){
|
|
379
|
+
const probe=card(tasks[0],-9999,-9999);
|
|
380
|
+
probe.style.visibility='hidden';
|
|
381
|
+
layer.appendChild(probe);
|
|
382
|
+
const prog=probe.querySelector('.progress');
|
|
383
|
+
const mid=prog?prog.offsetTop+prog.offsetHeight/2+probe.clientTop:44;
|
|
384
|
+
const h=probe.offsetHeight||58;
|
|
385
|
+
probe.remove();
|
|
386
|
+
return {mid,h};
|
|
387
|
+
}
|
|
388
|
+
function bandCenter(r, bandH){ return r.y-12+bandH/2; }
|
|
389
|
+
function cardTop(r){
|
|
390
|
+
const bandH=state.mode==='detail'?230:145;
|
|
391
|
+
const center=bandCenter(r,bandH);
|
|
392
|
+
// 详细:进度条中心线对齐泳道中心;简约:卡片盒整体垂直居中
|
|
393
|
+
return state.mode==='detail'?center-cardMetrics.mid:center-cardMetrics.h/2;
|
|
394
|
+
}
|
|
395
|
+
function edgeOffset(){ return state.mode==='detail'?cardMetrics.mid:cardMetrics.h/2; }
|
|
396
|
+
function pos(){
|
|
397
|
+
const rows=rowsFor(), p=new Map();
|
|
398
|
+
rows.forEach(r=>r.tasks.forEach((t,i)=>p.set(t.id,{x:170+i*230,y:cardTop(r)})));
|
|
399
|
+
return {p,rows};
|
|
400
|
+
}
|
|
401
|
+
function card(t,x,y,showPrio=false){
|
|
402
|
+
const el=document.createElement('div'); el.className='node task '+t.status; el.style.left=x+'px'; el.style.top=y+'px';
|
|
403
|
+
const rel=taskEdges.filter(e=>e.from===t.id||e.to===t.id).map(e=>{
|
|
404
|
+
const outgoing=e.from===t.id, other=outgoing?e.to:e.from;
|
|
405
|
+
const kind=e.kind==='depends_on'?(outgoing?'depends_on':'depended_by'):e.kind;
|
|
406
|
+
return '<div class="meta" style="margin-top:2px">↳ '+esc(kind)+': '+esc(other)+'</div>';
|
|
407
|
+
}).slice(0,4).join('');
|
|
408
|
+
const badge=showPrio?'<span class="prio '+esc(t.prio.toLowerCase())+'">'+esc(t.prio)+'</span>':'';
|
|
409
|
+
el.innerHTML='<b>'+esc(t.label)+badge+'</b>'+
|
|
410
|
+
'<div class="meta">'+esc(t.id)+' · <span class="st '+esc(t.status)+'">'+stIcon[t.status]+' '+esc(t.status)+'</span> · '+t.progress+'%</div>'+
|
|
411
|
+
'<div class="progress"><i class="'+esc(t.status)+'" style="width:'+t.progress+'%"></i></div>'+
|
|
412
|
+
(state.mode==='detail'?'<div class="meta" style="margin-top:5px">'+(t.owner?'@'+esc(t.owner):'unassigned')+' · dep '+t.deps.length+'</div>'+rel:'');
|
|
413
|
+
el.dataset.id=t.id;
|
|
414
|
+
el.onclick=()=>{ clearTimeout(clickTimer); clickTimer=setTimeout(()=>openDrawer(t.id,'task'),180); };
|
|
415
|
+
el.ondblclick=e=>{ e.preventDefault(); clearTimeout(clickTimer); toggleSel(t.id); };
|
|
185
416
|
return el;
|
|
186
417
|
}
|
|
187
|
-
function
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
418
|
+
function edgePath(a,b){
|
|
419
|
+
const o=edgeOffset(), x1=a.x+190,y1=a.y+o,x2=b.x,y2=b.y+o;
|
|
420
|
+
if(x2>=x1) return `M${x1} ${y1} H${x1+Math.max(24,(x2-x1)/2)} V${y2} H${x2}`;
|
|
421
|
+
return `M${x1} ${y1} H${x1-30} V${y2} H${x2}`;
|
|
422
|
+
}
|
|
423
|
+
function drawGraph(){
|
|
424
|
+
canvas.classList.toggle('simple', state.mode==='simple');
|
|
425
|
+
if(state.layout==='dep'){ drawDepGraph(); return; }
|
|
426
|
+
cardMetrics=measureCard();
|
|
427
|
+
const {p,rows}=pos();
|
|
428
|
+
[...layer.querySelectorAll('.node,.pgroup')].forEach(e=>e.remove());
|
|
429
|
+
rail.style.display=''; rail.querySelectorAll('.plane-label').forEach(e=>e.remove());
|
|
430
|
+
const maxX=Math.max(600, ...rows.map(r=>170+(r.tasks.length-1)*230+190+10));
|
|
431
|
+
const maxY=rows.length?Math.max(...rows.map(r=>r.y+r.h))+40:600;
|
|
432
|
+
const bandH=state.mode==='detail'?230:145;
|
|
433
|
+
rows.forEach((r,i)=>{
|
|
434
|
+
const g=document.createElement('div'); g.className='pgroup '+r.prio.toLowerCase();
|
|
435
|
+
g.style.top=(r.y-12)+'px'; g.style.width=maxX+'px'; g.style.height=bandH+'px';
|
|
436
|
+
const alpha=Math.max(0.02, 0.075-i*0.012);
|
|
437
|
+
g.style.background='color-mix(in srgb, var(--blue) '+Math.round(alpha*100)+'%, transparent)';
|
|
438
|
+
layer.appendChild(g);
|
|
439
|
+
const label=document.createElement('div'); label.className='plane-label '+r.prio.toLowerCase();
|
|
440
|
+
label.dataset.y=String(r.y-12+bandH/2); label.style.top=(r.y-12+bandH/2-layer.scrollTop)+'px';
|
|
441
|
+
label.style.color=i===0?'var(--red)':i===1?'var(--amber)':'var(--muted)';
|
|
442
|
+
label.innerHTML=esc(r.prio)+' · '+r.tasks.length+(i===0?'<small>紧急</small>':i===1?'<small>高</small>':'<small>常规</small>');
|
|
443
|
+
rail.appendChild(label);
|
|
444
|
+
r.tasks.forEach((t,j)=>layer.appendChild(card(t,170+j*230,cardTop(r))));
|
|
445
|
+
});
|
|
192
446
|
svg.setAttribute('width',maxX); svg.setAttribute('height',maxY);
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
447
|
+
const defs=`<defs>
|
|
448
|
+
<marker id="ar-dep" markerWidth="7" markerHeight="7" refX="6" refY="3.5" orient="auto"><path d="M0,0 L7,3.5 L0,7" fill="#2563eb"/></marker>
|
|
449
|
+
<marker id="ar-collab" markerWidth="7" markerHeight="7" refX="6" refY="3.5" orient="auto"><path d="M0,0 L7,3.5 L0,7" fill="#64748b"/></marker>
|
|
450
|
+
</defs>`;
|
|
451
|
+
const lines=taskEdges.filter(e=>e.kind==='depends_on'||e.kind==='collaborates_with').map(e=>{
|
|
452
|
+
const a=p.get(e.from), b=p.get(e.to); if(!a||!b) return '';
|
|
453
|
+
const style=e.kind==='collaborates_with'
|
|
454
|
+
? 'stroke="#64748b" stroke-dasharray="1 3" marker-start="url(#ar-collab)" marker-end="url(#ar-collab)"'
|
|
455
|
+
: 'stroke="#2563eb" marker-end="url(#ar-dep)"';
|
|
456
|
+
return `<path d="${edgePath(a,b)}" fill="none" stroke-width="1.6" ${style}/>`;
|
|
457
|
+
}).join('');
|
|
458
|
+
svg.innerHTML=defs+lines;
|
|
459
|
+
document.getElementById('updated').textContent='updated '+new Date().toLocaleTimeString();
|
|
460
|
+
syncStats(); updateHint(); applySearch();
|
|
461
|
+
}
|
|
462
|
+
let drawerTask=null, drawerTab='task', ivChannel='tui';
|
|
463
|
+
function relations(t){
|
|
464
|
+
return taskEdges.filter(e=>e.from===t.id||e.to===t.id).map(e=>{
|
|
465
|
+
const outgoing=e.from===t.id, other=outgoing?e.to:e.from;
|
|
466
|
+
const kind=e.kind==='depends_on'?(outgoing?'depends_on':'depended_by'):e.kind;
|
|
467
|
+
return '<div class="item"><span class="k">'+esc(kind)+'</span><span>'+esc(other)+'</span></div>';
|
|
468
|
+
}).join('') || '<p class="k">无边</p>';
|
|
469
|
+
}
|
|
470
|
+
function taskHtml(t){
|
|
471
|
+
return '<div class="kv"><div class="k">status</div><div>'+esc(t.status)+'</div>'+
|
|
472
|
+
'<div class="k">priority</div><div>'+esc(t.prio)+'</div>'+
|
|
473
|
+
'<div class="k">owner</div><div>'+esc(t.owner||'-')+'</div>'+
|
|
474
|
+
'<div class="k">progress</div><div>'+t.progress+'%</div>'+
|
|
475
|
+
'<div class="k">deps</div><div>'+esc(t.deps.join(', ')||'-')+'</div></div>'+
|
|
476
|
+
'<h4>关联</h4><div class="rel-list">'+relations(t)+'</div>';
|
|
477
|
+
}
|
|
478
|
+
function targets(ch){
|
|
479
|
+
if(ch==='dingtalk') return [['group','乐语助人前端组 · 群聊'],['dm','与 session-a 私聊']];
|
|
480
|
+
if(ch==='msg') return [['ses_a','session-a'],['ses_b','session-b']];
|
|
481
|
+
return [['ses_a','session-a · opencode TUI (online)'],['ses_b','session-b · dsh TUI (online)']];
|
|
482
|
+
}
|
|
483
|
+
function interveneHtml(t){
|
|
484
|
+
const chs=[['tui','TUI 会话'],['dingtalk','钉钉消息链'],['msg','dia msg']];
|
|
485
|
+
const opts=targets(ivChannel).map(([v,l])=>'<option value="'+v+'">'+esc(l)+'</option>').join('');
|
|
486
|
+
return '<div class="iv-seg">'+chs.map(([v,l])=>'<button class="'+(ivChannel===v?'on':'')+'" onclick="setChannel(\''+v+'\')">'+l+'</button>').join('')+'</div>'+
|
|
487
|
+
'<div class="iv-row"><label>目标会话 / 消息链</label><select id="iv-target">'+opts+'</select></div>'+
|
|
488
|
+
'<div class="iv-row"><label>介入消息</label><textarea id="iv-text" placeholder="例如:这个任务卡在什么点?需要我做什么?"></textarea></div>'+
|
|
489
|
+
'<div><button class="btn primary" onclick="sendIntervention()">发送介入</button><span class="iv-status" id="iv-status"></span></div>'+
|
|
490
|
+
'<p class="k" style="margin-top:12px">设计稿:发送仅演示。真实接入分别走 TUI 注入 / 钉钉消息链 / dia msg;同一任务可切换通道。</p>';
|
|
491
|
+
}
|
|
492
|
+
function openDrawer(id, tab){
|
|
493
|
+
drawerTask=tasks.find(t=>t.id===id); if(!drawerTask) return;
|
|
494
|
+
drawerTab=tab||'task';
|
|
495
|
+
document.getElementById('drawer-title').textContent='任务详情';
|
|
496
|
+
document.getElementById('drawer-sub').textContent=drawerTask.label+' · '+drawerTask.id+' · '+drawerTask.prio+' · '+drawerTask.status;
|
|
497
|
+
document.getElementById('drawer').classList.add('show');
|
|
498
|
+
document.getElementById('drawer').setAttribute('aria-hidden','false');
|
|
499
|
+
document.getElementById('backdrop').classList.add('show');
|
|
500
|
+
setDrawerTab(drawerTab);
|
|
501
|
+
}
|
|
502
|
+
function setDrawerTab(tab){
|
|
503
|
+
drawerTab=tab;
|
|
504
|
+
document.querySelectorAll('[data-dtab]').forEach(b=>b.classList.toggle('on',b.dataset.dtab===tab));
|
|
505
|
+
const body=document.getElementById('drawer-body');
|
|
506
|
+
body.innerHTML=tab==='intervene'?interveneHtml(drawerTask):taskHtml(drawerTask);
|
|
507
|
+
}
|
|
508
|
+
function setChannel(ch){ ivChannel=ch; setDrawerTab('intervene'); }
|
|
509
|
+
function sendIntervention(){
|
|
510
|
+
const text=(document.getElementById('iv-text')||{}).value||'';
|
|
511
|
+
if(!text.trim()){ document.getElementById('iv-status').textContent='请先输入内容'; return; }
|
|
512
|
+
document.getElementById('iv-status').textContent='已排队(设计稿)';
|
|
513
|
+
}
|
|
514
|
+
function closeDrawer(){
|
|
515
|
+
document.getElementById('drawer').classList.remove('show');
|
|
516
|
+
document.getElementById('drawer').setAttribute('aria-hidden','true');
|
|
517
|
+
document.getElementById('backdrop').classList.remove('show');
|
|
518
|
+
}
|
|
519
|
+
function dayDate(d){
|
|
520
|
+
const base=new Date('2026-11-06T00:00:00');
|
|
521
|
+
const dt=new Date(base.getTime()+d*86400000);
|
|
522
|
+
return ('0'+(dt.getMonth()+1)).slice(-2)+'-'+('0'+dt.getDate()).slice(-2);
|
|
523
|
+
}
|
|
524
|
+
function isoDate(d){ const base=new Date('2026-11-06T00:00:00'); const dt=new Date(base.getTime()+d*86400000); return dt.toISOString().slice(0,10); }
|
|
525
|
+
function timelineWindow(){
|
|
526
|
+
if(state.from!==null&&state.to!==null) return {from:state.from,to:state.to};
|
|
527
|
+
const list=visibleTasks();
|
|
528
|
+
const w=state.window>0?state.window:14;
|
|
529
|
+
const from=-w;
|
|
530
|
+
const maxEnd=list.length?Math.max(...list.map(t=>t.end)):w;
|
|
531
|
+
return {from,to:Math.max(0,maxEnd)+1};
|
|
532
|
+
}
|
|
533
|
+
function drawTimeline(){
|
|
534
|
+
const win=timelineWindow(), span=Math.max(1,win.to-win.from);
|
|
535
|
+
const x=v=>((v-win.from)/span)*100;
|
|
536
|
+
const chartW=Math.max(640, span*state.zoom);
|
|
537
|
+
document.getElementById('view-time').style.setProperty('--tlw', chartW+'px');
|
|
538
|
+
const axis=document.getElementById('tl-axis'); axis.innerHTML='';
|
|
539
|
+
const step=span<=10?1:span<=18?2:3;
|
|
540
|
+
for(let d=win.from;d<=win.to;d+=step){
|
|
541
|
+
const el=document.createElement('span'); el.style.left=x(d)+'%';
|
|
542
|
+
el.innerHTML=dayDate(d)+'<small>'+(d===0?'今天':(d>0?'+'+d+'d':d+'d'))+'</small>';
|
|
543
|
+
if(d===win.from) el.style.transform='translateX(0)'; else if(d+step>win.to) el.style.transform='translateX(-100%)';
|
|
544
|
+
if(d===0)el.style.color='var(--red)'; axis.appendChild(el);
|
|
545
|
+
}
|
|
546
|
+
const body=document.getElementById('tl-body'); body.innerHTML=''; const labelsBody=document.getElementById('tl-labels-body'); labelsBody.innerHTML=''; const critical=new Set(['t-1','t-2','t-3']);
|
|
547
|
+
visibleTasks().slice().sort((a,b)=>a.start-b.start).forEach(t=>{
|
|
548
|
+
const row=document.createElement('div'); row.className='tl-row';
|
|
549
|
+
row.innerHTML='<div class="tl-track"></div>';
|
|
550
|
+
const label=document.createElement('div'); label.className='tl-label-row'; label.innerHTML='<b>'+esc(t.label)+'</b><span>'+esc(t.id)+' · '+esc(t.prio)+' · '+esc(t.status)+'</span>'; labelsBody.appendChild(label);
|
|
551
|
+
const track=row.querySelector('.tl-track');
|
|
552
|
+
const bar=document.createElement('div'); bar.className='tl-bar '+t.status+(critical.has(t.id)?' critical':'');
|
|
553
|
+
bar.style.left=x(t.start)+'%'; bar.style.width=Math.max(1.5,x(t.end)-x(t.start))+'%';
|
|
554
|
+
bar.title=dayDate(t.start)+' → '+dayDate(t.end)+' ('+t.progress+'%)';
|
|
555
|
+
bar.innerHTML='<div class="fill" style="width:'+t.progress+'%"></div>'; bar.onclick=()=>openDrawer(t.id,'task'); track.appendChild(bar);
|
|
556
|
+
if(win.from<=0&&win.to>=0){ const line=document.createElement('div'); line.className='tl-today'; line.style.left=x(0)+'%'; line.style.top='0'; line.style.bottom='0'; line.textContent=''; track.appendChild(line); }
|
|
557
|
+
body.appendChild(row);
|
|
558
|
+
});
|
|
559
|
+
syncStats();
|
|
560
|
+
}
|
|
561
|
+
function drawDepGraph(){
|
|
562
|
+
cardMetrics=measureCard();
|
|
563
|
+
const list=visibleTasks(), d=depths(), groups=new Map();
|
|
564
|
+
list.forEach(t=>{ const k=d.get(t.id)||0; groups.set(k,[...(groups.get(k)||[]),t]); });
|
|
565
|
+
const p=new Map(); const rows=[];
|
|
566
|
+
const gap=14;
|
|
567
|
+
[...groups.keys()].sort((a,b)=>a-b).forEach((depth,ci)=>{
|
|
568
|
+
const all=groups.get(depth).sort((a,b)=>a.prio.localeCompare(b.prio)||a.label.localeCompare(b.label));
|
|
569
|
+
rows.push({depth,all,x:30+ci*230});
|
|
570
|
+
});
|
|
571
|
+
[...layer.querySelectorAll('.node,.pgroup')].forEach(e=>e.remove());
|
|
572
|
+
rail.innerHTML=''; rail.style.display='none';
|
|
573
|
+
let maxY=0;
|
|
574
|
+
// 先渲染卡片,再按实测高度逐列堆叠,避免详细模式卡片相互重叠
|
|
575
|
+
rows.forEach(r=>{
|
|
576
|
+
let y=60;
|
|
577
|
+
r.all.forEach(t=>{
|
|
578
|
+
const el=card(t,r.x,y,true);
|
|
579
|
+
el.style.top=y+'px';
|
|
580
|
+
layer.appendChild(el);
|
|
581
|
+
p.set(t.id,{x:r.x,y});
|
|
582
|
+
y+=el.offsetHeight+gap;
|
|
583
|
+
});
|
|
584
|
+
maxY=Math.max(maxY,y);
|
|
585
|
+
});
|
|
586
|
+
maxY=Math.max(600,maxY);
|
|
587
|
+
const maxX=Math.max(600,...rows.map(r=>r.x+200+10));
|
|
588
|
+
svg.setAttribute('width',maxX); svg.setAttribute('height',maxY+40);
|
|
589
|
+
const defs=`<defs>
|
|
590
|
+
<marker id="ar-dep" markerWidth="7" markerHeight="7" refX="6" refY="3.5" orient="auto"><path d="M0,0 L7,3.5 L0,7" fill="#2563eb"/></marker>
|
|
591
|
+
<marker id="ar-collab" markerWidth="7" markerHeight="7" refX="6" refY="3.5" orient="auto"><path d="M0,0 L7,3.5 L0,7" fill="#64748b"/></marker>
|
|
592
|
+
</defs>`;
|
|
593
|
+
const lines=taskEdges.filter(e=>e.kind==='depends_on'||e.kind==='collaborates_with').map(e=>{
|
|
594
|
+
const a=p.get(e.from), b=p.get(e.to); if(!a||!b) return '';
|
|
595
|
+
const style=e.kind==='collaborates_with'?'stroke="#64748b" stroke-dasharray="1 3" marker-start="url(#ar-collab)" marker-end="url(#ar-collab)"':'stroke="#2563eb" marker-end="url(#ar-dep)"';
|
|
596
|
+
return `<path d="${edgePath(a,b)}" fill="none" stroke-width="1.6" ${style}/>`;
|
|
196
597
|
}).join('');
|
|
598
|
+
svg.innerHTML=defs+lines;
|
|
599
|
+
document.getElementById('updated').textContent='updated '+new Date().toLocaleTimeString();
|
|
600
|
+
syncStats(); updateHint(); applySearch();
|
|
601
|
+
}
|
|
602
|
+
function syncLaneLabels(){
|
|
603
|
+
rail.querySelectorAll('.plane-label').forEach(el=>{
|
|
604
|
+
el.style.top=(Number(el.dataset.y||0)-layer.scrollTop)+'px';
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
function applySearch(){
|
|
608
|
+
const ms=searchMatches();
|
|
609
|
+
layer.querySelectorAll('.node.task').forEach(el=>{
|
|
610
|
+
el.classList.remove('hit','dim');
|
|
611
|
+
if(!ms) return;
|
|
612
|
+
if(ms.some(t=>t.id===el.dataset.id)) el.classList.add('hit'); else el.classList.add('dim');
|
|
613
|
+
});
|
|
614
|
+
const box=document.getElementById('q-list');
|
|
615
|
+
if(box && box.classList.contains('open')) renderSearchList(ms);
|
|
616
|
+
}
|
|
617
|
+
function renderSearchList(ms){
|
|
618
|
+
const box=document.getElementById('q-list'); if(!box) return;
|
|
619
|
+
const q=(state.q||'').trim();
|
|
620
|
+
const browse=!q;
|
|
621
|
+
const pool=browse?tasks.filter(t=>state.window<=0||t.start>=-state.window).slice().sort((a,b)=>a.prio.localeCompare(b.prio)||a.start-b.start):ms;
|
|
622
|
+
if(!pool||!pool.length){
|
|
623
|
+
box.innerHTML='<div class="q-empty">'+(browse?'当前时间窗内没有任务':'没有匹配的任务')+'</div>';
|
|
624
|
+
box.classList.add('open'); return;
|
|
625
|
+
}
|
|
626
|
+
const head=browse?('<div class="q-head">'+pool.length+' 个任务 · ↑↓ 选择,Enter 加入</div>')
|
|
627
|
+
:('<div class="q-head">匹配 '+pool.length+' 个 · ↑↓ 选择,Enter 加入</div>');
|
|
628
|
+
box.innerHTML=head+pool.slice(0,20).map(t=>{
|
|
629
|
+
const on=state.sel.includes(t.id);
|
|
630
|
+
return '<div class="q-item'+(on?' sel':'')+'" data-id="'+esc(t.id)+'"><span>'+esc(t.label)+'<i class="q-prio">'+esc(t.prio)+'</i></span><span class="k">'+(on?'✓ ':'')+esc(t.id)+'</span></div>';
|
|
631
|
+
}).join('');
|
|
632
|
+
box.classList.add('open');
|
|
633
|
+
box.querySelectorAll('.q-item').forEach(el=>el.onclick=e=>{ e.stopPropagation(); toggleSel(el.dataset.id); });
|
|
634
|
+
}
|
|
635
|
+
function searchListItems(){
|
|
636
|
+
const box=document.getElementById('q-list');
|
|
637
|
+
return box?[...box.querySelectorAll('.q-item')]:[];
|
|
638
|
+
}
|
|
639
|
+
function moveSearchCursor(delta){
|
|
640
|
+
let items=searchListItems();
|
|
641
|
+
if(!items.length){ renderSearchList(searchMatches()); items=searchListItems(); if(!items.length) return; }
|
|
642
|
+
let idx=items.findIndex(el=>el.classList.contains('active'));
|
|
643
|
+
idx=idx<0?0:(idx+delta+items.length)%items.length;
|
|
644
|
+
items.forEach((el,i)=>el.classList.toggle('active',i===idx));
|
|
645
|
+
items[idx].scrollIntoView({block:'nearest'});
|
|
646
|
+
}
|
|
647
|
+
function toggleSel(id){
|
|
648
|
+
const i=state.sel.indexOf(id);
|
|
649
|
+
if(i>=0) state.sel.splice(i,1); else state.sel.push(id);
|
|
650
|
+
state.fd=state.fd||2; state.q='';
|
|
651
|
+
const qi=document.getElementById('q'); if(qi) qi.value='';
|
|
652
|
+
go(state.view,state.mode,state.range,state.zoom);
|
|
653
|
+
}
|
|
654
|
+
function renderTokens(){
|
|
655
|
+
const box=document.getElementById('tokens'); if(!box) return;
|
|
656
|
+
box.innerHTML=state.sel.map(id=>'<span class="token">'+esc(id)+'<button data-id="'+esc(id)+'" title="移除">×</button></span>').join('');
|
|
657
|
+
box.querySelectorAll('button').forEach(b=>b.onclick=e=>{ e.stopPropagation(); toggleSel(b.dataset.id); });
|
|
658
|
+
}
|
|
659
|
+
function readRoute(){
|
|
660
|
+
const raw=location.hash.replace(/^#\/?/,''); const [path,qs]=raw.split('?');
|
|
661
|
+
const params=new URLSearchParams(qs||location.search);
|
|
662
|
+
const range=(params.get('range')||'30d');
|
|
663
|
+
const zoom=Math.min(84,Math.max(14,parseInt(params.get('zoom')||'28',10)||28));
|
|
664
|
+
const sx=Number(params.get('sx')||0);
|
|
665
|
+
const fromRaw=params.get('from'), toRaw=params.get('to');
|
|
666
|
+
const toOff=v=>v?Math.round((new Date(v+'T00:00:00').getTime()-new Date('2026-11-06T00:00:00').getTime())/86400000):null;
|
|
667
|
+
const winRaw=params.get('w');
|
|
668
|
+
const rangeRaw=params.get('range');
|
|
669
|
+
const win=winRaw!==null?Math.max(0,parseInt(winRaw,10)||0):(rangeRaw?Math.max(0,parseInt(rangeRaw,10)||0):7);
|
|
670
|
+
const fdRaw=params.get('fd');
|
|
671
|
+
const fd=fdRaw==='all'?'all':Math.min(9,Math.max(1,parseInt(fdRaw||'2',10)||2));
|
|
672
|
+
return {view:(path==='timeline'||params.get('view')==='timeline')?'time':'graph', layout:params.get('layout')==='dep'?'dep':'priority', mode:params.get('mode')==='detail'?'detail':'simple', range, zoom, sx, window:win, q:params.get('q')||'', sel:(params.get('sel')||params.get('focus')||'').split(',').map(x=>x.trim()).filter(x=>x&&tasks.some(t=>t.id===x)), fd, from:toOff(fromRaw), to:toOff(toRaw), task:params.get('task'), tab:params.get('tab')==='intervene'?'intervene':'task'};
|
|
673
|
+
}
|
|
674
|
+
function routeString(view,mode,range,zoom,from,to){
|
|
675
|
+
const p=view==='time'?'timeline':'graph'; const qs=new URLSearchParams();
|
|
676
|
+
if(view==='graph'&&mode==='detail') qs.set('mode','detail');
|
|
677
|
+
if(view==='graph'&&state.layout==='dep') qs.set('layout','dep');
|
|
678
|
+
if(view==='graph'){
|
|
679
|
+
if(state.window!==7) qs.set('w',String(state.window));
|
|
680
|
+
if(state.q&&state.q.trim()) qs.set('q',state.q.trim());
|
|
681
|
+
if(state.sel.length){ qs.set('sel',state.sel.join(',')); if(state.fd!==2) qs.set('fd',String(state.fd)); }
|
|
682
|
+
}
|
|
683
|
+
if(view==='time'){
|
|
684
|
+
if(from!==null&&to!==null){ qs.set('from',isoDate(from)); qs.set('to',isoDate(to)); }
|
|
685
|
+
if(state.window!==7) qs.set('w',String(state.window));
|
|
686
|
+
if(zoom&&zoom!==28) qs.set('zoom',String(zoom));
|
|
687
|
+
}
|
|
688
|
+
return '#/'+p+(qs.toString()?'?'+qs.toString():'');
|
|
689
|
+
}
|
|
690
|
+
function applyRoute(){
|
|
691
|
+
const r=readRoute(); state.view=r.view; state.layout=r.layout; state.mode=r.mode; state.range=r.range; state.zoom=r.zoom; state.sx=r.sx||0; state.window=r.window; state.q=r.q; state.sel=r.sel; state.fd=r.fd; state.from=r.from; state.to=r.to;
|
|
692
|
+
document.querySelectorAll('#tabs button').forEach(x=>x.classList.toggle('on',x.dataset.v===r.view));
|
|
693
|
+
document.querySelectorAll('#mode button').forEach(x=>x.classList.toggle('on',x.dataset.m===r.mode));
|
|
694
|
+
document.querySelectorAll('#layout button').forEach(x=>x.classList.toggle('on',x.dataset.l===r.layout));
|
|
695
|
+
document.getElementById('view-graph').style.display=r.view==='graph'?'':'none';
|
|
696
|
+
document.getElementById('view-time').style.display=r.view==='time'?'':'none';
|
|
697
|
+
document.getElementById('graph-tools').style.display=r.view==='graph'?'flex':'none';
|
|
698
|
+
document.getElementById('time-tools').style.display=r.view==='time'?'flex':'none';
|
|
699
|
+
const zbtn=document.querySelector('#zoom button[data-z="reset"]'); if(zbtn) zbtn.textContent=Math.round(r.zoom/28*100)+'%';
|
|
700
|
+
const dec=document.querySelector('#zoom button[data-z="-"]'), inc=document.querySelector('#zoom button[data-z="+"]'); if(dec) dec.disabled=r.zoom<=14; if(inc) inc.disabled=r.zoom>=84;
|
|
701
|
+
if(r.view!=='graph') document.getElementById('hint').textContent='时间线:左右滚动查看;-/+缩放;橙框 = 关键路径';
|
|
702
|
+
const winSel=document.getElementById('win'); if(winSel) winSel.value=String(r.window);
|
|
703
|
+
const qIn=document.getElementById('q'); if(qIn && qIn.value!==r.q) qIn.value=r.q;
|
|
704
|
+
const chip=document.getElementById('focus-chip');
|
|
705
|
+
if(chip) chip.classList.toggle('on',r.sel.length>0);
|
|
706
|
+
if(chip) chip.title='按依赖邻域聚焦;清空即回全量';
|
|
707
|
+
const fdEl=document.getElementById('focus-fd'); if(fdEl) fdEl.value=String(r.fd);
|
|
708
|
+
renderTokens();
|
|
709
|
+
const rp=document.getElementById('route'); if(rp) rp.textContent=location.hash||routeString(r.view,r.mode,r.range,state.zoom,state.from,state.to);
|
|
710
|
+
document.title='dia graph · '+(r.view==='time'?'时间线':'关系图');
|
|
711
|
+
if(r.view==='time'){ drawTimeline(); const sc=document.getElementById('tl-scroll'), lb=document.getElementById('tl-labels-body'); sc.scrollTop=0; lb.style.transform='translateY(0px)'; if(state.sx) sc.scrollLeft=state.sx; } else drawGraph();
|
|
712
|
+
if(r.task) openDrawer(r.task,r.tab);
|
|
713
|
+
syncLaneLabels();
|
|
714
|
+
}
|
|
715
|
+
function go(view,mode,range,zoom){ const h=routeString(view,mode,range||state.range,zoom||state.zoom,state.from,state.to); if(location.hash===h) applyRoute(); else location.hash=h; }
|
|
716
|
+
document.querySelectorAll('#tabs button').forEach(b=>b.onclick=()=>go(b.dataset.v==='time'?'time':'graph',state.mode,state.range));
|
|
717
|
+
document.querySelectorAll('#layout button').forEach(b=>b.onclick=()=>{state.layout=b.dataset.l;go(state.view,state.mode,state.range,state.zoom);});
|
|
718
|
+
const winSelEl=document.getElementById('win');
|
|
719
|
+
if(winSelEl) winSelEl.onchange=()=>{ state.window=Number(winSelEl.value); go(state.view,state.mode,state.range,state.zoom); };
|
|
720
|
+
const qInput=document.getElementById('q');
|
|
721
|
+
if(qInput){
|
|
722
|
+
qInput.oninput=()=>{ state.q=qInput.value; applySearch(); renderSearchList(searchMatches()); };
|
|
723
|
+
qInput.onkeydown=e=>{
|
|
724
|
+
if(e.key==='ArrowDown'){ e.preventDefault(); moveSearchCursor(1); }
|
|
725
|
+
else if(e.key==='ArrowUp'){ e.preventDefault(); moveSearchCursor(-1); }
|
|
726
|
+
else if(e.key==='Enter'){
|
|
727
|
+
const items=searchListItems();
|
|
728
|
+
const active=items.find(el=>el.classList.contains('active'))||items[0];
|
|
729
|
+
if(active) toggleSel(active.dataset.id);
|
|
730
|
+
} else if(e.key==='Escape'){ state.q=''; qInput.value=''; applySearch(); qInput.blur(); }
|
|
731
|
+
else if(e.key==='Backspace' && !qInput.value && state.sel.length){ e.preventDefault(); toggleSel(state.sel[state.sel.length-1]); }
|
|
732
|
+
};
|
|
733
|
+
qInput.onfocus=()=>{ renderSearchList(searchMatches()); };
|
|
734
|
+
qInput.onclick=()=>{ renderSearchList(searchMatches()); };
|
|
735
|
+
const sbox=document.getElementById('search-box');
|
|
736
|
+
if(sbox) sbox.onclick=e=>{ if(e.target.closest('.q-item')||e.target.closest('.token')) return; qInput.focus(); };
|
|
737
|
+
qInput.onblur=()=>{ setTimeout(()=>{ const box=document.getElementById('q-list'); if(box && !document.getElementById('search-box').contains(document.activeElement)) box.classList.remove('open'); },160); };
|
|
197
738
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
document.querySelectorAll('
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
});
|
|
212
|
-
document.querySelectorAll('#density button').forEach(b=>b.onclick=()=>{
|
|
213
|
-
document.querySelectorAll('#density button').forEach(x=>x.classList.remove('on')); b.classList.add('on');
|
|
214
|
-
const compact=b.dataset.d==='compact';
|
|
215
|
-
document.querySelectorAll('.node').forEach(el=>{el.style.padding=compact?'5px 8px':'9px 11px';el.style.width=compact?'180px':'206px';});
|
|
216
|
-
});
|
|
217
|
-
draw();
|
|
739
|
+
const fdSel=document.getElementById('focus-fd');
|
|
740
|
+
if(fdSel) fdSel.onchange=()=>{ state.fd=fdSel.value==='all'?'all':Number(fdSel.value); go(state.view,state.mode,state.range,state.zoom); };
|
|
741
|
+
const fxBtn=document.getElementById('focus-exit');
|
|
742
|
+
if(fxBtn) fxBtn.onclick=()=>{ state.sel=[]; go(state.view,state.mode,state.range,state.zoom); };
|
|
743
|
+
document.querySelectorAll('#mode button').forEach(b=>b.onclick=()=>go(state.view,b.dataset.m,state.range,state.zoom));
|
|
744
|
+
document.querySelectorAll('#zoom button').forEach(b=>b.onclick=()=>{ const z=b.dataset.z==='+'?Math.min(84,state.zoom+8):b.dataset.z==='-'?Math.max(14,state.zoom-8):28; go('time',state.mode,state.range,z); });
|
|
745
|
+
layer.addEventListener('scroll',syncLaneLabels,{passive:true});
|
|
746
|
+
const tlScroller=document.getElementById('tl-scroll');
|
|
747
|
+
tlScroller.addEventListener('scroll',()=>{ const lb=document.getElementById('tl-labels-body'); if(lb) lb.style.transform='translateY(-'+tlScroller.scrollTop+'px)'; },{passive:true});
|
|
748
|
+
document.querySelectorAll('.attention .row[data-task]').forEach(r=>r.onclick=()=>openDrawer(r.dataset.task,'intervene'));
|
|
749
|
+
window.addEventListener('hashchange',applyRoute);
|
|
750
|
+
applyRoute();
|
|
751
|
+
if(document.fonts&&document.fonts.ready){ document.fonts.ready.then(()=>{ if(state.view==='graph'){ if(state.layout==='dep') drawDepGraph(); else drawGraph(); } }); }
|
|
218
752
|
</script>
|
|
219
753
|
</body>
|
|
220
754
|
</html>
|