From 2d669a3ff313b121c47e27ecceac285bdd803617 Mon Sep 17 00:00:00 2001 From: min Date: Fri, 5 Jun 2026 18:36:55 +0300 Subject: [PATCH] =?UTF-8?q?fix(studio):=20=D0=B2=D1=8B=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=B0=D0=BF=D0=BA=D0=B8=20?= =?UTF-8?q?=D1=80=D0=B0=D1=81=D0=BA=D1=80=D1=8B=D0=B2=D0=B0=D0=B5=D1=82=20?= =?UTF-8?q?=D0=B4=D0=B5=D1=80=D0=B5=D0=B2=D0=BE=20+=20Delete=20=D1=83?= =?UTF-8?q?=D0=B4=D0=B0=D0=BB=D1=8F=D0=B5=D1=82=20=D0=B2=D1=81=D1=8E=20?= =?UTF-8?q?=D0=BF=D0=B0=D0=BF=D0=BA=D1=83=20=D1=81=20=D1=81=D0=BE=D0=B4?= =?UTF-8?q?=D0=B5=D1=80=D0=B6=D0=B8=D0=BC=D1=8B=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1) При выделении папки (клик по сцене / вставка кита) дерево авто-раскрывается: workspace + цепочка родителей + scrollIntoView к строке папки. Раньше папка выделялась на сцене, но в свёрнутом дереве её не было видно. 2) Delete на выделенной папке (type='folder') → removeFolder(id, true) удаляет всю папку со ВСЕМ содержимым + _cleanupOrphanScripts чистит осиротевшие скрипты привязанные к удалённым объектам. Co-Authored-By: Claude Opus 4.8 --- src/editor/HierarchyPanel.jsx | 21 ++++++++++++++++++++- src/editor/engine/BabylonScene.js | 19 +++++++++++++++++++ src/editor/engine/SelectionManager.js | 11 +++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) 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(); }