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:
parent
1ad669fae9
commit
7f65cd65bf
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue