File size: 15,927 Bytes
3613e77
 
 
 
 
 
 
 
 
 
 
0c571a4
 
3613e77
 
 
0c571a4
3613e77
cdff6a6
 
0c571a4
 
cdff6a6
0c571a4
cdff6a6
0c571a4
cdff6a6
0c571a4
 
 
 
 
cdff6a6
3613e77
cdff6a6
 
 
0c571a4
 
 
 
 
 
 
3613e77
 
 
 
 
 
 
0c571a4
3613e77
0c571a4
 
 
3613e77
 
cdff6a6
3613e77
cdff6a6
 
 
 
0c571a4
3613e77
cdff6a6
 
 
 
0c571a4
3613e77
cdff6a6
3613e77
cdff6a6
0c571a4
 
3613e77
 
cdff6a6
0c571a4
cdff6a6
 
 
3613e77
cdff6a6
 
 
 
 
 
 
0c571a4
cdff6a6
 
 
 
0c571a4
cdff6a6
0c571a4
 
 
cdff6a6
 
 
 
0c571a4
 
eeeb56e
 
0c571a4
eeeb56e
0c571a4
 
 
 
 
eeeb56e
 
cdff6a6
 
 
 
0c571a4
cdff6a6
 
 
0c571a4
 
 
cdff6a6
 
eeeb56e
cdff6a6
 
0c571a4
cdff6a6
0c571a4
 
 
 
 
 
cdff6a6
 
0c571a4
cdff6a6
0c571a4
 
cdff6a6
 
 
 
0c571a4
3613e77
 
cdff6a6
3613e77
 
 
 
 
 
 
 
 
 
 
 
 
 
0c571a4
3613e77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0c571a4
 
 
3613e77
0c571a4
3613e77
 
 
0c571a4
 
 
3613e77
 
 
 
 
 
 
 
 
 
 
 
0c571a4
 
 
 
 
3613e77
 
 
 
 
 
 
 
 
 
0c571a4
3613e77
 
 
 
 
 
 
0c571a4
3613e77
 
 
 
 
 
 
 
0c571a4
3613e77
 
 
 
 
 
 
 
 
0c571a4
3613e77
 
0c571a4
 
 
 
3613e77
 
0c571a4
3613e77
 
 
 
 
 
 
 
 
 
 
 
0c571a4
3613e77
 
 
0c571a4
3613e77
 
 
 
 
0c571a4
 
3613e77
 
 
0c571a4
 
 
 
 
 
 
 
3613e77
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>SetVL Node Graph Viewer</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="topographic-grid" aria-hidden="true"></div>

<!-- 界面文字的中英两份都在 app.js 的 I18N 表里。标了 data-i18n* 的地方由
     applyLang() 替换,这里写的中文只是脚本跑起来之前的初始值。 -->
<header class="topbar">
  <div class="brand">
    <div class="brand-mark" aria-hidden="true">SG</div>
    <h1>Node Graph Viewer<em data-i18n="brand.readonly">SetVL · 只读</em></h1>
  </div>

  <!-- 全库读数:一行内用竖线断开,不再单独占一条 progress-strip -->
  <div class="readout" role="group" aria-label="全库统计" data-i18n-aria-label="stats.aria">
    <b id="statScenes">—</b><span data-i18n="stats.scenes">场景</span>
    <i aria-hidden="true"></i>
    <b id="statItems">—</b><span data-i18n="stats.nodes">节点</span>
    <i aria-hidden="true"></i>
    <b id="statQueue">—</b><span data-i18n="stats.edges">边</span>
    <i aria-hidden="true"></i>
    <span class="readout-origins" id="originBreakdown" title="边按 §1-§4 分类" data-i18n-title="stats.byOrigin">
      <em class="o-corridor" data-i18n="origin.corridor"><b id="countCorridor">—</b>同走廊</em>
      <em class="o-road" data-i18n="origin.road_route"><b id="countRoad">—</b>路网可达</em>
      <em class="o-short" data-i18n="origin.short_link"><b id="countShort">—</b>短直连</em>
      <em class="o-merge" data-i18n="origin.component_merge"><b id="countMerge">—</b>分量合并</em>
    </span>
  </div>

  <div class="origin-meter" id="originMeter" aria-hidden="true"><span id="progressBar"></span></div>
  <span class="readout-label" id="progressLabel">—</span>
  <button class="icon-button" id="helpButton" title="帮助与快捷键" aria-label="帮助与快捷键"
          data-i18n-title="help.title" data-i18n-aria-label="help.title">?</button>
  <!-- 语言切换。两个按钮各用自己的语言写,不随界面翻译——不懂另一种语言的人也认得出 -->
  <div class="lang-switch" role="group" aria-label="语言 / Language">
    <button type="button" id="langZh" lang="zh-CN">中文</button>
    <button type="button" id="langEn" lang="en">EN</button>
  </div>
</header>

<main class="app-shell">
  <!-- 01 队列 --------------------------------------------------------->
  <aside class="queue-panel">
    <div class="panel-heading">
      <p class="section-index">01 / BROWSE</p>
      <h2 id="panelTitle" data-i18n="panel.scenes">场景浏览</h2>
    </div>
    <div class="mode-switch" role="group" aria-label="列表模式" data-i18n-aria-label="mode.aria">
      <button type="button" id="modeScenes" class="active" data-i18n="mode.scenes">全部场景</button>
      <button type="button" id="modeQueue" data-i18n="mode.queue">带信号的</button>
    </div>
    <div class="filter-stack">
      <!-- 一级:搜索。常驻,最宽,视觉重量最大。 -->
      <div class="search-wrap">
        <svg class="search-icon" viewBox="0 0 16 16" aria-hidden="true">
          <circle cx="7" cy="7" r="4.5" fill="none" stroke="currentColor" stroke-width="1.4"/>
          <path d="M10.4 10.4 L14 14" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
        </svg>
        <input id="searchInput" type="search" placeholder="scene_id 片段、城市…" data-i18n-placeholder="search.scenes">
      </div>

      <!-- 二级:最常用的两个,压成一行紧凑控件 -->
      <div class="filter-row">
        <label class="ctl"><span>City</span>
          <select id="cityFilter"><option value="" data-i18n="opt.all">全部</option></select>
        </label>
        <label class="ctl"><span>Sort</span>
          <select id="orderFilter">
            <option value="scene_id">ID</option>
            <option value="nodes_desc" data-i18n="order.nodesDesc">节点多→少</option>
            <option value="nodes_asc" data-i18n="order.nodesAsc">节点少→多</option>
          </select>
        </label>
        <button type="button" class="filter-toggle" id="filterToggle"
                aria-expanded="false" aria-controls="filterDrawer" data-i18n="filter.toggle">
          筛选<b class="filter-count" id="filterCount" hidden>0</b>
          <svg viewBox="0 0 10 6" aria-hidden="true"><path d="M1 1l4 4 4-4" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
        </button>
      </div>

      <!-- 已激活的筛选以 token 回显:抽屉收起时状态依然可见、可单独撤销 -->
      <div class="token-row" id="activeTokens" hidden></div>

      <!-- 三级:抽屉。默认收起,把纵向空间让给列表 -->
      <div class="filter-drawer" id="filterDrawer" hidden>
        <div class="drawer-group" id="sceneFilters">
          <p class="drawer-title" data-i18n="drawer.scenes">场景</p>
          <div class="filter-row">
            <label class="ctl"><span>Min nodes</span>
              <input id="minNodes" type="number" min="1" max="38" placeholder="—">
            </label>
            <label class="ctl"><span data-i18n="f.edited">状态</span>
              <select id="editedFilter">
                <option value="" data-i18n="opt.all">全部</option>
                <option value="1" data-i18n="edited.yes">已人工改过</option>
                <option value="0" data-i18n="edited.no">未改动</option>
              </select>
            </label>
          </div>
          <div class="chip-row" id="sceneChips">
            <label class="chip"><input id="fOrphan" type="checkbox"><span data-i18n="chip.orphan">含孤儿节点</span></label>
            <label class="chip"><input id="fNoted" type="checkbox"><span data-i18n="chip.noted">含带标记的边</span></label>
          </div>
          <div class="filter-row">
            <label class="ctl"><span data-i18n="f.origin">含某类边</span>
              <select id="originFilter">
                <option value="" data-i18n="opt.all">全部</option>
                <option value="corridor" data-i18n="sec.corridor">§1 同走廊</option>
                <option value="road_route" data-i18n="sec.road_route">§2 路网可达</option>
                <option value="short_link" data-i18n="sec.short_link">§3 短直连</option>
                <option value="component_merge" data-i18n="sec.component_merge">§4 分量合并</option>
              </select>
            </label>
          </div>
        </div>

        <div class="drawer-group" id="queueFilters" hidden>
          <p class="drawer-title" data-i18n="drawer.queue">信号队列</p>
          <div class="filter-row">
            <label class="ctl"><span>Kind</span>
              <select id="kindFilter">
                <option value="" data-i18n="kind.all">节点 + 边</option>
                <option value="node" data-i18n="kind.node">只看节点</option>
                <option value="edge" data-i18n="kind.edge">只看边</option>
              </select>
            </label>

          </div>
          <div class="filter-row">
            <label class="ctl"><span data-i18n="f.signal">标记</span>
              <select id="signalFilter">
                <option value="" data-i18n="sig.all">全部标记</option>
                <option value="orphan" data-i18n="sig.orphan">§6 孤儿节点</option>
                <option value="water_crossed" data-i18n="sig.water">§0 跨水</option>
                <option value="building_crossed" data-i18n="sig.building">§0 穿建筑</option>
                <option value="no_road_path" data-i18n="sig.noRoad">无道路通路</option>
                <option value="long_detour" data-i18n="sig.detour">§4 红边绕远</option>
              </select>
            </label>
            <label class="ctl"><span data-i18n="f.reviewed">已复核</span>
              <select id="showReviewedSel">
                <option value="" data-i18n="rev.exclude">不含</option>
                <option value="1" data-i18n="rev.include">含已复核</option>
              </select>
            </label>
          </div>
        </div>
        <button type="button" class="drawer-reset" id="filterReset" data-i18n="filter.reset">清空全部筛选</button>
      </div>
    </div>
    <div class="list-meter"><span id="listCount">—</span></div>
    <div class="queue-list" id="queueList" role="listbox" aria-label="Review queue"></div>
    <div class="queue-footer">
      <button class="quiet-button" id="pagePrev">←</button>
      <button class="quiet-button" id="previousButton"><kbd>K</kbd> Prev</button>
      <button class="quiet-button" id="nextButton">Next <kbd>J</kbd></button>
      <button class="quiet-button" id="pageNext">→</button>
    </div>
  </aside>

  <!-- 02 证据 --------------------------------------------------------->
  <section class="evidence-panel">
    <div class="scene-header">
      <div>
        <div class="scene-title-row">
          <h2 id="sceneTitle" data-i18n="scene.none">未选择</h2>
          <span class="scene-city" id="sceneCity">—</span>
        </div>
        <p class="scene-id" id="sceneId">—</p>
      </div>
      <div class="scene-metrics">
        <div><span>Nodes</span><strong id="mNodes">—</strong></div>
        <div><span>Edges</span><strong id="mEdges">—</strong></div>
        <div><span>Corridors</span><strong id="mCorridors">—</strong></div>
        <div><span>Orphans</span><strong id="mOrphans">—</strong></div>
      </div>
    </div>

    <div class="evidence-grid">
      <article class="image-card">
        <div class="image-card-head">
          <span class="image-kind">Scene Graph</span>
          <div class="map-card-tools">
            <div class="basemap-switch" role="group" aria-label="边的画法" data-i18n-aria-label="edgeStyle.aria">
              <button type="button" id="edgeStraight" class="active" title="直线连接(默认,更醒目)" data-i18n-title="edgeStyle.straight">LINE</button>
              <button type="button" id="edgeRoad" title="沿道路走线" data-i18n-title="edgeStyle.road">ROAD</button>
            </div>
            <div class="basemap-switch" role="group" aria-label="底图切换" data-i18n-aria-label="basemap.aria">
              <button type="button" id="baseSat" class="active">SAT</button>
              <button type="button" id="baseMap">MAP</button>
            </div>
            <div class="map-controls" aria-label="电子地图缩放" data-i18n-aria-label="mapZoom.aria">
              <button type="button" id="mapZoomIn" title="放大" data-i18n-title="zoom.in">+</button>
              <button type="button" id="mapZoomOut" title="缩小" data-i18n-title="zoom.out">−</button>
            </div>
          </div>
        </div>
        <div class="image-stage satellite-stage" id="satelliteStage">
          <img id="satelliteImage" alt="satellite">
          <div class="street-map" id="streetMap" aria-hidden="true">
            <div class="map-tile-layer" id="mapTileLayer"></div>
          </div>
          <svg class="graph-svg" id="graphSvg" viewBox="0 0 400 400" preserveAspectRatio="none"></svg>
        </div>
        <div class="image-caption">
          <span class="legend">
            <span data-i18n="sec.corridor"><i class="lg-corridor"></i>§1 同走廊</span>
            <span data-i18n="sec.road_route"><i class="lg-road"></i>§2 路网可达</span>
            <span data-i18n="sec.short_link"><i class="lg-short"></i>§3 短直连</span>
            <span data-i18n="sec.component_merge"><i class="lg-merge"></i>§4 分量合并</span>
            <span data-i18n="legend.orphan"><i class="lg-orphan"></i>孤儿</span>
          </span>
        </div>
      </article>

      <div class="ground-stack">
        <article class="image-card ground-card">
          <div class="image-card-head compact">
            <div class="node-label anchor-label" id="anchorBadge">—</div>
            <span class="image-kind" id="anchorKind">Node</span>
          </div>
          <div class="ground-grid" id="anchorGrid" aria-label="四个朝向的街景" data-i18n-aria-label="ground.aria"></div>
          <div class="image-caption mono" id="anchorCaption">—</div>
        </article>
        <article class="image-card ground-card" id="targetCard">
          <div class="image-card-head compact">
            <div class="node-label target-label" id="targetBadge">—</div>
            <span class="image-kind">Target</span>
          </div>
          <div class="ground-grid" id="targetGrid" aria-label="四个朝向的街景" data-i18n-aria-label="ground.aria"></div>
          <div class="image-caption mono" id="targetCaption">—</div>
        </article>
      </div>
    </div>

    <div class="context-rail">
      <div>
        <span class="image-kind">Scene Nodes</span>
        <p data-i18n="rail.hint">点击可切换到该节点</p>
      </div>
      <div class="node-thumbnails" id="nodeThumbnails"></div>
      <div class="corridor-legend" id="corridorLegend"></div>
    </div>

    <section class="graph-card" aria-labelledby="graphTitle">
      <div class="graph-head">
        <div>
          <p class="section-index">SCENE ADJACENCY</p>
          <h3 id="graphTitle" data-i18n="adj.title">节点邻接矩阵</h3>
        </div>
        <div class="graph-counts">
          <span data-i18n="adj.corridor"><b id="gCorridor">—</b> 同走廊</span>
          <span data-i18n="adj.road"><b id="gRoad">—</b> 路网</span>
          <span data-i18n="adj.short"><b id="gShort">—</b> 直连</span>
          <span data-i18n="adj.merge"><b id="gMerge">—</b> 合并</span>
        </div>
      </div>
      <p class="graph-rule" data-i18n="adj.rule">每一格是一条无向边,颜色对应它的来源。点击格子或右侧列表可直接检查该条边。</p>
      <div class="graph-body">
        <div><table class="adjacency-table" id="adjacencyTable"></table></div>
        <div class="graph-edge-list" id="edgeList"></div>
      </div>
    </section>
  </section>

  <!-- 03 判定 --------------------------------------------------------->
  <aside class="inspector-panel">
    <div class="panel-heading inspector-heading">
      <div>
        <p class="section-index">03 / PROPERTIES</p>
        <h2 id="decisionTitle" data-i18n="insp.title">属性</h2>
      </div>
      <span class="relation-id" id="itemId">—</span>
    </div>
    <div id="inspectorBody"><p class="empty-note" data-i18n="insp.pick">从左侧队列选一条,或在图上点节点/边。</p></div>
  </aside>
</main>

<dialog id="helpDialog">
  <div class="dialog-head">
    <h3 data-i18n="help.heading">快捷键</h3>
    <button class="icon-button" id="helpClose" aria-label="关闭" data-i18n-aria-label="help.close">×</button>
  </div>
  <div class="dialog-body">
    <div class="shortcut-grid">
      <kbd>J</kbd><span data-i18n="help.j">队列下一条</span>
      <kbd>K</kbd><span data-i18n="help.k">队列上一条</span>
      <kbd>B</kbd><span data-i18n="help.b">切换卫星图 / 电子地图</span>
      <kbd>E</kbd><span data-i18n="help.e">切换边的画法:沿路走线 / 直线</span>
      <kbd>H</kbd><span data-i18n-html="help.h">把当前节点的四个朝向依次放大看(<kbd>Shift</kbd>+<kbd>H</kbd> 反向)</span>
      <span class="keys"><kbd>[</kbd> <kbd>]</kbd></span><span data-i18n="help.page">列表上一页 / 下一页</span>
      <kbd>Q</kbd><span data-i18n="help.q">切换「全部场景」/「带信号的」</span>
      <kbd>?</kbd><span data-i18n="help.help">打开本帮助</span>
    </div>
  </div>
</dialog>

<div class="lightbox" id="lightbox"><img id="lightboxImage" alt=""></div>
<div id="toast" class="toast"></div>
<script src="app.js"></script>
</body>
</html>