diff --git a/src/editor/HierarchyPanel.jsx b/src/editor/HierarchyPanel.jsx index aef2513..a39613c 100644 --- a/src/editor/HierarchyPanel.jsx +++ b/src/editor/HierarchyPanel.jsx @@ -376,6 +376,23 @@ const HierarchyPanel = ({ useEffect(() => { if (!selection) return; 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 по выделению. let obj = null, kind = null; if (t === 'block') { @@ -486,10 +503,12 @@ const HierarchyPanel = ({ const subUserModels = userModelsByFolder.get(folder.id) || []; const totalCount = subBlocks.length + subModels.length + subPrims.length + subUserModels.length + subFolders.length; + const folderSelected = selection?.type === 'folder' && selection?.folderId === folder.id; return (
{ if (el) el.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); } : null} + className={`${cl.folderHeader} ${folderSelected ? cl.itemSelected : ''}`} style={{ paddingLeft: depth * 12 + 8 }} onClick={() => onSelectFolder?.(folder.id)} onContextMenu={(e) => handleContextMenu(e, { type: 'folder', ...folder })} diff --git a/src/editor/engine/BabylonScene.js b/src/editor/engine/BabylonScene.js index edaccc0..d5ff5e7 100644 --- a/src/editor/engine/BabylonScene.js +++ b/src/editor/engine/BabylonScene.js @@ -6604,6 +6604,25 @@ export class BabylonScene { 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 * (вызывается когда player сам инициирует exit, например по Esc). diff --git a/src/editor/engine/SelectionManager.js b/src/editor/engine/SelectionManager.js index 22b0a94..cf1dbcf 100644 --- a/src/editor/engine/SelectionManager.js +++ b/src/editor/engine/SelectionManager.js @@ -724,6 +724,17 @@ export class SelectionManager { } else if (this._selection.type === 'spawn') { // Удаление точки спавна → игрок будет появляться в (0, высота, 0). 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(); }