fix(studio): выделение папки раскрывает дерево + Delete удаляет всю папку с содержимым
1) При выделении папки (клик по сцене / вставка кита) дерево авто-раскрывается: workspace + цепочка родителей + scrollIntoView к строке папки. Раньше папка выделялась на сцене, но в свёрнутом дереве её не было видно. 2) Delete на выделенной папке (type='folder') → removeFolder(id, true) удаляет всю папку со ВСЕМ содержимым + _cleanupOrphanScripts чистит осиротевшие скрипты привязанные к удалённым объектам. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
46414d874b
commit
2d669a3ff3
@ -376,6 +376,23 @@ const HierarchyPanel = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selection) return;
|
if (!selection) return;
|
||||||
const t = selection.type;
|
const t = selection.type;
|
||||||
|
// Выделена ПАПКА (клик по сцене / вставка кита из тулбокса) — раскрыть
|
||||||
|
// «Сцену» и цепочку папок до неё, чтобы папка стала видна в дереве.
|
||||||
|
if (t === 'folder') {
|
||||||
|
setWorkspaceOpen(true);
|
||||||
|
setOpenFolders(prev => {
|
||||||
|
const n = new Set(prev);
|
||||||
|
let cur = selection.folderId;
|
||||||
|
const guard = new Set();
|
||||||
|
while (cur != null && !guard.has(cur)) {
|
||||||
|
guard.add(cur);
|
||||||
|
const f = folders.find(ff => ff.id === cur);
|
||||||
|
if (f && f.parentId != null) { n.add(f.parentId); cur = f.parentId; } else cur = null;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Находим объект и его folderId по выделению.
|
// Находим объект и его folderId по выделению.
|
||||||
let obj = null, kind = null;
|
let obj = null, kind = null;
|
||||||
if (t === 'block') {
|
if (t === 'block') {
|
||||||
@ -486,10 +503,12 @@ const HierarchyPanel = ({
|
|||||||
const subUserModels = userModelsByFolder.get(folder.id) || [];
|
const subUserModels = userModelsByFolder.get(folder.id) || [];
|
||||||
const totalCount = subBlocks.length + subModels.length + subPrims.length + subUserModels.length + subFolders.length;
|
const totalCount = subBlocks.length + subModels.length + subPrims.length + subUserModels.length + subFolders.length;
|
||||||
|
|
||||||
|
const folderSelected = selection?.type === 'folder' && selection?.folderId === folder.id;
|
||||||
return (
|
return (
|
||||||
<div key={`folder-${folder.id}`} className={cl.folderWrap}>
|
<div key={`folder-${folder.id}`} className={cl.folderWrap}>
|
||||||
<div
|
<div
|
||||||
className={`${cl.folderHeader} ${selection?.type === 'folder' && selection?.folderId === folder.id ? cl.itemSelected : ''}`}
|
ref={folderSelected ? (el) => { if (el) el.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); } : null}
|
||||||
|
className={`${cl.folderHeader} ${folderSelected ? cl.itemSelected : ''}`}
|
||||||
style={{ paddingLeft: depth * 12 + 8 }}
|
style={{ paddingLeft: depth * 12 + 8 }}
|
||||||
onClick={() => onSelectFolder?.(folder.id)}
|
onClick={() => onSelectFolder?.(folder.id)}
|
||||||
onContextMenu={(e) => handleContextMenu(e, { type: 'folder', ...folder })}
|
onContextMenu={(e) => handleContextMenu(e, { type: 'folder', ...folder })}
|
||||||
|
|||||||
@ -6604,6 +6604,25 @@ export class BabylonScene {
|
|||||||
if (this._onSceneChange) this._onSceneChange();
|
if (this._onSceneChange) this._onSceneChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Удалить скрипты, чей объект-носитель больше не существует (после удаления
|
||||||
|
* папки/объектов). Глобальные (target null/'game') не трогаем. */
|
||||||
|
_cleanupOrphanScripts() {
|
||||||
|
if (!Array.isArray(this._scripts)) return;
|
||||||
|
const exists = (t) => {
|
||||||
|
if (!t || t === 'game') return true;
|
||||||
|
if (t.kind === 'primitive') return !!this.primitiveManager?.instances?.has(t.id);
|
||||||
|
if (t.kind === 'model') return !!this.modelManager?.instances?.has(t.id);
|
||||||
|
if (t.kind === 'userModel') return !!this.userModelManager?.instances?.has(t.id);
|
||||||
|
return true; // block и пр. — не чистим
|
||||||
|
};
|
||||||
|
const before = this._scripts.length;
|
||||||
|
this._scripts = this._scripts.filter(s => exists(s.target));
|
||||||
|
if (this._scripts.length !== before) {
|
||||||
|
this.history?.markChange();
|
||||||
|
if (this._onSceneChange) this._onSceneChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Зарегистрировать колбэк для уведомлений об изменении режима Play
|
* Зарегистрировать колбэк для уведомлений об изменении режима Play
|
||||||
* (вызывается когда player сам инициирует exit, например по Esc).
|
* (вызывается когда player сам инициирует exit, например по Esc).
|
||||||
|
|||||||
@ -724,6 +724,17 @@ export class SelectionManager {
|
|||||||
} else if (this._selection.type === 'spawn') {
|
} else if (this._selection.type === 'spawn') {
|
||||||
// Удаление точки спавна → игрок будет появляться в (0, высота, 0).
|
// Удаление точки спавна → игрок будет появляться в (0, высота, 0).
|
||||||
this._scene3d?.deleteSpawn?.();
|
this._scene3d?.deleteSpawn?.();
|
||||||
|
} else if (this._selection.type === 'folder') {
|
||||||
|
// Папка целиком — удаляем со ВСЕМ содержимым (рекурсивно).
|
||||||
|
const fid = this._selection.folderId;
|
||||||
|
this.clear();
|
||||||
|
this._scene3d?.folderManager?.removeFolder?.(fid, true);
|
||||||
|
// Удаляем скрипты, привязанные к объектам этой папки? Они привязаны
|
||||||
|
// к примитивам, которые removeFolder удалит; скрипты на них чистятся
|
||||||
|
// через _onSceneChange / при сохранении. Дополнительно пусть движок
|
||||||
|
// подчистит «осиротевшие» скрипты.
|
||||||
|
this._scene3d?._cleanupOrphanScripts?.();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
this.clear();
|
this.clear();
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user