2026-03-06 20:14:50 +07:00
|
|
|
|
import React, { useState } from 'react';
|
2026-05-09 13:08:51 +07:00
|
|
|
|
import { useTranslation } from 'react-i18next';
|
feat(editor): rename boards & custom chips; show which target owns each file
Phase 2 of the run-system/UX work.
- BoardInstance gains an optional user ; boardDisplayName(board) resolver
(name || kind label) routes every INSTANCE-label surface: file-explorer
section header, compile console (EditorToolbar), canvas selector/tooltip/
context-menu, Serial Monitor tabs, Oscilloscope board picker, Board Options
subtitle. Board/component pickers keep the KIND label (they pick new boards).
- Inline rename on board AND chip section headers (double-click the name, or a
hover pencil button). Board -> updateBoard(id,{name}); chip -> chipName in
properties. Enter commits, Escape cancels (cancel-flag ref guards the
unmount-fires-onBlur footgun), empty clears to the kind / 'Custom Chip'.
- FileTabs shows an owner badge naming the board/chip whose files are shown
(resolved as a selector so it doesn't re-render on every sim pin toggle).
- CustomChipDialog no longer clobbers a user-given chipName: chip.json's name
only seeds the blank defaults (My Chip / Custom Chip); loading an example
relabels explicitly.
- Persistence: board name round-trips via projectPayload (+ dirty hash),
vlxFile, ProjectByIdPage load + loadProjectState; chipName rides components_json.
- Drive-by: fixed a pre-existing rules-of-hooks violation in BoardOptionsModal
(early return before a useCallback).
Reviewed by a 3-agent adversarial pass (completeness / persistence / correctness);
all major findings folded in.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 11:22:25 +07:00
|
|
|
|
import { useEditorStore, CHIP_GROUP_PREFIX } from '../../store/useEditorStore';
|
|
|
|
|
|
import { useSimulatorStore } from '../../store/useSimulatorStore';
|
|
|
|
|
|
import { boardDisplayName } from '../../types/board';
|
2026-03-06 20:14:50 +07:00
|
|
|
|
import './FileTabs.css';
|
|
|
|
|
|
|
|
|
|
|
|
export const FileTabs: React.FC = () => {
|
2026-05-09 13:08:51 +07:00
|
|
|
|
const { t } = useTranslation();
|
feat(editor): rename boards & custom chips; show which target owns each file
Phase 2 of the run-system/UX work.
- BoardInstance gains an optional user ; boardDisplayName(board) resolver
(name || kind label) routes every INSTANCE-label surface: file-explorer
section header, compile console (EditorToolbar), canvas selector/tooltip/
context-menu, Serial Monitor tabs, Oscilloscope board picker, Board Options
subtitle. Board/component pickers keep the KIND label (they pick new boards).
- Inline rename on board AND chip section headers (double-click the name, or a
hover pencil button). Board -> updateBoard(id,{name}); chip -> chipName in
properties. Enter commits, Escape cancels (cancel-flag ref guards the
unmount-fires-onBlur footgun), empty clears to the kind / 'Custom Chip'.
- FileTabs shows an owner badge naming the board/chip whose files are shown
(resolved as a selector so it doesn't re-render on every sim pin toggle).
- CustomChipDialog no longer clobbers a user-given chipName: chip.json's name
only seeds the blank defaults (My Chip / Custom Chip); loading an example
relabels explicitly.
- Persistence: board name round-trips via projectPayload (+ dirty hash),
vlxFile, ProjectByIdPage load + loadProjectState; chipName rides components_json.
- Drive-by: fixed a pre-existing rules-of-hooks violation in BoardOptionsModal
(early return before a useCallback).
Reviewed by a 3-agent adversarial pass (completeness / persistence / correctness);
all major findings folded in.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 11:22:25 +07:00
|
|
|
|
const { files, openFileIds, activeFileId, activeGroupId, setActiveFile, closeFile } =
|
|
|
|
|
|
useEditorStore();
|
2026-03-06 20:14:50 +07:00
|
|
|
|
const [confirmCloseId, setConfirmCloseId] = useState<string | null>(null);
|
|
|
|
|
|
|
|
|
|
|
|
const openFiles = openFileIds
|
|
|
|
|
|
.map((id) => files.find((f) => f.id === id))
|
|
|
|
|
|
.filter(Boolean) as typeof files;
|
|
|
|
|
|
|
feat(editor): rename boards & custom chips; show which target owns each file
Phase 2 of the run-system/UX work.
- BoardInstance gains an optional user ; boardDisplayName(board) resolver
(name || kind label) routes every INSTANCE-label surface: file-explorer
section header, compile console (EditorToolbar), canvas selector/tooltip/
context-menu, Serial Monitor tabs, Oscilloscope board picker, Board Options
subtitle. Board/component pickers keep the KIND label (they pick new boards).
- Inline rename on board AND chip section headers (double-click the name, or a
hover pencil button). Board -> updateBoard(id,{name}); chip -> chipName in
properties. Enter commits, Escape cancels (cancel-flag ref guards the
unmount-fires-onBlur footgun), empty clears to the kind / 'Custom Chip'.
- FileTabs shows an owner badge naming the board/chip whose files are shown
(resolved as a selector so it doesn't re-render on every sim pin toggle).
- CustomChipDialog no longer clobbers a user-given chipName: chip.json's name
only seeds the blank defaults (My Chip / Custom Chip); loading an example
relabels explicitly.
- Persistence: board name round-trips via projectPayload (+ dirty hash),
vlxFile, ProjectByIdPage load + loadProjectState; chipName rides components_json.
- Drive-by: fixed a pre-existing rules-of-hooks violation in BoardOptionsModal
(early return before a useCallback).
Reviewed by a 3-agent adversarial pass (completeness / persistence / correctness);
all major findings folded in.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 11:22:25 +07:00
|
|
|
|
// 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;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-04-22 02:45:45 +07:00
|
|
|
|
const handleCloseClick = (e: React.MouseEvent, fileId: string, modified: boolean) => {
|
2026-03-06 20:14:50 +07:00
|
|
|
|
e.stopPropagation();
|
|
|
|
|
|
if (modified) {
|
|
|
|
|
|
setConfirmCloseId(fileId);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
closeFile(fileId);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const confirmClose = () => {
|
|
|
|
|
|
if (confirmCloseId) closeFile(confirmCloseId);
|
|
|
|
|
|
setConfirmCloseId(null);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<div className="file-tabs">
|
feat(editor): rename boards & custom chips; show which target owns each file
Phase 2 of the run-system/UX work.
- BoardInstance gains an optional user ; boardDisplayName(board) resolver
(name || kind label) routes every INSTANCE-label surface: file-explorer
section header, compile console (EditorToolbar), canvas selector/tooltip/
context-menu, Serial Monitor tabs, Oscilloscope board picker, Board Options
subtitle. Board/component pickers keep the KIND label (they pick new boards).
- Inline rename on board AND chip section headers (double-click the name, or a
hover pencil button). Board -> updateBoard(id,{name}); chip -> chipName in
properties. Enter commits, Escape cancels (cancel-flag ref guards the
unmount-fires-onBlur footgun), empty clears to the kind / 'Custom Chip'.
- FileTabs shows an owner badge naming the board/chip whose files are shown
(resolved as a selector so it doesn't re-render on every sim pin toggle).
- CustomChipDialog no longer clobbers a user-given chipName: chip.json's name
only seeds the blank defaults (My Chip / Custom Chip); loading an example
relabels explicitly.
- Persistence: board name round-trips via projectPayload (+ dirty hash),
vlxFile, ProjectByIdPage load + loadProjectState; chipName rides components_json.
- Drive-by: fixed a pre-existing rules-of-hooks violation in BoardOptionsModal
(early return before a useCallback).
Reviewed by a 3-agent adversarial pass (completeness / persistence / correctness);
all major findings folded in.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 11:22:25 +07:00
|
|
|
|
{ownerLabel && (
|
|
|
|
|
|
<span className="file-tabs-owner" title={`These files belong to ${ownerLabel}`}>
|
|
|
|
|
|
{ownerLabel}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
2026-03-06 20:14:50 +07:00
|
|
|
|
{openFiles.map((file) => (
|
|
|
|
|
|
<div
|
|
|
|
|
|
key={file.id}
|
|
|
|
|
|
className={`file-tab${file.id === activeFileId ? ' file-tab-active' : ''}`}
|
|
|
|
|
|
onClick={() => setActiveFile(file.id)}
|
|
|
|
|
|
title={file.name}
|
|
|
|
|
|
>
|
2026-05-09 13:08:51 +07:00
|
|
|
|
{file.modified && (
|
|
|
|
|
|
<span className="file-tab-modified" title={t('editor.fileTabs.unsavedChanges')} />
|
|
|
|
|
|
)}
|
2026-03-06 20:14:50 +07:00
|
|
|
|
<span className="file-tab-name">{file.name}</span>
|
|
|
|
|
|
<button
|
|
|
|
|
|
className="file-tab-close"
|
|
|
|
|
|
onClick={(e) => handleCloseClick(e, file.id, file.modified)}
|
2026-05-09 13:08:51 +07:00
|
|
|
|
title={t('editor.fileTabs.close')}
|
2026-03-06 20:14:50 +07:00
|
|
|
|
>
|
|
|
|
|
|
×
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{confirmCloseId && (
|
|
|
|
|
|
<div className="ftabs-overlay" onClick={() => setConfirmCloseId(null)}>
|
2026-04-22 02:45:45 +07:00
|
|
|
|
<div className="ftabs-confirm-box" onClick={(e) => e.stopPropagation()}>
|
2026-05-09 13:08:51 +07:00
|
|
|
|
<p>{t('editor.fileTabs.confirmClose')}</p>
|
2026-03-06 20:14:50 +07:00
|
|
|
|
<div className="ftabs-confirm-actions">
|
|
|
|
|
|
<button className="ftabs-btn-close" onClick={confirmClose}>
|
2026-05-09 13:08:51 +07:00
|
|
|
|
{t('editor.fileTabs.closeAnyway')}
|
2026-03-06 20:14:50 +07:00
|
|
|
|
</button>
|
2026-05-09 13:08:51 +07:00
|
|
|
|
<button onClick={() => setConfirmCloseId(null)}>{t('editor.fileTabs.cancel')}</button>
|
2026-03-06 20:14:50 +07:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|