From e0f0726e6815bb7c2220e80172835ceaf47db85e7597bf85ce9321f31e35ee37 Mon Sep 17 00:00:00 2001 From: Pixel Date: Tue, 9 Jun 2026 21:23:16 +0300 Subject: [PATCH 1/6] =?UTF-8?q?=D0=A1=D0=B2=D1=8F=D0=B7=D0=B8:=20=D0=B8?= =?UTF-8?q?=D0=BD=D1=82=D0=B5=D1=80=D0=B0=D0=BA=D1=82=D0=B8=D0=B2=D0=BD?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=BA=D0=B0=D1=80=D1=82=D0=B0=20=D1=81=D0=B2?= =?UTF-8?q?=D1=8F=D0=B7=D0=B5=D0=B9=20(force-directed=20graph)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Переработка экрана «Связи» в интерактивный нод-граф с премиальными переходами. Движок (js/pages/network/force-graph.js): - diffing-переходы: общие узлы перелетают, новые расцветают каскадом, исчезнувшие — Ghost-слой (800мс, на месте); - мягкая радиальная пружина + отталкивание (органичная орбита), упругий влёт фокуса; - динамическая вязкость на старте (трение 0.92→0.82, отталкивание ослаблено) — мягкий разлёт без тряски; - жёсткая заморозка (kill-switch) при затухании — нет «треска», экономия батареи; - линии — SVG Безье (изогнутые нити), прорастание; жесты pan с инерцией; - хард-лимит DOM-аватарок (остальное — SVG-точки). Интеграция и UX: - adapter.js: getUserConnectionsGraph → модель движка (сервер не трогаем, read-only); - фильтры (Все/Семья/Друзья/Сияющие), контекстное меню (node-menu.js), нижний сниппет, профиль; - прицел в центре, дыхание фокуса, свечение сияющих; - лаборатория network-view/lab на мок-данных (networkGraphUsers) для тестов без бэкенда. Документация: shine-UI/Dev_Docs/features/interactive-network-graph.md. Бамп client.version 1.2.135 -> 1.2.136. Co-Authored-By: Claude Opus 4.8 (1M context) --- VERSION.properties | 2 +- .../features/interactive-network-graph.md | 72 ++ shine-UI/index.html | 2 +- shine-UI/js/mock-data.js | 113 +++ shine-UI/js/pages/network-view.js | 548 +++-------- shine-UI/js/pages/network/adapter.js | 72 ++ shine-UI/js/pages/network/force-graph.js | 900 ++++++++++++++++++ shine-UI/js/pages/network/lab.js | 86 ++ shine-UI/js/pages/network/node-menu.js | 84 ++ shine-UI/styles/network-graph.css | 426 +++++++++ 10 files changed, 1887 insertions(+), 418 deletions(-) create mode 100644 shine-UI/Dev_Docs/features/interactive-network-graph.md create mode 100644 shine-UI/js/pages/network/adapter.js create mode 100644 shine-UI/js/pages/network/force-graph.js create mode 100644 shine-UI/js/pages/network/lab.js create mode 100644 shine-UI/js/pages/network/node-menu.js create mode 100644 shine-UI/styles/network-graph.css diff --git a/VERSION.properties b/VERSION.properties index 5f66ce5..fe2f1be 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.135 +client.version=1.2.136 server.version=1.2.127 diff --git a/shine-UI/Dev_Docs/features/interactive-network-graph.md b/shine-UI/Dev_Docs/features/interactive-network-graph.md new file mode 100644 index 0000000..91601d6 --- /dev/null +++ b/shine-UI/Dev_Docs/features/interactive-network-graph.md @@ -0,0 +1,72 @@ +# Интерактивная карта связей (force-directed graph) + +Экран **«Связи»** (`network-view`) — интерактивная нод-граф карта вместо статичного списка: +фокусный пользователь в центре, связи на орбите, навигация тапом/свайпом, премиальные +переходы в духе нативного iOS. + +## Где код +- `js/pages/network/force-graph.js` — **движок** (физика, рендер, жизненный цикл узлов, жесты). +- `js/pages/network/adapter.js` — реальные данные → нейтральная модель движка. +- `js/pages/network/node-menu.js` — общее контекстное меню узла. +- `js/pages/network/lab.js` — лаборатория (`network-view/lab`) на мок-данных, без бэкенда. +- `js/pages/network-view.js` — страница: шапка, поиск, фильтры, история, склейка с движком. +- `js/mock-data.js` — `networkGraphUsers` (связанный мульти-граф из 10 человек для лаборатории). +- `styles/network-graph.css` — все стили `.fg-*`. + +## Данные (read-only, сервер не трогаем) +Единый источник — `authService.getUserConnectionsGraph(login)` (один запрос: логин → прямые связи). +`network-view.js` → `buildGraphModel()` нормализует роли (parent/child/sibling/spouse/friend/contact), +направление и метки; `adapter.engineModelFromGraphModel()` превращает это в модель движка: +`{ focusId, nodes:[{ id, login, name, avatar, relationType, strength, shining, tier }] }`. + +## Модель движка и API +`createForceGraph({ stage, model, onNodeTap, onCenterTap, onNodeLongPress })` → +`{ setModel(model), setFilter(pred), recenter(id), getFocusNode(), destroy() }`. + +## Ключевые механики +- **Diffing-переходы (непрерывность состояний):** при смене фокуса общие узлы (тот же `id`) не + пересоздаются, а перелетают пружиной на новые места; новые «расцветают» (bloom) каскадом из центра; + исчезнувшие уходят в Ghost-слой. +- **Ghost-слой:** снимок всего старого графа (узлы + линии) на полноэкранном overlay, застывает + на месте, `scale 1→0.7` + `opacity 0.5→0` за **800мс**, затем удаляется (красивый шлейф истории). +- **Физика:** мягкая радиальная пружина к орбите + взаимное отталкивание (charge) → органичная, + слегка неровная орбита; фокус влетает в центр упруго. Координаты узлов на трансформах (GPU). +- **Каскадный bloom:** новые узлы скрыты в центре и «выстреливают» по очереди (`order × 40мс`). +- **Динамическая вязкость:** первые ~600мс после перестроения трение завышено (0.92), отталкивание + ослаблено (×0.45) → гасит «взрыв», затем плавно к базе (0.82) — мягкое «резиновое» появление. +- **Жёсткая заморозка (kill-switch):** когда сумма |vx|+|vy| < 0.03 — скорости обнуляются, координаты + округляются до целых пикселей, `cancelAnimationFrame` (sleep). Нет «треска», батарея не страдает. +- **Линии:** SVG ` Q` (квадратичные Безье) — изящные изогнутые нити, тонкие/полупрозрачные; + при движении изгиб реагирует на скорость; новые линии прорастают (`stroke-dashoffset`). +- **Жесты:** свайп-pan с инерцией (новое касание прерывает); короткий тап — центрирование + нижний + сниппет; долгий тап — контекстное меню (в `#modal-root`, позиция по `getBoundingClientRect`); тап + по центру — профиль. +- **Фильтры слоёв:** Все / Семья / Друзья / Сияющие (плавное скрытие/перераспределение). +- **Поллиш:** «прицел» в центре (+пульс при захвате фокуса), «дыхание» фокуса (бесконечная CSS-анимация + размера 1.48–1.52x, GPU, не будит rAF), свечение «сияющих», хард-лимит ~90 DOM-аватарок (остальное — + SVG-точки). + +## Параметры тюнинга (константы в начале `force-graph.js`) +| Константа | Значение | Назначение | +|---|---|---| +| `ORBIT_MIN / ORBIT_MAX` | 150 / 240 | радиус орбиты (защитный отступ от центра — подписи не наезжают) | +| `K_RADIAL` | 0.035 | жёсткость орбитальной пружины (мягко) | +| `K_FOCUS` | 0.12 | жёсткость пружины фокуса к центру | +| `CHARGE` / `CHARGE_START_FACTOR` | 1400 / 0.45 | отталкивание (на старте ослаблено) | +| `FRICTION` / `FRICTION_BOOST` / `BOOST_FRAMES` | 0.82 / 0.92 / 36 | базовое трение / стартовая вязкость / длительность (~600мс) | +| `SLEEP_V` | 0.03 | порог суммарной |v| для заморозки | +| `FOCUS_SCALE` | 1.5 | базовый масштаб фокуса | +| `MAX_FULL_NODES` | 90 | хард-лимит полных аватарок (далее — точки) | + +## Локальный запуск / проверка +- Dev-сервер: `.claude/shine-ui-dev-server.cjs` (Node, порт 7321, SPA-fallback + инжект ``). +- Лаборатория (без бэкенда): `http://localhost:7321/network-view/lab` — мок `networkGraphUsers`, + тап по узлам переключает сети. +- Реальный путь (`/network-view`) требует живого `wss://shineup.me/ws` (локально — `ws_open_error`, это норма). + +## Ограничения / на будущее +- Многоуровневая глубина (друзья друзей мельче, 3-й уровень — точки), кластеры, «общие связи» + упираются в API (отдаёт только прямые связи) — требуют доработки сервера. +- `lerpX/lerpY` в движке больше не используются для отрисовки — кандидат на чистку. +- Превью в простое троттлит `requestAnimationFrame` (физика не идёт между вызовами) — для замеров + прокачивать кадры; в активном табе всё работает на 60 FPS. diff --git a/shine-UI/index.html b/shine-UI/index.html index 5d1a017..9c7f792 100644 --- a/shine-UI/index.html +++ b/shine-UI/index.html @@ -12,7 +12,7 @@