fix(ui): hide Electron menu bar and polish editor chrome

Drop the system View menu across windows, show the open project name above Game properties, and keep scene list kebab visible with truncated titles and overflow tooltips.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-08-03 09:21:26 +08:00
parent 41b112159f
commit 61446dacfc
5 changed files with 47 additions and 32 deletions
+1 -24
View File
@@ -143,32 +143,9 @@ if (!gotTheLock) {
const projectStore = new ZipProjectStore(); const projectStore = new ZipProjectStore();
/** Без меню Electron не вешает горячие клавиши DevTools (Ctrl+Shift+I / F12). */ /** Системная полоса меню Electron отключена во всех режимах (в т.ч. без пункта «Вид»). */
function wantsDevToolsMenu(): boolean {
return (
process.env.NODE_ENV === 'development' ||
Boolean(process.env.VITE_DEV_SERVER_URL) ||
process.env.DND_OPEN_DEVTOOLS === '1'
);
}
function installAppMenuForSession(): void { function installAppMenuForSession(): void {
if (!wantsDevToolsMenu()) {
Menu.setApplicationMenu(null); Menu.setApplicationMenu(null);
return;
}
const template: Electron.MenuItemConstructorOptions[] = [];
if (process.platform === 'darwin') {
template.push({
label: app.name,
submenu: [{ role: 'about' }, { type: 'separator' }, { role: 'quit' }],
});
}
template.push({
label: 'Вид',
submenu: [{ role: 'reload' }, { role: 'forceReload' }, { type: 'separator' }, { role: 'toggleDevTools' }],
});
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
} }
const effectsStore = new EffectsStore(); const effectsStore = new EffectsStore();
+1 -5
View File
@@ -208,11 +208,11 @@ function createWindow(kind: WindowKind, opts?: CreateWindowOpts): BrowserWindow
const win = new BrowserWindow({ const win = new BrowserWindow({
width: size.width, width: size.width,
height: size.height, height: size.height,
autoHideMenuBar: true,
...(kind === 'sceneDescription' ...(kind === 'sceneDescription'
? { ? {
minWidth: 520, minWidth: 520,
minHeight: 420, minHeight: 420,
autoHideMenuBar: true,
} }
: {}), : {}),
...(kind === 'materials' ...(kind === 'materials'
@@ -222,7 +222,6 @@ function createWindow(kind: WindowKind, opts?: CreateWindowOpts): BrowserWindow
minWidth: 260, minWidth: 260,
maxWidth: 360, maxWidth: 360,
minHeight: 480, minHeight: 480,
autoHideMenuBar: true,
} }
: {}), : {}),
...(kind === 'npcsEditor' ...(kind === 'npcsEditor'
@@ -231,7 +230,6 @@ function createWindow(kind: WindowKind, opts?: CreateWindowOpts): BrowserWindow
height: NPCS_EDITOR_WINDOW_HEIGHT, height: NPCS_EDITOR_WINDOW_HEIGHT,
minWidth: 1100, minWidth: 1100,
minHeight: 640, minHeight: 640,
autoHideMenuBar: true,
} }
: {}), : {}),
...(kind === 'sceneEditor' ...(kind === 'sceneEditor'
@@ -240,7 +238,6 @@ function createWindow(kind: WindowKind, opts?: CreateWindowOpts): BrowserWindow
height: 800, height: 800,
minWidth: 960, minWidth: 960,
minHeight: 600, minHeight: 600,
autoHideMenuBar: true,
} }
: {}), : {}),
...(kind === 'npcs' ...(kind === 'npcs'
@@ -250,7 +247,6 @@ function createWindow(kind: WindowKind, opts?: CreateWindowOpts): BrowserWindow
minWidth: 560, minWidth: 560,
maxWidth: 900, maxWidth: 900,
minHeight: 480, minHeight: 480,
autoHideMenuBar: true,
} }
: {}), : {}),
show: false, show: false,
+21 -1
View File
@@ -265,6 +265,18 @@
flex-shrink: 0; flex-shrink: 0;
} }
.projectNameLabel {
font-weight: 700;
font-size: 13px;
letter-spacing: 0.02em;
margin-bottom: 10px;
color: var(--text1);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex-shrink: 0;
}
.inspectorScroll { .inspectorScroll {
flex: 1; flex: 1;
min-height: 0; min-height: 0;
@@ -1076,6 +1088,7 @@
border: 1px solid transparent; border: 1px solid transparent;
box-sizing: border-box; box-sizing: border-box;
background: transparent; background: transparent;
min-width: 0;
} }
.sceneCard:not(.sceneCardActive):hover { .sceneCard:not(.sceneCardActive):hover {
@@ -1163,21 +1176,23 @@
padding: 10px; padding: 10px;
display: grid; display: grid;
gap: 6px; gap: 6px;
min-width: 0;
} }
.sceneCardHeader { .sceneCardHeader {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
min-width: 0;
} }
.badgeCurrent { .badgeCurrent {
font-size: var(--text-xs); font-size: var(--text-xs);
color: var(--accent2); color: var(--accent2);
flex-shrink: 0;
} }
.sceneMenuBtn { .sceneMenuBtn {
margin-left: auto;
border: none; border: none;
background: var(--panel2); background: var(--panel2);
border-radius: var(--radius-xs); border-radius: var(--radius-xs);
@@ -1191,6 +1206,11 @@
.sceneCardTitle { .sceneCardTitle {
font-weight: 750; font-weight: 750;
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
.menuBackdrop { .menuBackdrop {
+21 -1
View File
@@ -1018,6 +1018,9 @@ export function EditorApp() {
<div className={styles.inspectorScroll}> <div className={styles.inspectorScroll}>
{state.project ? ( {state.project ? (
<> <>
<div className={styles.projectNameLabel} title={state.project.meta.name}>
{t('scenes.projectLabel', { name: state.project.meta.name })}
</div>
<div className={styles.inspectorTitle}>{t('scenes.inspectorGame')}</div> <div className={styles.inspectorTitle}>{t('scenes.inspectorGame')}</div>
<CampaignInspector <CampaignInspector
audioRefs={campaignAudioRefs} audioRefs={campaignAudioRefs}
@@ -2759,6 +2762,8 @@ function SceneListCard({
const previewUrl = useAssetUrl(scene.previewAssetId); const previewUrl = useAssetUrl(scene.previewAssetId);
const [menu, setMenu] = useState<{ x: number; y: number } | null>(null); const [menu, setMenu] = useState<{ x: number; y: number } | null>(null);
const [pendingDelete, setPendingDelete] = useState(false); const [pendingDelete, setPendingDelete] = useState(false);
const titleRef = useRef<HTMLDivElement | null>(null);
const [titleTooltip, setTitleTooltip] = useState<string | undefined>(undefined);
useEffect(() => { useEffect(() => {
if (!menu) return; if (!menu) return;
@@ -2875,6 +2880,22 @@ function SceneListCard({
<div className={styles.sceneCardBody}> <div className={styles.sceneCardBody}>
<div className={styles.sceneCardHeader}> <div className={styles.sceneCardHeader}>
{scene.active ? <div className={styles.badgeCurrent}>{t('sceneCard.current')}</div> : null} {scene.active ? <div className={styles.badgeCurrent}>{t('sceneCard.current')}</div> : null}
<div
ref={titleRef}
className={styles.sceneCardTitle}
title={titleTooltip}
onMouseEnter={() => {
const el = titleRef.current;
if (!el) {
setTitleTooltip(undefined);
return;
}
setTitleTooltip(el.scrollWidth > el.clientWidth + 1 ? scene.title : undefined);
}}
onMouseLeave={() => setTitleTooltip(undefined)}
>
{scene.title}
</div>
<button <button
type="button" type="button"
aria-label={t('sceneCard.menu')} aria-label={t('sceneCard.menu')}
@@ -2888,7 +2909,6 @@ function SceneListCard({
</button> </button>
</div> </div>
<div className={styles.sceneCardTitle}>{scene.title}</div>
</div> </div>
{menu && menuPos {menu && menuPos
? createPortal( ? createPortal(
@@ -282,6 +282,7 @@ export const EDITOR_MESSAGES: Record<EditorLocale, Record<string, string>> = {
'scenes.dropSkippedNoPath': 'не удалось получить путь к файлу', 'scenes.dropSkippedNoPath': 'не удалось получить путь к файлу',
'scenes.inspectorGame': 'Свойства игры', 'scenes.inspectorGame': 'Свойства игры',
'scenes.inspectorScene': 'Свойства сцены', 'scenes.inspectorScene': 'Свойства сцены',
'scenes.projectLabel': 'Проект: {name}',
'scenes.selectHint': 'Выберите сцену слева, чтобы редактировать её свойства.', 'scenes.selectHint': 'Выберите сцену слева, чтобы редактировать её свойства.',
'scenes.openProjectHint': 'Откройте проект, чтобы редактировать кампанию и сцены.', 'scenes.openProjectHint': 'Откройте проект, чтобы редактировать кампанию и сцены.',
@@ -851,6 +852,7 @@ export const EDITOR_MESSAGES: Record<EditorLocale, Record<string, string>> = {
'scenes.dropSkippedNoPath': 'could not resolve file path', 'scenes.dropSkippedNoPath': 'could not resolve file path',
'scenes.inspectorGame': 'Game properties', 'scenes.inspectorGame': 'Game properties',
'scenes.inspectorScene': 'Scene properties', 'scenes.inspectorScene': 'Scene properties',
'scenes.projectLabel': 'Project: {name}',
'scenes.selectHint': 'Select a scene on the left to edit its properties.', 'scenes.selectHint': 'Select a scene on the left to edit its properties.',
'scenes.openProjectHint': 'Open a project to edit the campaign and scenes.', 'scenes.openProjectHint': 'Open a project to edit the campaign and scenes.',