node-graph-viewer / web /index.html
MarcoXL's picture
Claude Opus 5.5
viewer: 中英双语界面(顶栏右侧 中文/EN 切换)
0c571a4
Raw History Blame Contribute Delete
15.9 kB
<!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>