Связи (pixel-aquarium, 10.06): партия 2 (UI-фишки) — поиск, хлебные крошки, бейджи, цветовые кластеры
Всё в лаборатории (вариант 2: реальный путь /network-view не трогаем). - Поиск + телепорт: строка .fg-search; Enter → graph.findNode(имя) → камера летит к узлу (dive в «Вселенной», иначе перецентр). - Хлебные крошки: .fg-breadcrumb «Иван › Нина › Ада» (движок шлёт onDiveChange(path), API getDivePath); клик по корню — полный сброс, по предку — навигация на его уровень. - Бейдж числа связей: .fg-node-badge (degreeById → updateBadges; у центра — число связей 1-го уровня). - Цветовые кластеры: мягкая аура узла по типу связи (CSS is-family/friend/business/contact). Автопроверки расширены до 17 ассертов (добавлены поиск/крошки/бейдж) — прогон 17/17 PASS. Фикс: TDZ breadcrumbEl (объявлен до createForceGraph, т.к. onDiveChange вызывается при монтировании). Бамп client.version → 1.2.149. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9a49cc67f0
commit
557ea96be0
@ -1,2 +1,2 @@
|
||||
client.version=1.2.148
|
||||
server.version=1.2.132
|
||||
client.version=1.2.149
|
||||
server.version=1.2.133
|
||||
|
||||
@ -137,10 +137,18 @@
|
||||
мигания у порога); **двойной тап по фону** и **сильный pinch-out на мин. зуме** = быстрый выход;
|
||||
**префетч аватарок** детей при наведении/нырке.
|
||||
|
||||
**Фишки (партия 2, лаборатория):**
|
||||
- **Поиск + телепорт** — строка `.fg-search`; Enter → `graph.findNode(имя)` → камера летит к узлу (dive в
|
||||
«Вселенной», иначе перецентр).
|
||||
- **Хлебные крошки** — `.fg-breadcrumb` «Иван › Нина › Ада» (движок шлёт `onDiveChange(path)`,
|
||||
API `getDivePath()`); клик по корню — полный сброс, по предку — навигация на тот уровень.
|
||||
- **Бейдж числа связей** — `.fg-node-badge` (число из `degreeById`, обновляется в `updateBadges`).
|
||||
- **Цветовые кластеры** — мягкая аура узла по типу связи (CSS `is-family/friend/business/contact`).
|
||||
|
||||
**Автопроверки (`?fgtest`):** `js/pages/network/selftest.js` автозапускается в лаборатории при `?fgtest`,
|
||||
прогоняет 14 ассертов (центровка/collision/полукруг/spotlight/переключение/LOD/выход) через детерминированные
|
||||
dev-хелперы движка `graph.debugState()` и `graph.pumpForTest()` (синхронно докручивают кадры до покоя — не
|
||||
зависят от троттлинга rAF). Результат → консоль и `window.__fgTestResults`. В обычной работе не активны.
|
||||
прогоняет 17 ассертов (центровка/collision/полукруг/spotlight/переключение/LOD/поиск/крошки/бейдж/выход) через
|
||||
детерминированные dev-хелперы движка `graph.debugState()` и `graph.pumpForTest()` (синхронно докручивают кадры
|
||||
до покоя — не зависят от троттлинга rAF). Результат → консоль и `window.__fgTestResults`. В обычной работе не активны.
|
||||
|
||||
> ⚠️ Эксперименты на ветках `pixel-web` (паутина) и `pixel-aquarium` (Smart Zoom) — для отката.
|
||||
> Реальный путь `/network-view` не затронут: deep-код под `tier ≥ 2` / `hasDeep`, dive — только tier≥2
|
||||
|
||||
@ -143,7 +143,7 @@ function hash01(str) {
|
||||
* @param {Function} [opts.onNodeLongPress] - долгое нажатие (node, screenPoint) => void
|
||||
* @returns {{ destroy: Function, recenter: Function, setModel: Function, getFocusNode: Function }}
|
||||
*/
|
||||
export function createForceGraph({ stage, model, onCenterTap, onNodeTap, onNodeLongPress, onNodeHover } = {}) {
|
||||
export function createForceGraph({ stage, model, onCenterTap, onNodeTap, onNodeLongPress, onNodeHover, onDiveChange } = {}) {
|
||||
// Слои DOM
|
||||
const edgesSvg = document.createElementNS(SVGNS, 'svg');
|
||||
edgesSvg.setAttribute('class', 'fg-edges');
|
||||
@ -173,18 +173,25 @@ export function createForceGraph({ stage, model, onCenterTap, onNodeTap, onNodeL
|
||||
let diveZoom = 1; // целевой зум активного погружения
|
||||
let surfacing = false; // идёт «всплытие» назад (камера/зум возвращаются к корню)
|
||||
let childCountByParent = new Map(); // parentId → число детей (для адаптивного радиуса орбиты, без слипания)
|
||||
let degreeById = new Map(); // id → число связей узла (для бейджа-счётчика на аватарке)
|
||||
const rebuildIndex = () => {
|
||||
nodeById = new Map(nodes.map((n) => [String(n.id), n]));
|
||||
hasDeep = nodes.some((n) => n.tier >= 2);
|
||||
// число детей у родителя + порядковый индекс ребёнка среди братьев (для веера «полукругом наружу»)
|
||||
childCountByParent = new Map();
|
||||
degreeById = new Map();
|
||||
let tier1count = 0;
|
||||
for (const n of nodes) {
|
||||
if (n.tier >= 2 && n.parentId) {
|
||||
const i = childCountByParent.get(n.parentId) || 0;
|
||||
n.sibIndex = i;
|
||||
childCountByParent.set(n.parentId, i + 1);
|
||||
degreeById.set(n.parentId, (degreeById.get(n.parentId) || 0) + 1); // у родителя +1 связь
|
||||
} else if (n.tier === 1 && String(n.id) !== focusId) {
|
||||
tier1count += 1;
|
||||
}
|
||||
}
|
||||
degreeById.set(focusId, tier1count); // у центра — число связей 1-го уровня
|
||||
};
|
||||
|
||||
// Spotlight: при закреплённой кликом ветке остальной граф тускнеет до SPOTLIGHT_DIM (0.25), чтобы
|
||||
@ -212,12 +219,34 @@ export function createForceGraph({ stage, model, onCenterTap, onNodeTap, onNodeL
|
||||
return set;
|
||||
}
|
||||
let _pathSet = new Set();
|
||||
let _pathSetKey = ' | ||||