SHA256
Add WS push events, PWA/FCM scaffolding, and direct messaging MVP
This commit is contained in:
@@ -1,77 +1,75 @@
|
||||
import { renderHeader } from '../components/header.js?v=20260403081123';
|
||||
import { networkGraph } from '../mock-data.js?v=20260403081123';
|
||||
import { authService, state } from '../state.js?v=20260403081123';
|
||||
|
||||
export const pageMeta = { id: 'network-view', title: 'Связи' };
|
||||
|
||||
function toPoint(v) {
|
||||
return `${v.x}%`;
|
||||
}
|
||||
|
||||
function showHelpModal() {
|
||||
const root = document.getElementById('modal-root');
|
||||
root.innerHTML = `
|
||||
<div class="modal" id="network-help-modal">
|
||||
<div class="modal-card stack">
|
||||
<h3 style="font-size:18px;">Справка по схеме связей</h3>
|
||||
<p class="meta-muted">В центре находишься ты.</p>
|
||||
<p class="meta-muted">Рядом показаны друзья первого уровня.</p>
|
||||
<p class="meta-muted">Далее могут существовать друзья второго уровня.</p>
|
||||
<p class="meta-muted">При одном нажатии на узел можно показать его связи.</p>
|
||||
<p class="meta-muted">При двойном нажатии узел может переместиться в центр.</p>
|
||||
<p class="meta-muted">При долгом удержании может открываться меню действий.</p>
|
||||
<p class="meta-muted">Логика схемы строится на одном запросе связей пользователя, дальше дерево достраивается на его основе.</p>
|
||||
<button class="primary-btn" id="close-network-help">Понятно</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
root.querySelector('#close-network-help').addEventListener('click', () => {
|
||||
root.innerHTML = '';
|
||||
});
|
||||
function makeNode(name, cls = '') {
|
||||
const n = document.createElement('div');
|
||||
n.className = `node ${cls}`.trim();
|
||||
n.innerHTML = `<div class="node-dot">${(name[0] || '?').toUpperCase()}</div><div class="node-label">${name}</div>`;
|
||||
return n;
|
||||
}
|
||||
|
||||
export function render() {
|
||||
const screen = document.createElement('section');
|
||||
screen.className = 'stack';
|
||||
|
||||
const header = renderHeader({
|
||||
title: 'Связи',
|
||||
rightActions: [{ label: 'Справка', onClick: showHelpModal }],
|
||||
});
|
||||
|
||||
const board = document.createElement('div');
|
||||
board.className = 'network-board';
|
||||
|
||||
const lines = networkGraph.peers
|
||||
.map(
|
||||
(peer) =>
|
||||
`<line x1="${toPoint(networkGraph.center)}" y1="${networkGraph.center.y}%" x2="${peer.x}%" y2="${peer.y}%" stroke="rgba(125,170,255,0.55)" stroke-width="1.5"/>`
|
||||
)
|
||||
.join('');
|
||||
|
||||
board.innerHTML = `<svg class="network-svg" viewBox="0 0 100 100" preserveAspectRatio="none">${lines}</svg>`;
|
||||
|
||||
const centerNode = document.createElement('div');
|
||||
centerNode.className = 'node center';
|
||||
centerNode.style.left = `${networkGraph.center.x}%`;
|
||||
centerNode.style.top = `${networkGraph.center.y}%`;
|
||||
centerNode.innerHTML = `<div class="node-dot">${networkGraph.center.initials}</div><div class="node-label">${networkGraph.center.name}</div>`;
|
||||
|
||||
board.append(centerNode);
|
||||
|
||||
networkGraph.peers.forEach((peer) => {
|
||||
const node = document.createElement('div');
|
||||
node.className = 'node';
|
||||
node.style.left = `${peer.x}%`;
|
||||
node.style.top = `${peer.y}%`;
|
||||
node.innerHTML = `<div class="node-dot">${peer.initials}</div><div class="node-label">${peer.name}</div>`;
|
||||
board.append(node);
|
||||
});
|
||||
board.style.height = 'calc(100dvh - 170px)';
|
||||
|
||||
const note = document.createElement('p');
|
||||
note.className = 'meta-muted';
|
||||
note.textContent = 'Схема статичная для демо, архитектура подготовлена под дальнейшую интерактивность.';
|
||||
note.textContent = 'Загрузка связей...';
|
||||
|
||||
screen.append(header, board, note);
|
||||
const load = async (centerLogin) => {
|
||||
try {
|
||||
const graph = await authService.getUserConnectionsGraph(centerLogin || state.session.login);
|
||||
board.innerHTML = '';
|
||||
const center = makeNode(graph.login || state.session.login, 'center');
|
||||
center.style.left = '50%';
|
||||
center.style.top = '50%';
|
||||
board.append(center);
|
||||
|
||||
const all = [...new Set([...(graph.outFriends || []), ...(graph.inFriends || [])])];
|
||||
const left = all.slice(0, Math.ceil(all.length / 2));
|
||||
const right = all.slice(Math.ceil(all.length / 2));
|
||||
|
||||
const mk = (name, side, idx, total) => {
|
||||
const node = makeNode(name);
|
||||
const y = 15 + ((idx + 1) * 70) / (Math.max(total, 1) + 1);
|
||||
node.style.left = side === 'left' ? '20%' : '80%';
|
||||
node.style.top = `${y}%`;
|
||||
node.addEventListener('click', () => load(name));
|
||||
board.append(node);
|
||||
|
||||
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
||||
line.setAttribute('x1', '50');
|
||||
line.setAttribute('y1', '50');
|
||||
line.setAttribute('x2', side === 'left' ? '20' : '80');
|
||||
line.setAttribute('y2', String(y));
|
||||
line.setAttribute('stroke', 'rgba(125,170,255,0.6)');
|
||||
line.setAttribute('stroke-width', '1.5');
|
||||
return line;
|
||||
};
|
||||
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.setAttribute('class', 'network-svg');
|
||||
svg.setAttribute('viewBox', '0 0 100 100');
|
||||
svg.setAttribute('preserveAspectRatio', 'none');
|
||||
|
||||
left.forEach((name, i) => svg.append(mk(name, 'left', i, left.length)));
|
||||
right.forEach((name, i) => svg.append(mk(name, 'right', i, right.length)));
|
||||
board.prepend(svg);
|
||||
|
||||
note.textContent = 'Нажмите на любой узел, чтобы построить связи выбранного пользователя.';
|
||||
} catch (e) {
|
||||
note.textContent = `Ошибка загрузки связей: ${e.message || 'unknown'}`;
|
||||
}
|
||||
};
|
||||
|
||||
load();
|
||||
|
||||
screen.append(renderHeader({ title: 'Связи' }), board, note);
|
||||
return screen;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user