fix(hierarchy): scroll-to-selected раскрывает workspace+группы+папки
Бага: при выделении объекта в 3D-сцене иерархия не скроллила к нему
потому что:
- workspaceOpen=false скрывал всю секцию Сцены
- rootPrimsOpen/rootBlocksOpen/rootModelsOpen=false скрывали корневые
группы
- openFolders=пустой Set скрывал все папки с импортированной геометрией
Фикс: effect раскрывает все секции содержащие выбранный объект перед
скроллом. После раскрытия использует rAF×2 чтобы дать React дорендерить
ItemRow'ы, потом scrollIntoView.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
cc6447b851
commit
513c9ce26f
@ -269,24 +269,51 @@ const HierarchyPanel = ({
|
|||||||
const [renaming, setRenaming] = useState(null);
|
const [renaming, setRenaming] = useState(null);
|
||||||
|
|
||||||
// Авто-скролл к выбранному элементу: когда юзер выделяет объект в 3D-сцене
|
// Авто-скролл к выбранному элементу: когда юзер выделяет объект в 3D-сцене
|
||||||
// (или приходит выделение извне) — прокручиваем иерархию к нему.
|
// (или приходит выделение извне) — раскрываем родительские папки и
|
||||||
// Работает по data-sel-id который ItemRow получает через style (см. ниже).
|
// прокручиваем иерархию к нему.
|
||||||
const hierarchyRootRef = useRef(null);
|
const hierarchyRootRef = useRef(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selection) return;
|
if (!selection) return;
|
||||||
const root = hierarchyRootRef.current;
|
|
||||||
if (!root) return;
|
|
||||||
let selId = null;
|
let selId = null;
|
||||||
if (selection.type === 'primitive') selId = `primitive:${selection.id}`;
|
if (selection.type === 'primitive') selId = `primitive:${selection.id}`;
|
||||||
else if (selection.type === 'model') selId = `model:${selection.instanceId}`;
|
else if (selection.type === 'model') selId = `model:${selection.instanceId}`;
|
||||||
else if (selection.type === 'block') selId = `block:${selection.gridX},${selection.gridY},${selection.gridZ}`;
|
else if (selection.type === 'block') selId = `block:${selection.gridX},${selection.gridY},${selection.gridZ}`;
|
||||||
if (!selId) return;
|
if (!selId) return;
|
||||||
// querySelector через CSS.escape для безопасности с двоеточием и запятыми
|
|
||||||
const el = root.querySelector(`[data-sel-id="${CSS.escape(selId)}"]`);
|
// 1) Раскрываем ВСЁ что может скрывать выбранный объект:
|
||||||
if (el && typeof el.scrollIntoView === 'function') {
|
// - все папки (folders) - вдруг примитив сидит в Folder
|
||||||
el.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
// - rootPrimsOpen - группа "Примитивы" в корне
|
||||||
|
// - sceneOpen — корень сцены
|
||||||
|
if (folders && folders.length > 0) {
|
||||||
|
setOpenFolders(prev => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
let changed = false;
|
||||||
|
for (const f of folders) {
|
||||||
|
if (!next.has(f.id)) { next.add(f.id); changed = true; }
|
||||||
|
}
|
||||||
|
return changed ? next : prev;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [selection?.type, selection?.id, selection?.instanceId, selection?.gridX, selection?.gridY, selection?.gridZ]);
|
if (!workspaceOpen) setWorkspaceOpen(true);
|
||||||
|
if (selection.type === 'primitive' && !rootPrimsOpen) setRootPrimsOpen(true);
|
||||||
|
if (selection.type === 'block' && !rootBlocksOpen) setRootBlocksOpen(true);
|
||||||
|
if (selection.type === 'model' && !rootModelsOpen) setRootModelsOpen(true);
|
||||||
|
|
||||||
|
// 2) Скролл через 2 кадра (даём React перерендерить после раскрытия)
|
||||||
|
const tick = () => {
|
||||||
|
const root = hierarchyRootRef.current;
|
||||||
|
if (!root) return;
|
||||||
|
const el = root.querySelector(`[data-sel-id="${CSS.escape(selId)}"]`);
|
||||||
|
if (el && typeof el.scrollIntoView === 'function') {
|
||||||
|
el.scrollIntoView({ block: 'center', behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const raf1 = requestAnimationFrame(() => {
|
||||||
|
const raf2 = requestAnimationFrame(tick);
|
||||||
|
return () => cancelAnimationFrame(raf2);
|
||||||
|
});
|
||||||
|
return () => cancelAnimationFrame(raf1);
|
||||||
|
}, [selection?.type, selection?.id, selection?.instanceId, selection?.gridX, selection?.gridY, selection?.gridZ, folders?.length, workspaceOpen, rootPrimsOpen, rootBlocksOpen, rootModelsOpen, openFolders]);
|
||||||
|
|
||||||
const startRename = (kind, refKey, currentValue) => {
|
const startRename = (kind, refKey, currentValue) => {
|
||||||
setRenaming({ kind, refKey, value: currentValue || '' });
|
setRenaming({ kind, refKey, value: currentValue || '' });
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user