feat(pi-family): guestSetup seam + board-branded workspace texts
Overlay QEMU-Linux boards are not Raspberry Pis: ProBoardDef.guestSetup lets a board send one shell line at the boot prompt (hostname/PS1/clear) to de-brand the generic image, sent before piBooted flips so uploads cannot interleave; the workspace start button and power-on title carry the board's own label for non raspberry-pi kinds; the compile console line uses the board label instead of 'Raspberry Pi 3B'.
This commit is contained in:
parent
7bb54e8d32
commit
55dd25eeba
|
|
@ -502,9 +502,9 @@ export const EditorToolbar = ({
|
||||||
const blog = (type: CompilationLog['type'], message: string) =>
|
const blog = (type: CompilationLog['type'], message: string) =>
|
||||||
addLog({ timestamp: new Date(), type, message, target: boardTarget });
|
addLog({ timestamp: new Date(), type, message, target: boardTarget });
|
||||||
|
|
||||||
// Raspberry Pi 3B doesn't need arduino-cli compilation
|
// QEMU-Linux boards don't need arduino-cli compilation
|
||||||
if (isPiBoardKind(kind)) {
|
if (isPiBoardKind(kind)) {
|
||||||
blog('info', 'Raspberry Pi 3B: no compilation needed — run Python scripts directly.');
|
blog('info', `${boardLabel}: no compilation needed — run Python scripts directly.`);
|
||||||
setMessage({ type: 'success', text: 'Ready (no compilation needed)' });
|
setMessage({ type: 'success', text: 'Ready (no compilation needed)' });
|
||||||
setCompiling(false);
|
setCompiling(false);
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,15 @@ export const RaspberryPiWorkspace: React.FC<RaspberryPiWorkspaceProps> = ({ boar
|
||||||
// Display the active board's real name (Pi 3B / 4B / 5 / Zero / …) instead of
|
// Display the active board's real name (Pi 3B / 4B / 5 / Zero / …) instead of
|
||||||
// a hardcoded "Raspberry Pi 3B" — the workspace serves the whole Pi family.
|
// a hardcoded "Raspberry Pi 3B" — the workspace serves the whole Pi family.
|
||||||
const boardLabel = board ? boardDisplayName(board) : 'Raspberry Pi';
|
const boardLabel = board ? boardDisplayName(board) : 'Raspberry Pi';
|
||||||
|
// Overlay QEMU-Linux boards (piFamily kinds) are not Raspberry Pis — their
|
||||||
|
// start button / power title must carry the board's own name.
|
||||||
|
const isRaspberry = !board || board.boardKind.startsWith('raspberry-pi');
|
||||||
|
const startLabel = isRaspberry
|
||||||
|
? t('editor.pi.startPi')
|
||||||
|
: t('editor.pi.startBoard', { board: boardLabel, defaultValue: 'Start {{board}}' });
|
||||||
|
const powerOnTitle = isRaspberry
|
||||||
|
? t('editor.pi.powerOnTitle')
|
||||||
|
: t('editor.pi.powerOnBoardTitle', { board: boardLabel, defaultValue: 'Power on {{board}}' });
|
||||||
|
|
||||||
// Three display states: offline (!running) → booting (running, guest Linux
|
// Three display states: offline (!running) → booting (running, guest Linux
|
||||||
// still coming up) → ready (running + booted shell). `running` flips on click
|
// still coming up) → ready (running + booted shell). `running` flips on click
|
||||||
|
|
@ -193,9 +202,9 @@ export const RaspberryPiWorkspace: React.FC<RaspberryPiWorkspaceProps> = ({ boar
|
||||||
<button
|
<button
|
||||||
style={{ ...styles.toolbarBtn, color: '#4caf50', borderColor: '#4caf50' }}
|
style={{ ...styles.toolbarBtn, color: '#4caf50', borderColor: '#4caf50' }}
|
||||||
onClick={() => startBoard(boardId)}
|
onClick={() => startBoard(boardId)}
|
||||||
title={t('editor.pi.powerOnTitle')}
|
title={powerOnTitle}
|
||||||
>
|
>
|
||||||
<PlayIcon />{t('editor.pi.startPi')}
|
<PlayIcon />{startLabel}
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|
@ -268,7 +277,7 @@ export const RaspberryPiWorkspace: React.FC<RaspberryPiWorkspaceProps> = ({ boar
|
||||||
<div style={styles.offlineTitle}>{t('editor.pi.offlineTitle', { board: boardLabel })}</div>
|
<div style={styles.offlineTitle}>{t('editor.pi.offlineTitle', { board: boardLabel })}</div>
|
||||||
<div style={styles.offlineSubtitle}>{t('editor.pi.offlineSubtitle')}</div>
|
<div style={styles.offlineSubtitle}>{t('editor.pi.offlineSubtitle')}</div>
|
||||||
<button style={styles.startBtn} onClick={() => startBoard(boardId)}>
|
<button style={styles.startBtn} onClick={() => startBoard(boardId)}>
|
||||||
<PlayIcon />{t('editor.pi.startPi')}
|
<PlayIcon />{startLabel}
|
||||||
</button>
|
</button>
|
||||||
<div style={styles.offlineNote}>
|
<div style={styles.offlineNote}>
|
||||||
{t('editor.pi.offlineNote1')}
|
{t('editor.pi.offlineNote1')}
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,10 @@ export interface ProBoardDef {
|
||||||
* bridge (backend qemu WebSocket, VFS panel, boot terminal). The overlay
|
* bridge (backend qemu WebSocket, VFS panel, boot terminal). The overlay
|
||||||
* must also register a matching backend profile for the kind. */
|
* must also register a matching backend profile for the kind. */
|
||||||
piFamily?: boolean;
|
piFamily?: boolean;
|
||||||
|
/** One shell line sent to the guest right after it reaches the boot
|
||||||
|
* prompt (piFamily boards only). Lets a board de-brand the generic
|
||||||
|
* image, e.g. set its own hostname/PS1 and clear the stock motd. */
|
||||||
|
guestSetup?: string;
|
||||||
/** Canvas renderer. Receives the placed board's props; return a React node.
|
/** Canvas renderer. Receives the placed board's props; return a React node.
|
||||||
* When omitted, the canvas renders `<tag id=... style=absolute@x,y>`. */
|
* When omitted, the canvas renders `<tag id=... style=absolute@x,y>`. */
|
||||||
render?: (props: { id: string; x: number; y: number; running: boolean }) => React.ReactNode;
|
render?: (props: { id: string; x: number; y: number; running: boolean }) => React.ReactNode;
|
||||||
|
|
|
||||||
|
|
@ -1244,11 +1244,23 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
||||||
};
|
};
|
||||||
// Guest Linux finished booting (shell prompt reached). Flip piBooted so
|
// Guest Linux finished booting (shell prompt reached). Flip piBooted so
|
||||||
// the workspace swaps the "Booting…" overlay for the live terminal and
|
// the workspace swaps the "Booting…" overlay for the live terminal and
|
||||||
// uploads know the shell is ready.
|
// uploads know the shell is ready. Overlay boards may declare a
|
||||||
|
// guestSetup line (hostname/PS1/clear) to de-brand the generic image;
|
||||||
|
// it runs before piBooted flips so the VFS upload (gated on piBooted)
|
||||||
|
// cannot interleave with it.
|
||||||
bridge.onBooted = () => {
|
bridge.onBooted = () => {
|
||||||
set((s) => ({
|
const setup = getProBoard(boardKind)?.guestSetup;
|
||||||
boards: s.boards.map((b) => (b.id === id ? { ...b, piBooted: true } : b)),
|
const flip = () =>
|
||||||
}));
|
set((s) => ({
|
||||||
|
boards: s.boards.map((b) => (b.id === id ? { ...b, piBooted: true } : b)),
|
||||||
|
}));
|
||||||
|
if (setup) {
|
||||||
|
void bridge
|
||||||
|
.sendAndWaitForPrompt(setup.endsWith('\n') ? setup : setup + '\n')
|
||||||
|
.then(flip);
|
||||||
|
} else {
|
||||||
|
flip();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
bridge.onDisconnected = () => {
|
bridge.onDisconnected = () => {
|
||||||
set((s) => {
|
set((s) => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue