From aed64e76a72822f2a77200c81fe3afac4eefcde090cf8f8b21eedfc57b7ba726 Mon Sep 17 00:00:00 2001 From: AidarKC Date: Tue, 9 Jun 2026 19:12:48 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A1=D0=B2=D1=8F=D0=B7=D0=B8:=20=D0=BB=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=80=D0=B0=D1=82=D0=BE=D1=80=D0=B8=D1=8F=20=D1=82?= =?UTF-8?q?=D0=BE=D0=BB=D1=8C=D0=BA=D0=BE=20=D0=BB=D0=BE=D0=BA=D0=B0=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D0=BE=20(=D0=B8=D1=81=D0=BA=D0=BB=D1=8E=D1=87?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=B8=D0=B7=20=D1=80=D0=B5=D0=BF=D0=BE?= =?UTF-8?q?=D0=B7=D0=B8=D1=82=D0=BE=D1=80=D0=B8=D1=8F)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - shine-UI/js/pages/network/lab.js убран из git (git rm --cached) и добавлен в .gitignore: на сервере лаборатория не нужна (там реальные данные), локально файл остаётся и работает. - network-view.js: статический импорт lab.js заменён на ДИНАМИЧЕСКИЙ с фолбэком — если файла нет (прод/сервер), реальный экран «Связи» не ломается, а заход на /network-view/lab уводит на обычный экран. Локально лаборатория грузится как прежде. - Документация фичи отражает, что lab.js — локальный (в .gitignore). - Бамп client.version → 1.2.140. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 4 + VERSION.properties | 2 +- .../features/interactive-network-graph.md | 2 + shine-UI/js/pages/network-view.js | 15 +- shine-UI/js/pages/network/lab.js | 131 ------------------ 5 files changed, 19 insertions(+), 135 deletions(-) delete mode 100644 shine-UI/js/pages/network/lab.js diff --git a/.gitignore b/.gitignore index 914dba7..b3b6272 100644 --- a/.gitignore +++ b/.gitignore @@ -93,3 +93,7 @@ ESP32/**/*.a # Полные серверные бэкапы (тяжёлые архивы, не коммитим) server-backup/archive/** !server-backup/archive/.gitkeep + +# Лаборатория карты связей — только для локальной разработки (на сервере не нужна). +# Файл остаётся локально и работает; network-view.js грузит его динамически (с фолбэком). +shine-UI/js/pages/network/lab.js diff --git a/VERSION.properties b/VERSION.properties index 30b0b99..36e913f 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.139 +client.version=1.2.140 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 index 972a472..62a17ca 100644 --- a/shine-UI/Dev_Docs/features/interactive-network-graph.md +++ b/shine-UI/Dev_Docs/features/interactive-network-graph.md @@ -9,6 +9,8 @@ - `js/pages/network/adapter.js` — реальные данные → нейтральная модель движка. - `js/pages/network/node-menu.js` — общее контекстное меню узла. - `js/pages/network/lab.js` — лаборатория (`network-view/lab`) на мок-данных, без бэкенда. + **Только локально** (в `.gitignore`, в репозитории её нет): на сервере она не нужна. `network-view.js` + грузит её **динамическим** импортом — если файла нет (прод), реальный экран «Связи» не ломается. - `js/pages/network-view.js` — страница: шапка, поиск, фильтры, история, склейка с движком. - `js/mock-data.js` — `networkGraphUsers` (связанный мульти-граф из 10 человек для лаборатории). - `styles/network-graph.css` — все стили `.fg-*`. diff --git a/shine-UI/js/pages/network-view.js b/shine-UI/js/pages/network-view.js index 595f53c..e7f57f5 100644 --- a/shine-UI/js/pages/network-view.js +++ b/shine-UI/js/pages/network-view.js @@ -2,7 +2,6 @@ import { renderHeader } from '../components/header.js'; import { authService, state } from '../state.js'; import { makeProfileRoute } from '../services/shine-routes.js'; import { makeProfileLinksRoute } from '../services/shine-routes.js'; -import { renderNetworkLab } from './network/lab.js'; import { createForceGraph } from './network/force-graph.js'; import { engineModelFromGraphModel } from './network/adapter.js'; import { openNodeMenu, relationLabelRu } from './network/node-menu.js'; @@ -217,9 +216,19 @@ let persistedCenterLogin = ''; let persistedCenterHistory = []; export function render({ navigate, route }) { - // Лабораторный режим force-графа (Фаза 1): рендерится из мока, не трогая реальный путь ниже. + // Лабораторный режим force-графа — ТОЛЬКО для локальной разработки. Файл `network/lab.js` + // намеренно НЕ в гите (см. .gitignore): на сервере он не нужен (там реальные данные). Поэтому + // импорт ДИНАМИЧЕСКИЙ — если файла нет (прод/сервер), реальный экран «Связи» не ломается, + // а заход на /network-view/lab просто уводит на обычный экран. if (String(route?.params?.mode || '').trim().toLowerCase() === 'lab') { - return renderNetworkLab({ navigate }); + const host = document.createElement('div'); + host.style.display = 'contents'; // прозрачная обёртка: lab-экран ведёт себя как прямой потомок + let inner = null; + import('./network/lab.js') + .then((m) => { inner = m.renderNetworkLab({ navigate }); host.append(inner); }) + .catch(() => { navigate('network-view'); }); // нет файла (сервер) — на реальный экран + host.cleanup = () => { if (inner && typeof inner.cleanup === 'function') inner.cleanup(); }; + return host; } const keepHistory = String(route?.params?.mode || '').trim().toLowerCase() === 'keep-history'; const routeLogin = normalizeLogin(route?.params?.login || ''); diff --git a/shine-UI/js/pages/network/lab.js b/shine-UI/js/pages/network/lab.js deleted file mode 100644 index dc2a655..0000000 --- a/shine-UI/js/pages/network/lab.js +++ /dev/null @@ -1,131 +0,0 @@ -// Лабораторный режим карты связей (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'; - -// Фильтры слоёв — те же, что в реальном пути network-view (предикат по периферийным узлам; -// фокус виден всегда). Позволяют пощупать в лаборатории в т.ч. слой «Сияющие». -const FILTERS = { - all: { label: 'Все', pred: () => true }, - family: { label: 'Семья', pred: (n) => n.relationType === 'family' }, - friends: { label: 'Друзья', pred: (n) => n.relationType === 'friend' }, - shining: { label: 'Сияющие', pred: (n) => Boolean(n.shining) }, -}; -const FILTER_ORDER = ['all', 'family', 'friends', 'shining']; - -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)); - - // Состояние активного слоя (как в network-view): фокус всегда виден. - let activeFilter = 'all'; - const filterChips = {}; - function applyFilter(key) { - if (!FILTERS[key]) return; - activeFilter = key; - FILTER_ORDER.forEach((k) => { - const el = filterChips[k]; - if (el) el.classList.toggle('is-active', k === activeFilter); - }); - graph.setFilter(FILTERS[key].pred); - } - - stage.append(header); - screen.append(stage); - - // Монтируем движок синхронно (не через rAF): размеры сцены движок берёт с запасом - // (window.innerWidth) и корректирует через ResizeObserver, когда сцена попадёт в DOM. - const graph = createForceGraph({ - stage, - model, - // тап по узлу — переключаем карту на сеть выбранного человека - onNodeTap: (node) => { - graph.setModel(buildModelFromTz(graphForLogin(node.login))); - // сохраняем выбранный слой при переходе на сеть другого человека (как в network-view) - if (activeFilter !== 'all') graph.setFilter(FILTERS[activeFilter].pred); - }, - 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}`) }, - ], - }), - }); - - // Панель фильтров слоёв — тот же оверлей-стиль (.fg-filter-bar), что и в network-view. - const filterBar = document.createElement('div'); - filterBar.className = 'fg-filter-bar'; - // Не даём нажатию на чип «провалиться» в сцену: иначе движок делает setPointerCapture на stage, - // а захват указателя перенаправляет нативный click со сцены — и кнопка фильтра не срабатывает. - filterBar.addEventListener('pointerdown', (e) => e.stopPropagation()); - FILTER_ORDER.forEach((key) => { - const chip = document.createElement('button'); - chip.type = 'button'; - chip.className = `fg-filter-chip${key === activeFilter ? ' is-active' : ''}`; - chip.textContent = FILTERS[key].label; - chip.addEventListener('click', () => applyFilter(key)); - filterChips[key] = chip; - filterBar.append(chip); - }); - stage.append(filterBar); - - screen.cleanup = () => { - graph.destroy(); - appScreenEl?.classList.remove('network-scroll-lock'); - }; - - return screen; -}