feat(editor): per-target compilation console — a section per board/chip
Phase 4 of the run-system work. The compile console now groups output into a
section per run target (board or chip) with a status glyph and label, the way
multiple Arduinos already stream — instead of one flat list.
- CompilationLog gains an optional target { id, label, kind: 'board'|'chip' }.
message/type are unchanged so the pro overlay (diagnose-with-AI prompt +
errorCount slot) and the console's length-based clear/auto-error heuristics
are untouched. parseCompileResult stamps the target on every produced line.
- Producers stamp their lines: compileAllBoards (per-board, dropping the old
'<label>: ' string prefix the header now carries), prepareCustomChips
(per-chip, WASM + ROM), handleCompile + handleRun MicroPython (single board) —
including the Pi / MicroPython / FQBN / error paths so a target's lines never
fragment across sections.
- CompilationConsole groups filteredLogs into consecutive-run sections at RENDER
time only (the flat array is unchanged); each target section shows ✓/✕/▸ +
name + kind tag, with no-target lines ('Compiling all targets', 'Done') as
plain narration around them.
Reviewed by an adversarial pass; the flagged un-stamped edge paths (Pi /
MicroPython / single-board errors) are now stamped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
6ae1ed560d
commit
dd322b27e3
|
|
@ -5,7 +5,37 @@
|
||||||
|
|
||||||
import React, { useRef, useEffect, useState } from 'react';
|
import React, { useRef, useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import type { CompilationLog } from '../../utils/compilationLogger';
|
import type { CompilationLog, CompileTarget } from '../../utils/compilationLogger';
|
||||||
|
|
||||||
|
/** One contiguous run of log lines for the same target (or none). */
|
||||||
|
interface LogGroup {
|
||||||
|
target?: CompileTarget;
|
||||||
|
entries: { log: CompilationLog; index: number }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Group consecutive log lines by target so the console renders a section per
|
||||||
|
* board/chip. Consecutive-run (not collapse-all) preserves chronological order
|
||||||
|
* — a leading "Compiling all targets" and a trailing "Done" stay where they
|
||||||
|
* are, around the per-target sections. */
|
||||||
|
function groupLogs(logs: CompilationLog[]): LogGroup[] {
|
||||||
|
const groups: LogGroup[] = [];
|
||||||
|
logs.forEach((log, index) => {
|
||||||
|
const last = groups[groups.length - 1];
|
||||||
|
if (last && (last.target?.id ?? null) === (log.target?.id ?? null)) {
|
||||||
|
last.entries.push({ log, index });
|
||||||
|
} else {
|
||||||
|
groups.push({ target: log.target, entries: [{ log, index }] });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A target section's overall outcome, derived from its lines. */
|
||||||
|
function groupStatus(entries: LogGroup['entries']): 'error' | 'success' | 'running' {
|
||||||
|
if (entries.some((e) => e.log.type === 'error')) return 'error';
|
||||||
|
if (entries.some((e) => e.log.type === 'success')) return 'success';
|
||||||
|
return 'running';
|
||||||
|
}
|
||||||
|
|
||||||
interface CompilationConsoleProps {
|
interface CompilationConsoleProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
|
@ -154,33 +184,65 @@ export const CompilationConsole: React.FC<CompilationConsoleProps> = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Output content */}
|
{/* Output content — grouped into a section per board/chip target. */}
|
||||||
<div ref={outputRef} style={styles.output}>
|
<div ref={outputRef} style={styles.output}>
|
||||||
{filteredLogs.length === 0 ? (
|
{filteredLogs.length === 0 ? (
|
||||||
<div style={styles.emptyState}>{t('editor.console.empty')}</div>
|
<div style={styles.emptyState}>{t('editor.console.empty')}</div>
|
||||||
) : (
|
) : (
|
||||||
filteredLogs.map((log, i) => (
|
groupLogs(filteredLogs).map((group, gi) =>
|
||||||
<div key={i} style={styles.logLine}>
|
group.target ? (
|
||||||
<span style={styles.timestamp}>
|
<div key={`g-${gi}`} style={styles.targetGroup}>
|
||||||
{log.timestamp.toLocaleTimeString('en-US', {
|
<div style={styles.targetHeader}>
|
||||||
hour12: false,
|
<span
|
||||||
hour: '2-digit',
|
style={{ ...styles.targetStatus, color: statusColor(groupStatus(group.entries)) }}
|
||||||
minute: '2-digit',
|
>
|
||||||
second: '2-digit',
|
{statusGlyph(groupStatus(group.entries))}
|
||||||
})}
|
</span>
|
||||||
</span>
|
<span style={styles.targetLabel}>{group.target.label}</span>
|
||||||
<span style={{ ...styles.logMessage, color: logColor(log.type) }}>
|
<span style={styles.targetKind}>{group.target.kind}</span>
|
||||||
{log.type === 'core-install' && <span style={styles.coreTag}>CORE </span>}
|
</div>
|
||||||
{log.message}
|
<div style={styles.targetBody}>
|
||||||
</span>
|
{group.entries.map(({ log, index }) => (
|
||||||
</div>
|
<LogLine key={index} log={log} />
|
||||||
))
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
// No target — plain narration ("Compiling all targets...", "Done").
|
||||||
|
group.entries.map(({ log, index }) => <LogLine key={index} log={log} />)
|
||||||
|
),
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const LogLine: React.FC<{ log: CompilationLog }> = ({ log }) => (
|
||||||
|
<div style={styles.logLine}>
|
||||||
|
<span style={styles.timestamp}>
|
||||||
|
{log.timestamp.toLocaleTimeString('en-US', {
|
||||||
|
hour12: false,
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
<span style={{ ...styles.logMessage, color: logColor(log.type) }}>
|
||||||
|
{log.type === 'core-install' && <span style={styles.coreTag}>CORE </span>}
|
||||||
|
{log.message}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
function statusColor(status: 'error' | 'success' | 'running'): string {
|
||||||
|
return status === 'error' ? '#ef5350' : status === 'success' ? '#66bb6a' : '#9aa0a6';
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusGlyph(status: 'error' | 'success' | 'running'): string {
|
||||||
|
return status === 'error' ? '✕' : status === 'success' ? '✓' : '▸';
|
||||||
|
}
|
||||||
|
|
||||||
function logColor(type: CompilationLog['type']): string {
|
function logColor(type: CompilationLog['type']): string {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'error':
|
case 'error':
|
||||||
|
|
@ -301,6 +363,40 @@ const styles: Record<string, React.CSSProperties> = {
|
||||||
padding: '12px 0',
|
padding: '12px 0',
|
||||||
fontFamily: 'system-ui, sans-serif',
|
fontFamily: 'system-ui, sans-serif',
|
||||||
},
|
},
|
||||||
|
targetGroup: {
|
||||||
|
marginTop: 6,
|
||||||
|
borderLeft: '2px solid #3a3a3a',
|
||||||
|
paddingLeft: 8,
|
||||||
|
},
|
||||||
|
targetHeader: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 7,
|
||||||
|
padding: '1px 0 2px',
|
||||||
|
},
|
||||||
|
targetStatus: {
|
||||||
|
fontWeight: 700,
|
||||||
|
flexShrink: 0,
|
||||||
|
},
|
||||||
|
targetLabel: {
|
||||||
|
color: '#e0e0e0',
|
||||||
|
fontWeight: 700,
|
||||||
|
fontSize: 11.5,
|
||||||
|
fontFamily: 'system-ui, sans-serif',
|
||||||
|
},
|
||||||
|
targetKind: {
|
||||||
|
color: '#777',
|
||||||
|
fontSize: 9,
|
||||||
|
fontFamily: 'system-ui, sans-serif',
|
||||||
|
textTransform: 'uppercase' as const,
|
||||||
|
letterSpacing: '0.5px',
|
||||||
|
border: '1px solid #3a3a3a',
|
||||||
|
borderRadius: 3,
|
||||||
|
padding: '0 4px',
|
||||||
|
},
|
||||||
|
targetBody: {
|
||||||
|
paddingLeft: 4,
|
||||||
|
},
|
||||||
logLine: {
|
logLine: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: 8,
|
gap: 8,
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import { useProjectStore } from '../../store/useProjectStore';
|
||||||
import { LibraryManagerModal } from '../simulator/LibraryManagerModal';
|
import { LibraryManagerModal } from '../simulator/LibraryManagerModal';
|
||||||
import { InstallLibrariesModal } from '../simulator/InstallLibrariesModal';
|
import { InstallLibrariesModal } from '../simulator/InstallLibrariesModal';
|
||||||
import { parseCompileResult } from '../../utils/compilationLogger';
|
import { parseCompileResult } from '../../utils/compilationLogger';
|
||||||
import type { CompilationLog } from '../../utils/compilationLogger';
|
import type { CompilationLog, CompileTarget } from '../../utils/compilationLogger';
|
||||||
import { exportToWokwiZip } from '../../utils/wokwiZip';
|
import { exportToWokwiZip } from '../../utils/wokwiZip';
|
||||||
import { importProjectFile, PROJECT_FILE_ACCEPT } from '../../utils/importProject';
|
import { importProjectFile, PROJECT_FILE_ACCEPT } from '../../utils/importProject';
|
||||||
import { readFirmwareFile } from '../../utils/firmwareLoader';
|
import { readFirmwareFile } from '../../utils/firmwareLoader';
|
||||||
|
|
@ -287,38 +287,33 @@ export const EditorToolbar = ({
|
||||||
const sourceC = String(props.sourceC ?? '');
|
const sourceC = String(props.sourceC ?? '');
|
||||||
const chipJson = String(props.chipJson ?? '{}');
|
const chipJson = String(props.chipJson ?? '{}');
|
||||||
let changed = false;
|
let changed = false;
|
||||||
|
// Stamp every line for this chip with its target so the console groups
|
||||||
|
// it under its own section (alongside the boards).
|
||||||
|
const chipTarget: CompileTarget = { id: chip.id, label: chipLabel, kind: 'chip' };
|
||||||
|
const clog = (type: CompilationLog['type'], message: string) =>
|
||||||
|
addLog({ timestamp: new Date(), type, message, target: chipTarget });
|
||||||
|
|
||||||
// 1. C -> WASM. Only when missing — the chip designer fills this too.
|
// 1. C -> WASM. Only when missing — the chip designer fills this too.
|
||||||
if (!String(props.wasmBase64 ?? '') && sourceC) {
|
if (!String(props.wasmBase64 ?? '') && sourceC) {
|
||||||
addLog({
|
clog('info', `Compiling chip "${chipLabel}" to WASM...`);
|
||||||
timestamp: new Date(),
|
|
||||||
type: 'info',
|
|
||||||
message: `Compiling chip "${chipLabel}" to WASM...`,
|
|
||||||
});
|
|
||||||
try {
|
try {
|
||||||
const r = await compileChip(sourceC, chipJson);
|
const r = await compileChip(sourceC, chipJson);
|
||||||
if (r.success && r.wasm_base64) {
|
if (r.success && r.wasm_base64) {
|
||||||
props.wasmBase64 = r.wasm_base64;
|
props.wasmBase64 = r.wasm_base64;
|
||||||
changed = true;
|
changed = true;
|
||||||
addLog({
|
clog('success', `Chip "${chipLabel}" compiled (${r.byte_size} B WASM).`);
|
||||||
timestamp: new Date(),
|
|
||||||
type: 'success',
|
|
||||||
message: `Chip "${chipLabel}" compiled (${r.byte_size} B WASM).`,
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
addLog({
|
clog(
|
||||||
timestamp: new Date(),
|
'error',
|
||||||
type: 'error',
|
`Chip "${chipLabel}" WASM compile failed: ${r.error || r.stderr || 'unknown error'}`,
|
||||||
message: `Chip "${chipLabel}" WASM compile failed: ${r.error || r.stderr || 'unknown error'}`,
|
);
|
||||||
});
|
|
||||||
failed++;
|
failed++;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
addLog({
|
clog(
|
||||||
timestamp: new Date(),
|
'error',
|
||||||
type: 'error',
|
`Chip "${chipLabel}" WASM compile error: ${e instanceof Error ? e.message : String(e)}`,
|
||||||
message: `Chip "${chipLabel}" WASM compile error: ${e instanceof Error ? e.message : String(e)}`,
|
);
|
||||||
});
|
|
||||||
failed++;
|
failed++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -338,45 +333,34 @@ export const EditorToolbar = ({
|
||||||
chipGroupFiles.find((f) => f.name === programFile) ??
|
chipGroupFiles.find((f) => f.name === programFile) ??
|
||||||
boardFiles.find((f) => f.name === programFile);
|
boardFiles.find((f) => f.name === programFile);
|
||||||
if (!file) {
|
if (!file) {
|
||||||
addLog({
|
clog('error', `Chip "${chipLabel}": program file "${programFile}" not found in the chip's files.`);
|
||||||
timestamp: new Date(),
|
|
||||||
type: 'error',
|
|
||||||
message: `Chip "${chipLabel}": program file "${programFile}" not found in the chip's files.`,
|
|
||||||
});
|
|
||||||
failed++;
|
failed++;
|
||||||
} else {
|
} else {
|
||||||
const target = targetForChip(chipJson);
|
const target = targetForChip(chipJson);
|
||||||
const fmt = formatForFile(programFile);
|
const fmt = formatForFile(programFile);
|
||||||
addLog({
|
clog(
|
||||||
timestamp: new Date(),
|
'info',
|
||||||
type: 'info',
|
`Assembling "${programFile}" (target=${target}, format=${fmt}) for chip "${chipLabel}"...`,
|
||||||
message: `Assembling "${programFile}" (target=${target}, format=${fmt}) for chip "${chipLabel}"...`,
|
);
|
||||||
});
|
|
||||||
try {
|
try {
|
||||||
const rr = await compileRom(file.content, target, fmt);
|
const rr = await compileRom(file.content, target, fmt);
|
||||||
if (rr.success && rr.rom_base64) {
|
if (rr.success && rr.rom_base64) {
|
||||||
props.romBytes = rr.rom_base64;
|
props.romBytes = rr.rom_base64;
|
||||||
props.programFile = programFile;
|
props.programFile = programFile;
|
||||||
changed = true;
|
changed = true;
|
||||||
addLog({
|
clog('success', `ROM ready: ${rr.byte_size} B injected into "${chipLabel}".`);
|
||||||
timestamp: new Date(),
|
|
||||||
type: 'success',
|
|
||||||
message: `ROM ready: ${rr.byte_size} B injected into "${chipLabel}".`,
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
addLog({
|
clog(
|
||||||
timestamp: new Date(),
|
'error',
|
||||||
type: 'error',
|
`ROM compile failed for "${programFile}": ${rr.error || rr.stderr || 'unknown error'}`,
|
||||||
message: `ROM compile failed for "${programFile}": ${rr.error || rr.stderr || 'unknown error'}`,
|
);
|
||||||
});
|
|
||||||
failed++;
|
failed++;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
addLog({
|
clog(
|
||||||
timestamp: new Date(),
|
'error',
|
||||||
type: 'error',
|
`ROM compile error for "${programFile}": ${e instanceof Error ? e.message : String(e)}`,
|
||||||
message: `ROM compile error for "${programFile}": ${e instanceof Error ? e.message : String(e)}`,
|
);
|
||||||
});
|
|
||||||
failed++;
|
failed++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -432,14 +416,18 @@ export const EditorToolbar = ({
|
||||||
// ── End custom-chip preparation ─────────────────────────────────────
|
// ── End custom-chip preparation ─────────────────────────────────────
|
||||||
|
|
||||||
const kind = activeBoard?.boardKind;
|
const kind = activeBoard?.boardKind;
|
||||||
|
// The active board's console target, defined up front so EVERY board path
|
||||||
|
// (Pi, MicroPython, arduino-cli, errors) groups its lines under one section.
|
||||||
|
const boardLabel = activeBoard ? boardDisplayName(activeBoard) : 'Unknown';
|
||||||
|
const boardTarget: CompileTarget | undefined = activeBoardId
|
||||||
|
? { id: activeBoardId, label: boardLabel, kind: 'board' }
|
||||||
|
: undefined;
|
||||||
|
const blog = (type: CompilationLog['type'], message: string) =>
|
||||||
|
addLog({ timestamp: new Date(), type, message, target: boardTarget });
|
||||||
|
|
||||||
// Raspberry Pi 3B doesn't need arduino-cli compilation
|
// Raspberry Pi 3B doesn't need arduino-cli compilation
|
||||||
if (isPiBoardKind(kind)) {
|
if (isPiBoardKind(kind)) {
|
||||||
addLog({
|
blog('info', 'Raspberry Pi 3B: no compilation needed — run Python scripts directly.');
|
||||||
timestamp: new Date(),
|
|
||||||
type: 'info',
|
|
||||||
message: 'Raspberry Pi 3B: 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;
|
||||||
|
|
@ -447,24 +435,16 @@ export const EditorToolbar = ({
|
||||||
|
|
||||||
// MicroPython mode — no backend compilation needed
|
// MicroPython mode — no backend compilation needed
|
||||||
if (activeBoard?.languageMode === 'micropython' && activeBoardId) {
|
if (activeBoard?.languageMode === 'micropython' && activeBoardId) {
|
||||||
addLog({
|
blog('info', 'MicroPython: loading firmware and user files...');
|
||||||
timestamp: new Date(),
|
|
||||||
type: 'info',
|
|
||||||
message: 'MicroPython: loading firmware and user files...',
|
|
||||||
});
|
|
||||||
try {
|
try {
|
||||||
const groupFiles = useEditorStore.getState().getGroupFiles(activeBoard.activeFileGroupId);
|
const groupFiles = useEditorStore.getState().getGroupFiles(activeBoard.activeFileGroupId);
|
||||||
const pyFiles = groupFiles.map((f) => ({ name: f.name, content: f.content }));
|
const pyFiles = groupFiles.map((f) => ({ name: f.name, content: f.content }));
|
||||||
await loadMicroPythonProgram(activeBoardId, pyFiles);
|
await loadMicroPythonProgram(activeBoardId, pyFiles);
|
||||||
addLog({
|
blog('success', 'MicroPython firmware loaded successfully');
|
||||||
timestamp: new Date(),
|
|
||||||
type: 'success',
|
|
||||||
message: 'MicroPython firmware loaded successfully',
|
|
||||||
});
|
|
||||||
setMessage({ type: 'success', text: 'MicroPython ready' });
|
setMessage({ type: 'success', text: 'MicroPython ready' });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errMsg = err instanceof Error ? err.message : 'Failed to load MicroPython';
|
const errMsg = err instanceof Error ? err.message : 'Failed to load MicroPython';
|
||||||
addLog({ timestamp: new Date(), type: 'error', message: errMsg });
|
blog('error', errMsg);
|
||||||
setMessage({ type: 'error', text: errMsg });
|
setMessage({ type: 'error', text: errMsg });
|
||||||
} finally {
|
} finally {
|
||||||
setCompiling(false);
|
setCompiling(false);
|
||||||
|
|
@ -473,20 +453,15 @@ export const EditorToolbar = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
const fqbn = kind ? BOARD_KIND_FQBN[kind] : null;
|
const fqbn = kind ? BOARD_KIND_FQBN[kind] : null;
|
||||||
const boardLabel = activeBoard ? boardDisplayName(activeBoard) : 'Unknown';
|
|
||||||
|
|
||||||
if (!fqbn) {
|
if (!fqbn) {
|
||||||
addLog({ timestamp: new Date(), type: 'error', message: `No FQBN for board kind: ${kind}` });
|
blog('error', `No FQBN for board kind: ${kind}`);
|
||||||
setMessage({ type: 'error', text: 'Unknown board' });
|
setMessage({ type: 'error', text: 'Unknown board' });
|
||||||
setCompiling(false);
|
setCompiling(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
addLog({
|
blog('info', `Starting compilation for ${boardLabel} (${fqbn})...`);
|
||||||
timestamp: new Date(),
|
|
||||||
type: 'info',
|
|
||||||
message: `Starting compilation for ${boardLabel} (${fqbn})...`,
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const groupFiles = activeBoard?.activeFileGroupId
|
const groupFiles = activeBoard?.activeFileGroupId
|
||||||
|
|
@ -524,6 +499,7 @@ export const EditorToolbar = ({
|
||||||
timestamp: now,
|
timestamp: now,
|
||||||
type: 'info' as const,
|
type: 'info' as const,
|
||||||
message: line,
|
message: line,
|
||||||
|
target: boardTarget,
|
||||||
})),
|
})),
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
@ -539,7 +515,7 @@ export const EditorToolbar = ({
|
||||||
// the live stream — parseCompileResult highlights FAILED blocks and
|
// the live stream — parseCompileResult highlights FAILED blocks and
|
||||||
// tags compiler errors with type='error', which the console uses for
|
// tags compiler errors with type='error', which the console uses for
|
||||||
// colour + the auto-switch-to-errors filter.
|
// colour + the auto-switch-to-errors filter.
|
||||||
const resultLogs = parseCompileResult(result, boardLabel);
|
const resultLogs = parseCompileResult(result, boardLabel, boardTarget);
|
||||||
setCompileLogs((prev: CompilationLog[]) => [...prev, ...resultLogs]);
|
setCompileLogs((prev: CompilationLog[]) => [...prev, ...resultLogs]);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
|
@ -571,7 +547,7 @@ export const EditorToolbar = ({
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errMsg = err instanceof Error ? err.message : 'Compile failed';
|
const errMsg = err instanceof Error ? err.message : 'Compile failed';
|
||||||
addLog({ timestamp: new Date(), type: 'error', message: errMsg });
|
blog('error', errMsg);
|
||||||
setMessage({ type: 'error', text: errMsg });
|
setMessage({ type: 'error', text: errMsg });
|
||||||
} finally {
|
} finally {
|
||||||
setCompiling(false);
|
setCompiling(false);
|
||||||
|
|
@ -747,23 +723,22 @@ export const EditorToolbar = ({
|
||||||
|
|
||||||
setCompiling(true);
|
setCompiling(true);
|
||||||
setMessage(null);
|
setMessage(null);
|
||||||
addLog({
|
const mpyTarget: CompileTarget = {
|
||||||
timestamp: new Date(),
|
id: activeBoardId,
|
||||||
type: 'info',
|
label: boardDisplayName(board),
|
||||||
message: 'MicroPython: loading firmware and user files...',
|
kind: 'board',
|
||||||
});
|
};
|
||||||
|
const mlog = (type: CompilationLog['type'], message: string) =>
|
||||||
|
addLog({ timestamp: new Date(), type, message, target: mpyTarget });
|
||||||
|
mlog('info', 'MicroPython: loading firmware and user files...');
|
||||||
try {
|
try {
|
||||||
const groupFiles = useEditorStore.getState().getGroupFiles(board.activeFileGroupId);
|
const groupFiles = useEditorStore.getState().getGroupFiles(board.activeFileGroupId);
|
||||||
const pyFiles = groupFiles.map((f) => ({ name: f.name, content: f.content }));
|
const pyFiles = groupFiles.map((f) => ({ name: f.name, content: f.content }));
|
||||||
await loadMicroPythonProgram(activeBoardId, pyFiles);
|
await loadMicroPythonProgram(activeBoardId, pyFiles);
|
||||||
addLog({
|
mlog('success', 'MicroPython firmware loaded');
|
||||||
timestamp: new Date(),
|
|
||||||
type: 'success',
|
|
||||||
message: 'MicroPython firmware loaded',
|
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errMsg = err instanceof Error ? err.message : 'Failed to load MicroPython';
|
const errMsg = err instanceof Error ? err.message : 'Failed to load MicroPython';
|
||||||
addLog({ timestamp: new Date(), type: 'error', message: errMsg });
|
mlog('error', errMsg);
|
||||||
setMessage({ type: 'error', text: errMsg });
|
setMessage({ type: 'error', text: errMsg });
|
||||||
setCompiling(false);
|
setCompiling(false);
|
||||||
return;
|
return;
|
||||||
|
|
@ -955,29 +930,25 @@ export const EditorToolbar = ({
|
||||||
|
|
||||||
for (const board of boardsList) {
|
for (const board of boardsList) {
|
||||||
const label = boardDisplayName(board);
|
const label = boardDisplayName(board);
|
||||||
|
// Stamp this board's lines so the console groups them under its section.
|
||||||
|
const boardTarget: CompileTarget = { id: board.id, label, kind: 'board' };
|
||||||
|
const blog = (type: CompilationLog['type'], message: string) =>
|
||||||
|
addLog({ timestamp: new Date(), type, message, target: boardTarget });
|
||||||
|
|
||||||
if (isPiBoardKind(board.boardKind)) {
|
if (isPiBoardKind(board.boardKind)) {
|
||||||
addLog({
|
blog('info', 'skipped (no compilation needed)');
|
||||||
timestamp: new Date(),
|
|
||||||
type: 'info',
|
|
||||||
message: `${label}: skipped (no compilation needed)`,
|
|
||||||
});
|
|
||||||
ok++;
|
ok++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fqbn = BOARD_KIND_FQBN[board.boardKind];
|
const fqbn = BOARD_KIND_FQBN[board.boardKind];
|
||||||
if (!fqbn) {
|
if (!fqbn) {
|
||||||
addLog({
|
blog('error', 'no FQBN configured');
|
||||||
timestamp: new Date(),
|
|
||||||
type: 'error',
|
|
||||||
message: `${label}: no FQBN configured`,
|
|
||||||
});
|
|
||||||
boardFailed++;
|
boardFailed++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
addLog({ timestamp: new Date(), type: 'info', message: `${label}: compiling...` });
|
blog('info', 'compiling...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const groupFiles = useEditorStore.getState().getGroupFiles(board.activeFileGroupId);
|
const groupFiles = useEditorStore.getState().getGroupFiles(board.activeFileGroupId);
|
||||||
|
|
@ -1000,17 +971,19 @@ export const EditorToolbar = ({
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
setCompileLogs((prev: CompilationLog[]) => [
|
setCompileLogs((prev: CompilationLog[]) => [
|
||||||
...prev,
|
...prev,
|
||||||
|
// No `${label}: ` prefix — the target section header carries it.
|
||||||
...newLines.map((line) => ({
|
...newLines.map((line) => ({
|
||||||
timestamp: now,
|
timestamp: now,
|
||||||
type: 'info' as const,
|
type: 'info' as const,
|
||||||
message: `${label}: ${line}`,
|
message: line,
|
||||||
|
target: boardTarget,
|
||||||
})),
|
})),
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
{ boardOptions: board.boardOptions, spiffsFiles: board.spiffsFiles },
|
{ boardOptions: board.boardOptions, spiffsFiles: board.spiffsFiles },
|
||||||
);
|
);
|
||||||
|
|
||||||
const resultLogs = parseCompileResult(result, label);
|
const resultLogs = parseCompileResult(result, label, boardTarget);
|
||||||
setCompileLogs((prev: CompilationLog[]) => [...prev, ...resultLogs]);
|
setCompileLogs((prev: CompilationLog[]) => [...prev, ...resultLogs]);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
|
@ -1026,11 +999,7 @@ export const EditorToolbar = ({
|
||||||
boardFailed++;
|
boardFailed++;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addLog({
|
blog('error', err instanceof Error ? err.message : String(err));
|
||||||
timestamp: new Date(),
|
|
||||||
type: 'error',
|
|
||||||
message: `${label}: ${err instanceof Error ? err.message : String(err)}`,
|
|
||||||
});
|
|
||||||
boardFailed++;
|
boardFailed++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,28 @@
|
||||||
|
|
||||||
import type { CompileResult } from '../services/compilation';
|
import type { CompileResult } from '../services/compilation';
|
||||||
|
|
||||||
|
/** Which run target a log line belongs to — lets the console group output by
|
||||||
|
* board / chip, the way multiple Arduinos already stream. Optional: lines with
|
||||||
|
* no target (e.g. "Compiling all targets...", "Done") render as plain
|
||||||
|
* narration. */
|
||||||
|
export interface CompileTarget {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
kind: 'board' | 'chip';
|
||||||
|
}
|
||||||
|
|
||||||
export interface CompilationLog {
|
export interface CompilationLog {
|
||||||
timestamp: Date;
|
timestamp: Date;
|
||||||
type: 'info' | 'success' | 'error' | 'warning' | 'core-install';
|
type: 'info' | 'success' | 'error' | 'warning' | 'core-install';
|
||||||
message: string;
|
message: string;
|
||||||
|
target?: CompileTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseCompileResult(result: CompileResult, board: string): CompilationLog[] {
|
export function parseCompileResult(
|
||||||
|
result: CompileResult,
|
||||||
|
board: string,
|
||||||
|
target?: CompileTarget,
|
||||||
|
): CompilationLog[] {
|
||||||
const logs: CompilationLog[] = [];
|
const logs: CompilationLog[] = [];
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
|
|
@ -88,5 +103,5 @@ export function parseCompileResult(result: CompileResult, board: string): Compil
|
||||||
logs.push({ timestamp: now, type: 'error', message: `✕ ${errorMsg}` });
|
logs.push({ timestamp: now, type: 'error', message: `✕ ${errorMsg}` });
|
||||||
}
|
}
|
||||||
|
|
||||||
return logs;
|
return target ? logs.map((l) => ({ ...l, target })) : logs;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue