diff --git a/src/editor/engine/StudioCollab.js b/src/editor/engine/StudioCollab.js index ccb4c74..283c584 100644 --- a/src/editor/engine/StudioCollab.js +++ b/src/editor/engine/StudioCollab.js @@ -203,6 +203,96 @@ export class StudioCollab { }; } } + + // GUI-элементы (GuiManager: create/update/remove/setParent). + // Передаём полный набор полей с id, чтобы у соавтора создался ТОТ ЖЕ id. + const gm = this.scene.guiManager; + if (gm && !gm.__collabPatched) { + gm.__collabPatched = true; + const origGCreate = gm.create.bind(gm); + gm.create = function (type, opts = {}) { + const id = origGCreate(type, opts); + if (id != null && !self._applyingRemote) { + const el = gm.get?.(id); + self.sendOp({ op: 'guiCreate', id, type, def: el ? { ...el } : { ...opts, id } }); + } + return id; + }; + const origGUpd = gm.update.bind(gm); + gm.update = function (id, patch) { + const r = origGUpd(id, patch); + if (!self._applyingRemote && patch && typeof patch === 'object') { + self.sendOp({ op: 'guiUpdate', id, patch: { ...patch } }); + } + return r; + }; + const origGRem = gm.remove.bind(gm); + gm.remove = function (id) { + const r = origGRem(id); + if (!self._applyingRemote) self.sendOp({ op: 'guiRemove', id }); + return r; + }; + } + + // Папки (FolderManager: createFolder/renameFolder/removeFolder). + const fm = this.scene.folderManager; + if (fm && !fm.__collabPatched) { + fm.__collabPatched = true; + if (typeof fm.createFolder === 'function') { + const origFC = fm.createFolder.bind(fm); + fm.createFolder = function (name, parentId = null) { + const r = origFC(name, parentId); + if (!self._applyingRemote) { + // r может быть id или объектом папки — нормализуем. + const id = (r && typeof r === 'object') ? (r.id ?? r) : r; + self.sendOp({ op: 'folderCreate', id, name, parentId: parentId ?? null }); + } + return r; + }; + } + if (typeof fm.renameFolder === 'function') { + const origFR = fm.renameFolder.bind(fm); + fm.renameFolder = function (id, name) { + const r = origFR(id, name); + if (!self._applyingRemote) self.sendOp({ op: 'folderRename', id, name }); + return r; + }; + } + if (typeof fm.removeFolder === 'function') { + const origFRem = fm.removeFolder.bind(fm); + fm.removeFolder = function (id, deleteContent = false) { + const r = origFRem(id, deleteContent); + if (!self._applyingRemote) self.sendOp({ op: 'folderRemove', id, deleteContent: !!deleteContent }); + return r; + }; + } + } + + // Пользовательские модели (UserModelManager: addInstance async / removeInstance). + const um = this.scene.userModelManager; + if (um && !um.__collabPatched) { + um.__collabPatched = true; + if (typeof um.addInstance === 'function') { + const origUMAdd = um.addInstance.bind(um); + um.addInstance = function (userModelTypeId, x, y, z, rotationY = 0, options = {}) { + const ret = origUMAdd(userModelTypeId, x, y, z, rotationY, options); + Promise.resolve(ret).then((id) => { + if (id != null && !self._applyingRemote) { + self.sendOp({ op: 'userModelAdd', id, userModelTypeId, x, y, z, rotationY }); + } + }).catch(() => {}); + return ret; + }; + } + if (typeof um.removeInstance === 'function') { + const origUMRem = um.removeInstance.bind(um); + um.removeInstance = function (id) { + const r = origUMRem(id); + if (!self._applyingRemote) self.sendOp({ op: 'userModelRemove', id }); + return r; + }; + } + } } /** Снять обёртки (при выходе из коллаба восстановить оригиналы — простой флаг). */ @@ -440,6 +530,52 @@ export function applyRemoteOp(scene, op) { scene.removeScript?.(op.id); scene._onCollabScriptsChange?.(); return; + // GUI-элементы + case 'guiCreate': + scene.guiManager?.create?.(op.type, op.def || { id: op.id }); + return; + case 'guiUpdate': + scene.guiManager?.update?.(op.id, op.patch || {}); + return; + case 'guiRemove': + scene.guiManager?.remove?.(op.id); + return; + // Папки + case 'folderCreate': + // createFolder(name, parentId) генерит свой автоинкремент-id, но нам + // нужен ТОТ ЖЕ id что у автора (иначе rename/remove и folderId + // объектов разойдутся). folders — Map; перевешиваем запись. + { + const fm2 = scene.folderManager; + if (fm2?.createFolder) { + const madeId = fm2.createFolder(op.name, op.parentId ?? null); + if (op.id != null && madeId !== op.id && fm2.folders?.has?.(madeId)) { + const data = fm2.folders.get(madeId); + fm2.folders.delete(madeId); + data.id = op.id; + fm2.folders.set(op.id, data); + if (fm2._nextId != null && op.id >= fm2._nextId) fm2._nextId = op.id + 1; + fm2._notifyChange?.(); + } + } + } + return; + case 'folderRename': + scene.folderManager?.renameFolder?.(op.id, op.name); + return; + case 'folderRemove': + scene.folderManager?.removeFolder?.(op.id, !!op.deleteContent); + return; + // Пользовательские модели — forceInstanceId, чтобы id совпал у всех + // (иначе move/remove по id автора не найдут инстанс у соавтора). + case 'userModelAdd': + scene.userModelManager?.addInstance?.( + op.userModelTypeId, op.x, op.y, op.z, op.rotationY || 0, + { forceInstanceId: op.id }); + return; + case 'userModelRemove': + scene.userModelManager?.removeInstance?.(op.id); + return; } switch (t) { case 'add': {