feat(editor): M5Stack section (Cardputer ADV, Core) ahead of STM32 in the starter dialog
The New Project dialog now lists an M5Stack section — M5 Cardputer ADV first, then M5Stack Core — between ESP32 and STM32. Both kinds are overlay-registered, so the section only renders when the overlay mounted (an OSS build shows no empty block). Selecting a card loads the board's on-LCD hello example (cardputer-adv-hello / m5stack-core-m5-helloworld), which also provides the card thumbnail. The sections builder is extracted as buildStarterSections(defs) and covered by a unit test that pins the display order.
This commit is contained in:
parent
15126cd27a
commit
0e107fafe6
|
|
@ -0,0 +1,54 @@
|
||||||
|
/**
|
||||||
|
* NewProjectDialog starter sections — display order and overlay cards.
|
||||||
|
*
|
||||||
|
* The dialog hardcodes its sections; overlay-registered kinds (M5Stack,
|
||||||
|
* XIAO C6, XIAO RP2040) surface only when the private overlay registered
|
||||||
|
* them. Guards the operator's requested order: M5Stack (Cardputer ADV, then
|
||||||
|
* Core) sits ahead of STM32, and an OSS build shows no empty M5Stack block.
|
||||||
|
*/
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { buildStarterSections } from '../components/editor/NewProjectDialog';
|
||||||
|
import type { ProBoardDef } from '../lib/proBoardRegistry';
|
||||||
|
|
||||||
|
const def = (kind: string, label: string, description: string): ProBoardDef => ({
|
||||||
|
kind,
|
||||||
|
label,
|
||||||
|
description,
|
||||||
|
fqbn: null,
|
||||||
|
tag: `velxio-${kind}`,
|
||||||
|
size: { w: 100, h: 100 },
|
||||||
|
});
|
||||||
|
|
||||||
|
const titles = (defs: ProBoardDef[]) =>
|
||||||
|
buildStarterSections(defs)
|
||||||
|
.filter((s) => s.entries.length > 0)
|
||||||
|
.map((s) => s.title);
|
||||||
|
|
||||||
|
describe('NewProjectDialog starter sections', () => {
|
||||||
|
it('OSS build (no overlay boards) shows no M5Stack section', () => {
|
||||||
|
expect(titles([])).toEqual(['Arduino', 'ESP32', 'STM32', 'Raspberry Pi']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with the M5Stack overlay boards registered, M5Stack comes before STM32', () => {
|
||||||
|
const defs = [
|
||||||
|
def('m5stack-core', 'M5Stack Core', 'ESP32 all-in-one'),
|
||||||
|
def('cardputer-adv', 'M5 Cardputer ADV', 'ESP32-S3 card computer'),
|
||||||
|
];
|
||||||
|
const order = titles(defs);
|
||||||
|
expect(order).toEqual(['Arduino', 'ESP32', 'M5Stack', 'STM32', 'Raspberry Pi']);
|
||||||
|
expect(order.indexOf('M5Stack')).toBeLessThan(order.indexOf('STM32'));
|
||||||
|
|
||||||
|
const m5 = buildStarterSections(defs).find((s) => s.title === 'M5Stack')!;
|
||||||
|
// Cardputer ADV first, then the Core — regardless of registration order.
|
||||||
|
expect(m5.entries.map((e) => e.kind)).toEqual(['cardputer-adv', 'm5stack-core']);
|
||||||
|
// Card blurb comes from the registered definition, not a hardcoded string.
|
||||||
|
expect(m5.entries[0].blurb).toBe('ESP32-S3 card computer');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('a partial overlay (only one M5 kind) still renders the section with that card', () => {
|
||||||
|
const m5 = buildStarterSections([def('m5stack-core', 'M5Stack Core', 'x')]).find(
|
||||||
|
(s) => s.title === 'M5Stack',
|
||||||
|
)!;
|
||||||
|
expect(m5.entries.map((e) => e.kind)).toEqual(['m5stack-core']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -4,9 +4,10 @@
|
||||||
* Shown (a) on a pristine `/editor` visit (over an emptied canvas), and
|
* Shown (a) on a pristine `/editor` visit (over an emptied canvas), and
|
||||||
* (b) from the "New workspace" button / File menu entry. Offers a blank
|
* (b) from the "New workspace" button / File menu entry. Offers a blank
|
||||||
* workspace plus a ready-to-run Blink starter per board family — Arduino,
|
* workspace plus a ready-to-run Blink starter per board family — Arduino,
|
||||||
* ESP32 (one card per chip generation, XIAO variant preferred), STM32,
|
* ESP32 (one card per chip generation, XIAO variant preferred), M5Stack
|
||||||
* Raspberry Pi — each card carrying the same circuit thumbnail the examples
|
* (overlay-registered all-in-ones, listed ahead of STM32), STM32, Raspberry
|
||||||
* gallery uses (/examples-thumbs/<id>.webp, CircuitPreview fallback).
|
* Pi — each card carrying the same circuit thumbnail the examples gallery
|
||||||
|
* uses (/examples-thumbs/<id>.webp, CircuitPreview fallback).
|
||||||
*
|
*
|
||||||
* Selecting a board loads its gallery Blink example when one exists (full
|
* Selecting a board loads its gallery Blink example when one exists (full
|
||||||
* wiring: 220Ω resistor + LED); boards without one get a fresh board whose
|
* wiring: 220Ω resistor + LED); boards without one get a fresh board whose
|
||||||
|
|
@ -29,6 +30,7 @@ import {
|
||||||
listProBoards,
|
listProBoards,
|
||||||
subscribeProBoards,
|
subscribeProBoards,
|
||||||
getProBoardsVersion,
|
getProBoardsVersion,
|
||||||
|
type ProBoardDef,
|
||||||
} from '../../lib/proBoardRegistry';
|
} from '../../lib/proBoardRegistry';
|
||||||
import { useSimulatorStore, DEFAULT_BOARD_POSITION } from '../../store/useSimulatorStore';
|
import { useSimulatorStore, DEFAULT_BOARD_POSITION } from '../../store/useSimulatorStore';
|
||||||
import { useProjectStore } from '../../store/useProjectStore';
|
import { useProjectStore } from '../../store/useProjectStore';
|
||||||
|
|
@ -150,6 +152,10 @@ const PREFERRED_BLINK_EXAMPLE: Record<string, string> = {
|
||||||
'stm32-f4-discovery': 'stm32-f4-discovery-blink',
|
'stm32-f4-discovery': 'stm32-f4-discovery-blink',
|
||||||
'stm32-olimex-h405': 'stm32-olimex-h405-blink',
|
'stm32-olimex-h405': 'stm32-olimex-h405-blink',
|
||||||
'stm32-netduino-plus2': 'stm32-netduino-plus2-blink',
|
'stm32-netduino-plus2': 'stm32-netduino-plus2-blink',
|
||||||
|
// M5Stack all-in-ones (overlay kinds): no LED to blink — the on-LCD hello
|
||||||
|
// is the first run. Both ids carry a captured gallery thumb.
|
||||||
|
'cardputer-adv': 'cardputer-adv-hello',
|
||||||
|
'm5stack-core': 'm5stack-core-m5-helloworld',
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Dynamic import keeps the (large) gallery data out of the editor bundle
|
/** Dynamic import keeps the (large) gallery data out of the editor bundle
|
||||||
|
|
@ -207,6 +213,62 @@ async function applyStarter(kind: string | 'blank'): Promise<void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface StarterSection {
|
||||||
|
title: string;
|
||||||
|
entries: Array<{ kind: string; blurb: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The card sections, in display order. Overlay-registered kinds only exist
|
||||||
|
* when the private overlay mounted — an OSS build simply doesn't show those
|
||||||
|
* cards, and a section left with no entries is not rendered at all.
|
||||||
|
*/
|
||||||
|
export function buildStarterSections(defs: ProBoardDef[]): StarterSection[] {
|
||||||
|
const oss = (k: BoardKind) => ({ kind: k as string, blurb: BOARD_BLURBS[k] ?? '' });
|
||||||
|
const pro = (k: string) => {
|
||||||
|
const d = defs.find((x) => x.kind === k);
|
||||||
|
return d ? [{ kind: d.kind, blurb: d.description }] : [];
|
||||||
|
};
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: 'Arduino',
|
||||||
|
entries: [
|
||||||
|
oss('arduino-uno'),
|
||||||
|
oss('arduino-mega'),
|
||||||
|
oss('arduino-nano'),
|
||||||
|
oss('arduino-nano-esp32'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// One card per ESP32 chip generation, XIAO variant preferred where
|
||||||
|
// Seeed makes one: classic → DevKit V1, S3/C3 → XIAO, C6 → XIAO (overlay).
|
||||||
|
{
|
||||||
|
title: 'ESP32',
|
||||||
|
entries: [
|
||||||
|
oss('esp32'),
|
||||||
|
oss('esp32-cam'),
|
||||||
|
oss('xiao-esp32-s3'),
|
||||||
|
oss('xiao-esp32-c3'),
|
||||||
|
...pro('xiao-esp32c6'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// M5Stack all-in-ones (overlay): ahead of STM32 by operator request —
|
||||||
|
// they run on Free and are a friendlier first pick than the Pro-gated
|
||||||
|
// STM32 family. Cardputer first, then the Core.
|
||||||
|
{ title: 'M5Stack', entries: [...pro('cardputer-adv'), ...pro('m5stack-core')] },
|
||||||
|
{ title: 'STM32', entries: STM32_BOARDS.map(oss) },
|
||||||
|
{
|
||||||
|
title: 'Raspberry Pi',
|
||||||
|
entries: [
|
||||||
|
oss('raspberry-pi-pico'),
|
||||||
|
...pro('xiao-rp2040'),
|
||||||
|
oss('raspberry-pi-3'),
|
||||||
|
oss('raspberry-pi-4'),
|
||||||
|
oss('raspberry-pi-5'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
const ProPill: React.FC = () => (
|
const ProPill: React.FC = () => (
|
||||||
<span className="new-project-pro" title="Pro feature — paid plan or Velxio Desktop">
|
<span className="new-project-pro" title="Pro feature — paid plan or Velxio Desktop">
|
||||||
PRO
|
PRO
|
||||||
|
|
@ -224,51 +286,8 @@ export const NewProjectDialog: React.FC<NewProjectDialogProps> = ({ isOpen, onCl
|
||||||
getProBoardsVersion,
|
getProBoardsVersion,
|
||||||
);
|
);
|
||||||
|
|
||||||
const sections = useMemo(() => {
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
const defs = listProBoards();
|
const sections = useMemo(() => buildStarterSections(listProBoards()), [proBoardsVersion]);
|
||||||
const oss = (k: BoardKind) => ({ kind: k as string, blurb: BOARD_BLURBS[k] ?? '' });
|
|
||||||
// Overlay-registered kinds only exist when the private overlay mounted —
|
|
||||||
// an OSS build simply doesn't show those cards.
|
|
||||||
const pro = (k: string) => {
|
|
||||||
const d = defs.find((x) => x.kind === k);
|
|
||||||
return d ? [{ kind: d.kind, blurb: d.description }] : [];
|
|
||||||
};
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
title: 'Arduino',
|
|
||||||
entries: [
|
|
||||||
oss('arduino-uno'),
|
|
||||||
oss('arduino-mega'),
|
|
||||||
oss('arduino-nano'),
|
|
||||||
oss('arduino-nano-esp32'),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
// One card per ESP32 chip generation, XIAO variant preferred where
|
|
||||||
// Seeed makes one: classic → DevKit V1, S3/C3 → XIAO, C6 → XIAO (overlay).
|
|
||||||
{
|
|
||||||
title: 'ESP32',
|
|
||||||
entries: [
|
|
||||||
oss('esp32'),
|
|
||||||
oss('esp32-cam'),
|
|
||||||
oss('xiao-esp32-s3'),
|
|
||||||
oss('xiao-esp32-c3'),
|
|
||||||
...pro('xiao-esp32c6'),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{ title: 'STM32', entries: STM32_BOARDS.map(oss) },
|
|
||||||
{
|
|
||||||
title: 'Raspberry Pi',
|
|
||||||
entries: [
|
|
||||||
oss('raspberry-pi-pico'),
|
|
||||||
...pro('xiao-rp2040'),
|
|
||||||
oss('raspberry-pi-3'),
|
|
||||||
oss('raspberry-pi-4'),
|
|
||||||
oss('raspberry-pi-5'),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [proBoardsVersion]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOpen) return;
|
if (!isOpen) return;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue