diff --git a/src/editor/KubikonEditor.jsx b/src/editor/KubikonEditor.jsx index 0fe45d4..f5ba123 100644 --- a/src/editor/KubikonEditor.jsx +++ b/src/editor/KubikonEditor.jsx @@ -30,7 +30,7 @@ import BillboardEditorModal from './BillboardEditorModal'; import TerrainGenPanel from './TerrainGenPanel'; import ScriptConsole from './ScriptConsole'; import SceneTabs from './SceneTabs'; -import ScriptEditor from './ScriptEditor'; +import ScriptEditor, { LUA_TEMPLATE_PART, LUA_TEMPLATE_GLOBAL, JS_TEMPLATE_GLOBAL } from './ScriptEditor'; import GameHud from './GameHud'; import MinimapOverlay from './MinimapOverlay'; import GuiOverlay from './GuiOverlay'; @@ -3327,13 +3327,40 @@ const KubikonEditor = () => { language={sc.language || 'js'} flushRef={scriptEditorFlushRef} isSoloRunning={soloScriptId === sc.id} - onLanguageChange={(lang, newCode) => { - sceneRef.current?.upsertScript(sc.id, newCode, undefined, undefined, lang); + onLanguageChange={(lang, currentEditorCode) => { + // Два слота: code_js и code_lua живут в самом скрипте. + // При переключении: сохраняем текущий код в слот ТЕКУЩЕГО + // языка, достаём слот ЦЕЛЕВОГО языка (или шаблон если пусто). + const fromLang = sc.language === 'lua' ? 'lua' : 'js'; + if (fromLang === lang) return; + const fromSlotKey = fromLang === 'lua' ? 'code_lua' : 'code_js'; + const toSlotKey = lang === 'lua' ? 'code_lua' : 'code_js'; + // Сохраняем текущий редактируемый код в слот текущего языка + const savedSlots = { + ...(sc.code_js !== undefined ? { code_js: sc.code_js } : {}), + ...(sc.code_lua !== undefined ? { code_lua: sc.code_lua } : {}), + [fromSlotKey]: currentEditorCode || '', + }; + // Достаём слот целевого языка или подставляем шаблон + let nextCode = savedSlots[toSlotKey]; + if (nextCode === undefined || nextCode === '') { + nextCode = lang === 'lua' + ? (sc.target ? LUA_TEMPLATE_PART : LUA_TEMPLATE_GLOBAL) + : JS_TEMPLATE_GLOBAL; + } + sceneRef.current?.upsertScript( + sc.id, nextCode, undefined, undefined, lang, savedSlots + ); setScriptsList(sceneRef.current?.getScripts?.() || []); markDirty(); }} onSave={(code) => { - sceneRef.current?.upsertScript(sc.id, code, sc.target); + // Зеркалим в слот активного языка чтобы при swap не потерять. + const slotKey = (sc.language === 'lua') ? 'code_lua' : 'code_js'; + sceneRef.current?.upsertScript( + sc.id, code, sc.target, undefined, undefined, + { [slotKey]: code } + ); setScriptsList(sceneRef.current?.getScripts?.() || []); markDirty(); }} diff --git a/src/editor/ScriptEditor.jsx b/src/editor/ScriptEditor.jsx index d26f4a5..83da9de 100644 --- a/src/editor/ScriptEditor.jsx +++ b/src/editor/ScriptEditor.jsx @@ -38,21 +38,21 @@ import ConfirmModal from './ConfirmModal'; // командой `python _build_bundle.py` в той же папке. // Дефолтный шаблон Lua-скрипта для нового скрипта (на Part или глобальный). // Используется при смене языка JS→Lua когда текущий код выглядит «пустым». -const LUA_TEMPLATE_PART = `-- Скрипт привязан к Part. script.Parent = эта часть. +export const LUA_TEMPLATE_PART = `-- Скрипт привязан к Part. script.Parent = эта часть. local part = script.Parent part.Touched:Connect(function(hit) print("Касание:", hit.Name) end) `; -const LUA_TEMPLATE_GLOBAL = `-- Глобальный Lua-скрипт. Работает на стороне сервера/клиента. +export const LUA_TEMPLATE_GLOBAL = `-- Глобальный Lua-скрипт. Работает на стороне сервера/клиента. local Players = game:GetService("Players") Players.PlayerAdded:Connect(function(player) print("Игрок зашёл:", player.Name) end) `; -const JS_TEMPLATE_GLOBAL = `// Глобальный JS-скрипт. Подробнее: см. game.* API в /справочник. +export const JS_TEMPLATE_GLOBAL = `// Глобальный JS-скрипт. Подробнее: см. game.* API в /справочник. game.onPlayerJoined((player) => { game.chat.say('Привет, ' + player.name + '!'); }); @@ -115,6 +115,15 @@ function ScriptEditor({ value, onSave, onRunSolo, isSoloRunning, scriptId, targe // eslint-disable-next-line react-hooks/exhaustive-deps }, [value]); + // При смене языка — принудительно синхронизируем код со слотом нового языка. + // (родитель swap'нул code_js ↔ code_lua и прислал свежий value.) + useEffect(() => { + if (value !== undefined && value !== localCodeRef.current) { + setLocalCode(value || ''); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [language]); + // Дебаунс-сохранение const scheduleSave = useCallback((code) => { if (debounceRef.current) clearTimeout(debounceRef.current); @@ -340,16 +349,9 @@ function ScriptEditor({ value, onSave, onRunSolo, isSoloRunning, scriptId, targe onClick={() => { if (active) return; if (!onLanguageChange) return; - if (isCodeLikelyEmptyTemplate(localCode)) { - const nextCode = lang === 'lua' - ? (target ? LUA_TEMPLATE_PART : LUA_TEMPLATE_GLOBAL) - : JS_TEMPLATE_GLOBAL; - setLocalCode(nextCode); - onLanguageChange(lang, nextCode); - return; - } - // Код не пустой — переключаем сразу, без модалки. - // Код остаётся как есть, только подсветка синтаксиса меняется. + // Логика двух слотов (code_js / code_lua) живёт в родителе. + // Здесь только сигналим: «переключи на lang». + // Текущий код отдаём чтобы родитель сохранил в слот. onLanguageChange(lang, localCodeRef.current); }} style={{ diff --git a/src/editor/engine/BabylonScene.js b/src/editor/engine/BabylonScene.js index 3a7eb98..a7dc045 100644 --- a/src/editor/engine/BabylonScene.js +++ b/src/editor/engine/BabylonScene.js @@ -6734,7 +6734,7 @@ export class BabylonScene { } /** Установить код одного скрипта по id. Если id нет — создать новый. */ - upsertScript(id, code, target = undefined, name = undefined, language = undefined) { + upsertScript(id, code, target = undefined, name = undefined, language = undefined, slots = undefined) { const i = this._scripts.findIndex(s => s.id === id); if (i >= 0) { this._scripts[i] = { @@ -6743,6 +6743,10 @@ export class BabylonScene { ...(target !== undefined ? { target } : {}), ...(name !== undefined ? { name } : {}), ...(language !== undefined ? { language } : {}), + // Слоты code_js и code_lua — сохраняемый код для каждого языка. + // Передаются при переключении языка, чтобы код другого языка + // не пропадал. + ...(slots && typeof slots === 'object' ? slots : {}), }; } else { this._scripts.push({ @@ -6751,6 +6755,7 @@ export class BabylonScene { target: target !== undefined ? target : null, name: name || null, language: language || 'js', + ...(slots && typeof slots === 'object' ? slots : {}), }); } // Скрипты — часть сцены: фиксируем в истории, иначе undo откатит