diff --git a/src/editor/HierarchyPanel.module.css b/src/editor/HierarchyPanel.module.css
index f1f5404..509e62a 100644
--- a/src/editor/HierarchyPanel.module.css
+++ b/src/editor/HierarchyPanel.module.css
@@ -1,8 +1,10 @@
/* === Hierarchy Panel === */
+/* Компактные строки (как Roblox Explorer): меньше вертикальных отступов —
+ больше объектов влезает без скролла. */
.hierarchy {
flex: 1;
overflow-y: auto;
- padding: 6px 0;
+ padding: 4px 0;
font-size: 12px;
user-select: none;
position: relative;
@@ -13,13 +15,13 @@
}
.rootLine {
- padding: 6px 8px;
+ padding: 3px 8px;
color: var(--text);
- font-size: 13px;
+ font-size: 12px;
}
.systemItem {
- padding: 4px 8px 4px 28px;
+ padding: 2px 8px 2px 26px;
color: var(--text-dim);
font-size: 12px;
}
@@ -28,11 +30,11 @@
display: flex;
align-items: center;
gap: 4px;
- padding: 6px 8px;
+ padding: 3px 8px;
color: var(--text);
cursor: pointer;
border-radius: 4px;
- margin-top: 6px;
+ margin-top: 3px;
font-weight: 600;
}
@@ -72,8 +74,8 @@
.item {
display: flex;
align-items: center;
- gap: 8px;
- padding: 4px 8px;
+ gap: 7px;
+ padding: 2px 8px;
cursor: pointer;
border-radius: 4px;
color: var(--text);
diff --git a/src/editor/Icon.jsx b/src/editor/Icon.jsx
index 17dd379..3f24845 100644
--- a/src/editor/Icon.jsx
+++ b/src/editor/Icon.jsx
@@ -73,6 +73,9 @@ const GLYPHS = {
'arrow-down': () => (<>>),
'arrow-left': () => (<>>),
'arrow-right': () => (<>>),
+ // Полноэкранный режим: 4 уголка наружу / внутрь.
+ 'fullscreen': () => (<>>),
+ 'fullscreen-exit': () => (<>>),
refresh: () => (<>>),
cycle: () => (<>>),
flag: () => (<>>),
diff --git a/src/editor/KubikonEditor.jsx b/src/editor/KubikonEditor.jsx
index f234316..8f98726 100644
--- a/src/editor/KubikonEditor.jsx
+++ b/src/editor/KubikonEditor.jsx
@@ -47,6 +47,11 @@ import cl from './KubikonEditor.module.css';
import Icon from './Icon';
import ConfirmModal from './ConfirmModal';
+// В десктоп-приложении (Electron-обёртка, см. rublox-desktop) окно и так на
+// весь экран без браузерной панели — fullscreen НЕ нужен. preload выставляет
+// window.__RUBLOX_DESKTOP__. Глушим авто-fullscreen, чтобы не дёргать окно.
+const IS_DESKTOP_APP = typeof window !== 'undefined' && !!window.__RUBLOX_DESKTOP__;
+
const AUTOSAVE_DEBOUNCE_MS = 10000; // 10 секунд тишины → авто-сохранение
// Шаблон глобального скрипта.
@@ -470,6 +475,98 @@ const KubikonEditor = () => {
const canvasRef = useRef(null);
const sceneRef = useRef(null);
+ // === Fullscreen редактора ===
+ // Верхняя панель браузера съедает ~20% экрана. Браузер НЕ даёт включить
+ // fullscreen автоматически при загрузке (нужен user gesture), поэтому:
+ // 1) кнопка в шапке;
+ // 2) автоматический вход при ПЕРВОМ клике пользователя по редактору.
+ const [isFullscreen, setIsFullscreen] = useState(false);
+ const fsAutoTriedRef = useRef(false); // авто-вход пробуем только 1 раз
+ const requestEditorFullscreen = React.useCallback(() => {
+ try {
+ const root = document.documentElement;
+ const req = root.requestFullscreen
+ || root.webkitRequestFullscreen
+ || root.mozRequestFullScreen
+ || root.msRequestFullscreen;
+ if (req && !document.fullscreenElement) req.call(root).catch(() => {});
+ } catch (e) { /* юзер запретил — работаем в окне */ }
+ }, []);
+ const exitEditorFullscreen = React.useCallback(() => {
+ try {
+ const ex = document.exitFullscreen || document.webkitExitFullscreen
+ || document.mozCancelFullScreen || document.msExitFullscreen;
+ if (ex && document.fullscreenElement) ex.call(document).catch?.(() => {});
+ } catch (e) { /* ignore */ }
+ }, []);
+ const toggleEditorFullscreen = React.useCallback(() => {
+ if (document.fullscreenElement) exitEditorFullscreen();
+ else requestEditorFullscreen();
+ }, [requestEditorFullscreen, exitEditorFullscreen]);
+ // Следим за состоянием fullscreen (кнопка показывает актуальную иконку).
+ useEffect(() => {
+ const onFsChange = () => setIsFullscreen(!!document.fullscreenElement);
+ document.addEventListener('fullscreenchange', onFsChange);
+ document.addEventListener('webkitfullscreenchange', onFsChange);
+ return () => {
+ document.removeEventListener('fullscreenchange', onFsChange);
+ document.removeEventListener('webkitfullscreenchange', onFsChange);
+ };
+ }, []);
+ // Автовход в fullscreen при ПЕРВОМ клике/нажатии по редактору. Один раз.
+ useEffect(() => {
+ // В десктоп-приложении окно и так на весь экран — авто-FS не нужен.
+ if (IS_DESKTOP_APP) return;
+ const tryAuto = () => {
+ if (fsAutoTriedRef.current) return;
+ fsAutoTriedRef.current = true;
+ if (!document.fullscreenElement) requestEditorFullscreen();
+ };
+ window.addEventListener('pointerdown', tryAuto, { once: true, capture: true });
+ window.addEventListener('keydown', tryAuto, { once: true, capture: true });
+ return () => {
+ window.removeEventListener('pointerdown', tryAuto, { capture: true });
+ window.removeEventListener('keydown', tryAuto, { capture: true });
+ };
+ }, [requestEditorFullscreen]);
+ // При уходе со страницы редактора — выходим из fullscreen.
+ useEffect(() => () => { exitEditorFullscreen(); }, [exitEditorFullscreen]);
+
+ // === Регулируемая граница между «Объекты сцены» и «Свойства» ===
+ // Доля высоты под список объектов (0.2..0.85). Сохраняем в localStorage.
+ const [hierFraction, setHierFraction] = useState(() => {
+ const v = parseFloat(localStorage.getItem('rbxStudioHierFraction'));
+ return Number.isFinite(v) && v >= 0.2 && v <= 0.85 ? v : 0.5;
+ });
+ const rightPanelRef = useRef(null);
+ const splitDragRef = useRef(false);
+ useEffect(() => {
+ const onMove = (e) => {
+ if (!splitDragRef.current || !rightPanelRef.current) return;
+ const rect = rightPanelRef.current.getBoundingClientRect();
+ // Доля от верха панели до курсора (за вычетом верхнего заголовка ~24px).
+ let f = (e.clientY - rect.top - 24) / Math.max(1, rect.height - 24);
+ f = Math.max(0.2, Math.min(0.85, f));
+ setHierFraction(f);
+ };
+ const onUp = () => {
+ if (!splitDragRef.current) return;
+ splitDragRef.current = false;
+ document.body.style.cursor = '';
+ try { localStorage.setItem('rbxStudioHierFraction', String(hierFraction)); } catch (_) {}
+ };
+ window.addEventListener('pointermove', onMove);
+ window.addEventListener('pointerup', onUp);
+ return () => {
+ window.removeEventListener('pointermove', onMove);
+ window.removeEventListener('pointerup', onUp);
+ };
+ }, [hierFraction]);
+ const startSplitDrag = React.useCallback((e) => {
+ e.preventDefault();
+ splitDragRef.current = true;
+ document.body.style.cursor = 'row-resize';
+ }, []);
// Team Create — клиент совместного редактирования + presence-overlay.
const collabRef = useRef(null);
const collabOverlayRef = useRef(null);
@@ -2146,7 +2243,9 @@ const KubikonEditor = () => {
// fullscreen — иначе Ctrl+W/Ctrl+D случайно закрывают вкладку
// в режиме игры. Это user gesture (клик по кнопке Play),
// поэтому requestFullscreen() разрешён.
- try {
+ // В десктоп-приложении этого риска нет (нет вкладок браузера) —
+ // окно и так на весь экран, FS не нужен.
+ if (!IS_DESKTOP_APP) try {
const root = document.documentElement;
const req = root.requestFullscreen
|| root.webkitRequestFullscreen
@@ -2309,6 +2408,18 @@ const KubikonEditor = () => {
>
)}
+ {/* Полноэкранный режим — съедает верхнюю панель браузера.
+ В десктоп-приложении не нужен (окно и так на весь экран). */}
+ {!IS_DESKTOP_APP && (
+
+ )}
{/* Кнопка баг-репорта в шапке — кликает по скрытой плавающей. */}