// Лабораторный режим карты связей (network-view/lab). // // Назначение: на мок-данных (без бэкенда) щупать физику force-графа, панорамирование, // центрирование и навигацию между пользователями. Используется связанный мульти-граф // networkGraphUsers: у каждого пользователя свой набор связей, тап по узлу переключает // карту на сеть этого человека (как реальный путь, но локально). import { renderHeader } from '../../components/header.js'; import { networkGraphUsers } from '../../mock-data.js'; import { createForceGraph, buildModelFromTz } from './force-graph.js'; import { openNodeMenu } from './node-menu.js'; const START_LOGIN = 'ivan'; function helpText() { return [ 'Лаборатория карты связей (мок-данные, без сервера).', '• Тащите по экрану — карта свободно перемещается (pan).', '• Тап по узлу — он стягивается в центр и карта перестраивается под его сеть.', '• Тап по центральному узлу — здесь открылся бы профиль.', '• Долгое нажатие — контекстное меню (в реальном пути «Связи»).', '', 'Доступные люди: Иван, Алиса, Павел, Елена, Дмитрий, Олег, Нина, Марина, Света, Кирилл.', ].join('\n'); } // Граф пользователя по логину; если такого нет в датасете — одинокий узел (без связей). function graphForLogin(login) { const key = String(login || '').trim().toLowerCase(); if (networkGraphUsers[key]) return networkGraphUsers[key]; return { focusUser: { id: key, login: key, name: key, avatar: null, status: '' }, connections: [] }; } export function renderNetworkLab({ navigate }) { const screen = document.createElement('section'); screen.className = 'network-screen'; const appScreenEl = document.getElementById('app-screen'); appScreenEl?.classList.add('network-scroll-lock'); const stage = document.createElement('div'); stage.className = 'network-stage fg-stage'; const header = renderHeader({ title: 'Связи · лаборатория', leftAction: { label: '←', onClick: () => navigate('network-view'), }, rightActions: [ { label: '?', onClick: () => window.alert(helpText()) }, ], }); header.classList.add('network-header-overlay'); const model = buildModelFromTz(graphForLogin(START_LOGIN)); stage.append(header); screen.append(stage); // Монтируем движок синхронно (не через rAF): размеры сцены движок берёт с запасом // (window.innerWidth) и корректирует через ResizeObserver, когда сцена попадёт в DOM. const graph = createForceGraph({ stage, model, // тап по узлу — переключаем карту на сеть выбранного человека onNodeTap: (node) => { graph.setModel(buildModelFromTz(graphForLogin(node.login))); }, onCenterTap: (node) => window.alert(`Профиль: ${node.name || node.login || node.id}`), onNodeLongPress: (node, point) => openNodeMenu({ login: node.name || node.login || node.id, relationType: node.relationType, point, actions: [ { label: 'Профиль', onClick: () => window.alert(`Профиль: ${node.name || node.login}`) }, { label: 'Написать', onClick: () => window.alert(`Написать: ${node.name || node.login}`) }, ], }), }); screen.cleanup = () => { graph.destroy(); appScreenEl?.classList.remove('network-scroll-lock'); }; return screen; }