89 lines
3.3 KiB
JavaScript
89 lines
3.3 KiB
JavaScript
import { parseEpisodes } from './dashboard-core.js';
|
|
|
|
const AGD = window.AGD;
|
|
let episodes = [];
|
|
let skipped = 0;
|
|
let network = null;
|
|
|
|
// ── data loading ──────────────────────────────────────────────
|
|
async function loadEpisodes() {
|
|
const files = await fetch('/api/episodes').then((r) => r.json());
|
|
const all = [];
|
|
let skip = 0;
|
|
for (const f of files) {
|
|
const url = '/docs/observer/' + f;
|
|
const text = await fetch(url).then((r) => (r.ok ? r.text() : ''));
|
|
const r = parseEpisodes(text);
|
|
all.push(...r.episodes);
|
|
skip += r.skipped;
|
|
}
|
|
all.sort((a, b) => String(a.startedAt).localeCompare(String(b.startedAt)));
|
|
episodes = all;
|
|
skipped = skip;
|
|
document.getElementById('status').textContent =
|
|
`${episodes.length} эпизодов · ${skipped} пропущено`;
|
|
}
|
|
|
|
// ── graph banner ──────────────────────────────────────────────
|
|
function renderGraph() {
|
|
const nodes = new vis.DataSet(AGD.NODES);
|
|
const edges = new vis.DataSet(AGD.EDGES);
|
|
network = new vis.Network(
|
|
document.getElementById('network'),
|
|
{ nodes, edges },
|
|
{
|
|
groups: AGD.GROUPS,
|
|
nodes: { shape: 'dot', borderWidth: 2, font: { multi: 'html' } },
|
|
edges: { smooth: { type: 'continuous', roundness: 0.5 } },
|
|
physics: { enabled: false },
|
|
interaction: { hover: true, tooltipDelay: 400 },
|
|
}
|
|
);
|
|
network.once('afterDrawing', () => network.fit());
|
|
return { nodes, edges };
|
|
}
|
|
|
|
// ── view switching ────────────────────────────────────────────
|
|
const views = {};
|
|
let activeView = 'map';
|
|
|
|
views.map = function renderMapView() {
|
|
// Plain mode: clear any overlay coloring applied by other views.
|
|
window.__graph.nodes.update(AGD.NODES.map((n) => ({ id: n.id, color: undefined })));
|
|
// List the design-time conflict edges (dashed edges carry an emoji label).
|
|
const conflicts = AGD.EDGES.filter((e) => e.dashes === true);
|
|
const ul = document.getElementById('map-conflicts');
|
|
ul.innerHTML = '';
|
|
for (const c of conflicts) {
|
|
const li = document.createElement('li');
|
|
li.textContent = `${c.label || '•'} ${c.from} ↔ ${c.to}: ${c.title || ''}`;
|
|
ul.appendChild(li);
|
|
}
|
|
};
|
|
|
|
function switchView(name) {
|
|
activeView = name;
|
|
for (const v of ['map', 'replay', 'feed', 'aggregate']) {
|
|
document.getElementById('view-' + v).style.display = v === name ? 'block' : 'none';
|
|
}
|
|
document.querySelectorAll('#tabbar button').forEach((b) => {
|
|
b.classList.toggle('active', b.dataset.view === name);
|
|
});
|
|
if (views[name]) views[name]();
|
|
}
|
|
|
|
// ── boot ──────────────────────────────────────────────────────
|
|
async function boot() {
|
|
const gds = renderGraph();
|
|
window.__graph = { network, ...gds };
|
|
document.querySelectorAll('#tabbar button').forEach((b) => {
|
|
b.addEventListener('click', () => switchView(b.dataset.view));
|
|
});
|
|
await loadEpisodes();
|
|
switchView('map');
|
|
}
|
|
|
|
export function getEpisodes() { return episodes; }
|
|
export { views, switchView };
|
|
boot();
|