From 7f65cd65bf3d89bf793ac0367918af79cfc4be99 Mon Sep 17 00:00:00 2001 From: David Montero Date: Fri, 3 Jul 2026 06:50:21 +0200 Subject: [PATCH] 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. --- .../src/components/editor/EditorToolbar.css | 23 +-- .../src/components/editor/EditorToolbar.tsx | 12 +- frontend/src/components/editor/FileTabs.css | 161 ------------------ frontend/src/components/editor/FileTabs.tsx | 95 ----------- frontend/src/pages/EditorPage.tsx | 3 - 5 files changed, 9 insertions(+), 285 deletions(-) delete mode 100644 frontend/src/components/editor/FileTabs.css delete mode 100644 frontend/src/components/editor/FileTabs.tsx diff --git a/frontend/src/components/editor/EditorToolbar.css b/frontend/src/components/editor/EditorToolbar.css index 9f8dd7fa..b9f0c479 100644 --- a/frontend/src/components/editor/EditorToolbar.css +++ b/frontend/src/components/editor/EditorToolbar.css @@ -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 { diff --git a/frontend/src/components/editor/EditorToolbar.tsx b/frontend/src/components/editor/EditorToolbar.tsx index d9a2a623..3af4affe 100644 --- a/frontend/src/components/editor/EditorToolbar.tsx +++ b/frontend/src/components/editor/EditorToolbar.tsx @@ -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 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 = ({ )} - {/* Center slot — file tabs share the row so action icons stay pinned. */} - {centerSlot &&
{centerSlot}
} + {/* 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. */} +
{centerSlot}
{/* Hidden file input for project import. Accepts both .vlx diff --git a/frontend/src/components/editor/FileTabs.css b/frontend/src/components/editor/FileTabs.css deleted file mode 100644 index 2103108b..00000000 --- a/frontend/src/components/editor/FileTabs.css +++ /dev/null @@ -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; -} diff --git a/frontend/src/components/editor/FileTabs.tsx b/frontend/src/components/editor/FileTabs.tsx deleted file mode 100644 index 6040b250..00000000 --- a/frontend/src/components/editor/FileTabs.tsx +++ /dev/null @@ -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(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)?.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 ( - <> -
- {ownerLabel && ( - - {ownerLabel} - - )} - {openFiles.map((file) => ( -
setActiveFile(file.id)} - title={file.name} - > - {file.modified && ( - - )} - {file.name} - -
- ))} -
- - {confirmCloseId && ( -
setConfirmCloseId(null)}> -
e.stopPropagation()}> -

{t('editor.fileTabs.confirmClose')}

-
- - -
-
-
- )} - - ); -}; diff --git a/frontend/src/pages/EditorPage.tsx b/frontend/src/pages/EditorPage.tsx index df65f8a7..13b3bffe 100644 --- a/frontend/src/pages/EditorPage.tsx +++ b/frontend/src/pages/EditorPage.tsx @@ -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 ? : null} />
@@ -572,7 +570,6 @@ export const EditorPage: React.FC = () => { setConsoleOpen={setConsoleOpen} compileLogs={compileLogs} setCompileLogs={setCompileLogs} - centerSlot={!isLinuxPi ? : null} />