refactor(editor): remove redundant file-tabs bar from the toolbar

The file-tabs strip in the toolbar center duplicated affordances that
already exist elsewhere: the file it showed is selected in the left file
explorer, and its board-owner label duplicated the board selector combo.
It also ate horizontal space and crowded the action row on narrow panes.
Remove the FileTabs component entirely; the left explorer is now the single
place to switch files. The toolbar center slot stays as an empty flex
spacer so the right action group remains pinned to the far right.
This commit is contained in:
David Montero 2026-07-03 06:50:21 +02:00
parent 1ad669fae9
commit 7f65cd65bf
5 changed files with 9 additions and 285 deletions

View File

@ -37,8 +37,8 @@
gap: 4px;
}
/* Center slot fills the gap between left/right groups, allows file tabs
to scroll horizontally inside it without pushing the icons off-screen. */
/* Center slot a flexible spacer between the left and right action groups
that keeps the right icons pinned to the far right. Normally empty. */
.toolbar-center-slot {
flex: 1 1 auto;
min-width: 0;
@ -46,25 +46,6 @@
align-items: stretch;
overflow: hidden;
}
.toolbar-center-slot > .file-tabs {
flex: 1;
min-width: 0;
border-bottom: none;
background: transparent;
min-height: 0;
height: 100%;
}
.toolbar-center-slot .file-tab {
height: 100%;
padding: 0 8px;
min-width: 70px;
max-width: 160px;
font-size: 11.5px;
}
.toolbar-center-slot .file-tab-active {
background: #1e1e1e;
border-top: 2px solid var(--color-action-primary);
}
/* ── Divider ─────────────────────────────────────── */
.tb-divider {

View File

@ -98,9 +98,9 @@ interface EditorToolbarProps {
setCompileLogs: (logs: CompilationLog[] | ((prev: CompilationLog[]) => CompilationLog[])) => void;
/**
* Optional element rendered between the left action group and the right
* action group. The editor passes <FileTabs /> here so the tabs share the
* same row as the toolbar keeping every action icon pinned and visible
* regardless of how narrow the editor pane gets.
* action group. Normally empty (the slot just acts as a flexible spacer
* that keeps the right action icons pinned); private overlays may inject
* deployment-specific content here without forking the toolbar.
*/
centerSlot?: React.ReactNode;
/**
@ -1671,8 +1671,10 @@ export const EditorToolbar = ({
)}
</div>
{/* Center slot — file tabs share the row so action icons stay pinned. */}
{centerSlot && <div className="toolbar-center-slot">{centerSlot}</div>}
{/* Center slot a flexible spacer that keeps the right action group
pinned to the far right. Rendered unconditionally so the layout
holds even when no overlay supplies content here. */}
<div className="toolbar-center-slot">{centerSlot}</div>
<div className="toolbar-group toolbar-group-right">
{/* Hidden file input for project import. Accepts both .vlx

View File

@ -1,161 +0,0 @@
.file-tabs {
display: flex;
align-items: flex-end;
background: #1e1e1e;
border-bottom: 1px solid #333;
overflow-x: auto;
flex-shrink: 0;
min-height: 35px;
scrollbar-width: thin;
scrollbar-color: #3c3c3c transparent;
}
/* Owner badge names the board/chip whose files are shown, so the user knows
whose code these tabs belong to. */
.file-tabs-owner {
display: flex;
align-items: center;
padding: 0 10px;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.4px;
text-transform: uppercase;
color: #9aa0a6;
background: #181818;
border-right: 1px solid #333;
white-space: nowrap;
flex-shrink: 0;
}
.file-tab {
display: flex;
align-items: center;
gap: 5px;
padding: 0 10px;
height: 35px;
min-width: 90px;
max-width: 180px;
cursor: pointer;
background: #2d2d2d;
border-right: 1px solid #2a2a2a;
color: #9d9d9d;
font-size: 12px;
font-family: 'Segoe UI', sans-serif;
user-select: none;
flex-shrink: 0;
white-space: nowrap;
overflow: hidden;
transition:
background 0.1s,
color 0.1s;
}
.file-tab:hover {
background: #2a2d2e;
color: #ccc;
}
.file-tab-active {
background: #1e1e1e;
color: #fff;
border-top: 2px solid var(--color-action-primary);
}
.file-tab-modified {
width: 7px;
height: 7px;
border-radius: 50%;
background: #e8a87c;
flex-shrink: 0;
}
.file-tab-name {
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
}
.file-tab-close {
background: none;
border: none;
color: transparent;
font-size: 16px;
line-height: 1;
cursor: pointer;
padding: 0;
border-radius: 3px;
flex-shrink: 0;
width: 18px;
height: 18px;
display: flex;
align-items: center;
justify-content: center;
transition:
background 0.1s,
color 0.1s;
}
.file-tab:hover .file-tab-close,
.file-tab-active .file-tab-close {
color: #666;
}
.file-tab-close:hover {
background: rgba(255, 255, 255, 0.12) !important;
color: #fff !important;
}
/* Confirm dialog */
.ftabs-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.ftabs-confirm-box {
background: #252526;
border: 1px solid #3c3c3c;
border-radius: 6px;
padding: 20px 24px;
color: #ccc;
font-size: 14px;
min-width: 290px;
}
.ftabs-confirm-box p {
margin: 0 0 16px;
}
.ftabs-confirm-actions {
display: flex;
gap: 8px;
justify-content: flex-end;
}
.ftabs-confirm-actions button {
padding: 6px 14px;
border-radius: 4px;
border: 1px solid #3c3c3c;
cursor: pointer;
font-size: 13px;
background: #2a2d2e;
color: #ccc;
}
.ftabs-confirm-actions button:hover {
background: #333;
}
.ftabs-btn-close {
background: #c62828 !important;
border-color: #c62828 !important;
color: #fff !important;
}
.ftabs-btn-close:hover {
background: #d32f2f !important;
}

View File

@ -1,95 +0,0 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useEditorStore, CHIP_GROUP_PREFIX } from '../../store/useEditorStore';
import { useSimulatorStore } from '../../store/useSimulatorStore';
import { boardDisplayName } from '../../types/board';
import './FileTabs.css';
export const FileTabs: React.FC = () => {
const { t } = useTranslation();
const { files, openFileIds, activeFileId, activeGroupId, setActiveFile, closeFile } =
useEditorStore();
const [confirmCloseId, setConfirmCloseId] = useState<string | null>(null);
const openFiles = openFileIds
.map((id) => files.find((f) => f.id === id))
.filter(Boolean) as typeof files;
// Which target owns the files currently shown — a board or a custom chip —
// so the user can tell whose code they're editing at a glance. Resolved as a
// SELECTOR returning just the label string, so FileTabs only re-renders when
// that label changes — not on every pin toggle that mutates the components
// array during simulation.
const ownerLabel = useSimulatorStore((s): string | null => {
if (activeGroupId?.startsWith(CHIP_GROUP_PREFIX)) {
const chipId = activeGroupId.slice(CHIP_GROUP_PREFIX.length);
const chip = s.components.find((c) => c.id === chipId);
return (
String((chip?.properties as Record<string, unknown>)?.chipName ?? '').trim() ||
'Custom Chip'
);
}
const board = s.boards.find((b) => b.activeFileGroupId === activeGroupId);
return board ? boardDisplayName(board) : null;
});
const handleCloseClick = (e: React.MouseEvent, fileId: string, modified: boolean) => {
e.stopPropagation();
if (modified) {
setConfirmCloseId(fileId);
} else {
closeFile(fileId);
}
};
const confirmClose = () => {
if (confirmCloseId) closeFile(confirmCloseId);
setConfirmCloseId(null);
};
return (
<>
<div className="file-tabs">
{ownerLabel && (
<span className="file-tabs-owner" title={`These files belong to ${ownerLabel}`}>
{ownerLabel}
</span>
)}
{openFiles.map((file) => (
<div
key={file.id}
className={`file-tab${file.id === activeFileId ? ' file-tab-active' : ''}`}
onClick={() => setActiveFile(file.id)}
title={file.name}
>
{file.modified && (
<span className="file-tab-modified" title={t('editor.fileTabs.unsavedChanges')} />
)}
<span className="file-tab-name">{file.name}</span>
<button
className="file-tab-close"
onClick={(e) => handleCloseClick(e, file.id, file.modified)}
title={t('editor.fileTabs.close')}
>
×
</button>
</div>
))}
</div>
{confirmCloseId && (
<div className="ftabs-overlay" onClick={() => setConfirmCloseId(null)}>
<div className="ftabs-confirm-box" onClick={(e) => e.stopPropagation()}>
<p>{t('editor.fileTabs.confirmClose')}</p>
<div className="ftabs-confirm-actions">
<button className="ftabs-btn-close" onClick={confirmClose}>
{t('editor.fileTabs.closeAnyway')}
</button>
<button onClick={() => setConfirmCloseId(null)}>{t('editor.fileTabs.cancel')}</button>
</div>
</div>
</div>
)}
</>
);
};

View File

@ -8,7 +8,6 @@ import { startSimulation } from '../simulation/spice/start';
import { useSEO } from '../utils/useSEO';
import { CodeEditor } from '../components/editor/CodeEditor';
import { EditorToolbar } from '../components/editor/EditorToolbar';
import { FileTabs } from '../components/editor/FileTabs';
import { FileExplorer } from '../components/editor/FileExplorer';
// Lazy-load Pi workspace so xterm.js isn't in the main bundle
@ -491,7 +490,6 @@ export const EditorPage: React.FC = () => {
setConsoleOpen={setConsoleOpen}
compileLogs={compileLogs}
setCompileLogs={setCompileLogs}
centerSlot={!isLinuxPi ? <FileTabs /> : null}
/>
</div>
<div className="unified-toolbar-canvas" ref={setCanvasHeaderSlot} />
@ -572,7 +570,6 @@ export const EditorPage: React.FC = () => {
setConsoleOpen={setConsoleOpen}
compileLogs={compileLogs}
setCompileLogs={setCompileLogs}
centerSlot={!isLinuxPi ? <FileTabs /> : null}
/>
</div>
</div>