SHA256
Улучшить аватары и граф связей
This commit is contained in:
@@ -709,14 +709,23 @@ function buildStableHistoryEngineModel(history, historyDepth = HISTORY_MAX_PREVI
|
||||
const centerKey = normKey(snap?.centerLogin);
|
||||
const snapNodes = Array.isArray(snap?.engineModel?.nodes) ? snap.engineModel.nodes : [];
|
||||
const ownCenter = snapNodes.find((node) => normKey(node?.id) === centerKey);
|
||||
if (ownCenter) centerNodeByKey.set(centerKey, { ...ownCenter, isHistoryCenter: true, keepVisible: true, tier: 1 });
|
||||
if (ownCenter) centerNodeByKey.set(centerKey, {
|
||||
...ownCenter,
|
||||
isHistoryCenter: true,
|
||||
clusterOwnerId: centerKey,
|
||||
keepVisible: true,
|
||||
tier: 1,
|
||||
});
|
||||
|
||||
snapNodes.forEach((rawNode) => {
|
||||
const key = normKey(rawNode?.id);
|
||||
if (!key) return;
|
||||
// Исторические центры сохраняют позицию собственного кластера. Все остальные общие узлы
|
||||
// принадлежат самому свежему кластеру, где встретились, и поэтому «переезжают» туда без дубля.
|
||||
if (!centerKeys.has(key) || key === latestCenterKey || key === centerKey) nodeMap.set(key, { ...rawNode, id: key });
|
||||
if (!centerKeys.has(key) || key === latestCenterKey || key === centerKey) {
|
||||
// Общий узел принадлежит последнему кластеру, возле которого он был перерисован.
|
||||
nodeMap.set(key, { ...rawNode, id: key, clusterOwnerId: centerKey });
|
||||
}
|
||||
|
||||
if (key === centerKey) return;
|
||||
let parents = Array.isArray(rawNode?.edgeParents) ? rawNode.edgeParents : [];
|
||||
@@ -755,9 +764,11 @@ function buildStableHistoryEngineModel(history, historyDepth = HISTORY_MAX_PREVI
|
||||
id: key,
|
||||
login: node?.login || node?.id || key,
|
||||
tier: centerKeys.has(key) ? 1 : Math.max(1, Number(node?.tier) || 1),
|
||||
isHistoryCenter: centerKeys.has(key),
|
||||
fixedLayout: true,
|
||||
keepVisible: centerKeys.has(key) || Boolean(node?.keepVisible),
|
||||
alwaysVisible: (Number(node?.tier) || 1) >= 2 ? true : Boolean(node?.alwaysVisible),
|
||||
clusterOwnerId: normKey(node?.clusterOwnerId || key),
|
||||
edgeParents: edgeParentsByChild.get(key) || [],
|
||||
}));
|
||||
|
||||
@@ -1098,11 +1109,15 @@ export function render({ navigate, route, chrome } = {}) {
|
||||
window.setTimeout(() => inputEl.focus(), 0);
|
||||
}
|
||||
|
||||
function persistManualNodePosition(nodeId, point) {
|
||||
const key = normKey(nodeId);
|
||||
const x = Number(point?.x);
|
||||
const y = Number(point?.y);
|
||||
if (!key || !Number.isFinite(x) || !Number.isFinite(y)) return;
|
||||
function persistManualNodePositions(rows) {
|
||||
const positions = new Map();
|
||||
(Array.isArray(rows) ? rows : []).forEach((row) => {
|
||||
const key = normKey(row?.id || row?.login);
|
||||
const x = Number(row?.x);
|
||||
const y = Number(row?.y);
|
||||
if (key && Number.isFinite(x) && Number.isFinite(y)) positions.set(key, { x, y });
|
||||
});
|
||||
if (!positions.size) return;
|
||||
|
||||
// Координаты исторических snapshot'ов уже находятся в общей world-системе. Обновляем все
|
||||
// упоминания пользователя, чтобы следующий setModel/filter/history render не откатил ручной drag.
|
||||
@@ -1110,12 +1125,13 @@ export function render({ navigate, route, chrome } = {}) {
|
||||
const model = cloneEngineModel(snapshot?.engineModel || { focusId: '', nodes: [] });
|
||||
let changed = false;
|
||||
model.nodes = model.nodes.map((node) => {
|
||||
if (normKey(node?.id) !== key) return node;
|
||||
const point = positions.get(normKey(node?.id));
|
||||
if (!point) return node;
|
||||
changed = true;
|
||||
return {
|
||||
...node,
|
||||
layoutX: x,
|
||||
layoutY: y,
|
||||
layoutX: point.x,
|
||||
layoutY: point.y,
|
||||
fixedLayout: true,
|
||||
};
|
||||
});
|
||||
@@ -1149,10 +1165,19 @@ export function render({ navigate, route, chrome } = {}) {
|
||||
}
|
||||
void load(node.login, { pushHistory: true, transitionAngle, transitionX, transitionY });
|
||||
},
|
||||
// тап по центру — полноценный профиль
|
||||
// Короткий тап по текущему центру сразу открывает его меню.
|
||||
onCenterTap: (node) => {
|
||||
const routeTo = profileInfoRoute(node.login);
|
||||
if (routeTo) navigate(routeTo);
|
||||
const rect = node?.el?.getBoundingClientRect?.();
|
||||
openNodeMenu({
|
||||
login: normalizeLogin(node?.login),
|
||||
displayName: String(node?.name || '').trim(),
|
||||
relationType: node?.relationType,
|
||||
point: rect ? { x: rect.left + rect.width / 2, y: rect.top, rect } : undefined,
|
||||
actions: [
|
||||
{ label: 'Профиль', onClick: () => { const r = profileInfoRoute(node?.login); if (r) navigate(r); } },
|
||||
{ label: 'Написать', onClick: () => navigate(`chat/${encodeURIComponent(normalizeLogin(node?.login))}`) },
|
||||
],
|
||||
});
|
||||
},
|
||||
// долгое нажатие — контекстное меню (вне масштабируемого холста)
|
||||
onNodeLongPress: (node, point) => {
|
||||
@@ -1171,7 +1196,12 @@ export function render({ navigate, route, chrome } = {}) {
|
||||
// Drag периферийного аватара — ручная правка текущей карты. Движок уже двигает DOM/рёбра
|
||||
// в реальном времени; здесь только сохраняем итоговую world-позицию в историю/X2 snapshot.
|
||||
onNodeMoveEnd: (node, point) => {
|
||||
persistManualNodePosition(node?.id || node?.login, point);
|
||||
persistManualNodePositions([{ id: node?.id || node?.login, ...point }]);
|
||||
},
|
||||
// Центр и узлы, которые в последний раз были нарисованы возле него,
|
||||
// сохраняются одной пачкой, чтобы общий узел не вернулся в старый кластер при rebuild.
|
||||
onGroupMoveEnd: (movedNodes) => {
|
||||
persistManualNodePositions(movedNodes);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user