feat(editor): два слота code_js/code_lua в скриптах

Раньше при смене языка код оставался как есть → подсветка кричит
ошибками, юзер пишет JS в Lua-режиме и наоборот.

Сейчас:
- Скрипт хранит code_js и code_lua отдельно (плюс активный code).
- При переключении JS↔Lua: текущий code сохраняется в слот ТЕКУЩЕГО
  языка, достаётся слот ЦЕЛЕВОГО языка. Если слот пустой — шаблон.
- При обычном save (печатает юзер) код зеркалится в слот активного
  языка чтобы не потерять при swap.
- Никаких модалок: переключение мгновенное, ничего не пропадает.

Сценарии:
- Новый проект: js-шаблон в code_js. Клик Lua → подставляется lua-шаблон,
  code_js сохранён.
- Юзер написал JS-код, переключился на Lua: JS улетел в code_js,
  показывается пустой Lua-шаблон. Клик обратно JS → код возвращается.
- Старые проекты (без code_js/code_lua): первое переключение
  засеет слот текущего языка из code.
This commit is contained in:
min 2026-06-09 02:04:37 +03:00
parent 3f9f7cd6c7
commit ea4d759a7c
3 changed files with 52 additions and 18 deletions

View File

@ -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();
}}

View File

@ -38,21 +38,21 @@ import ConfirmModal from './ConfirmModal';
// командой `python _build_bundle.py` в той же папке.
// Дефолтный шаблон Lua-скрипта для нового скрипта (на Part или глобальный).
// Используется при смене языка JSLua когда текущий код выглядит «пустым».
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={{

View File

@ -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 откатит