feat: enhance ExamplesPage to auto-install required libraries; update example projects with library dependencies
parent
dbf6bd986f
commit
d12a2ad7e4
|
|
@ -48,6 +48,8 @@ export interface ExampleProject {
|
||||||
color: string;
|
color: string;
|
||||||
}>;
|
}>;
|
||||||
thumbnail?: string;
|
thumbnail?: string;
|
||||||
|
/** Arduino libraries required by this example (auto-installed when loading). */
|
||||||
|
libraries?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const exampleProjects: ExampleProject[] = [
|
export const exampleProjects: ExampleProject[] = [
|
||||||
|
|
@ -635,6 +637,7 @@ void loop() {
|
||||||
id: 'tft-display',
|
id: 'tft-display',
|
||||||
title: 'TFT ILI9341 Display',
|
title: 'TFT ILI9341 Display',
|
||||||
description: 'Color TFT display demo: fills, text, and a bouncing ball animation using the Adafruit ILI9341 library (240x320)',
|
description: 'Color TFT display demo: fills, text, and a bouncing ball animation using the Adafruit ILI9341 library (240x320)',
|
||||||
|
libraries: ['Adafruit GFX Library', 'Adafruit ILI9341'],
|
||||||
category: 'displays',
|
category: 'displays',
|
||||||
difficulty: 'intermediate',
|
difficulty: 'intermediate',
|
||||||
code: `// TFT ILI9341 Display Demo (240x320)
|
code: `// TFT ILI9341 Display Demo (240x320)
|
||||||
|
|
@ -3134,7 +3137,8 @@ void loop() {
|
||||||
{
|
{
|
||||||
id: 'uno-dht22',
|
id: 'uno-dht22',
|
||||||
title: 'Uno: DHT22 Temperature & Humidity',
|
title: 'Uno: DHT22 Temperature & Humidity',
|
||||||
description: 'Read temperature and humidity using a DHT22 sensor on pin 7. Requires the Adafruit DHT sensor library.',
|
description: 'Read temperature and humidity using a DHT22 sensor on pin 7.',
|
||||||
|
libraries: ['DHT sensor library'],
|
||||||
category: 'sensors',
|
category: 'sensors',
|
||||||
difficulty: 'beginner',
|
difficulty: 'beginner',
|
||||||
boardFilter: 'arduino-uno',
|
boardFilter: 'arduino-uno',
|
||||||
|
|
@ -3427,7 +3431,8 @@ void loop() {
|
||||||
{
|
{
|
||||||
id: 'pico-dht22',
|
id: 'pico-dht22',
|
||||||
title: 'Pico: DHT22 Temperature & Humidity',
|
title: 'Pico: DHT22 Temperature & Humidity',
|
||||||
description: 'Read temperature and humidity from a DHT22 sensor on GP7 using the Raspberry Pi Pico. Requires Adafruit DHT library.',
|
description: 'Read temperature and humidity from a DHT22 sensor on GP7 using the Raspberry Pi Pico.',
|
||||||
|
libraries: ['DHT sensor library'],
|
||||||
category: 'sensors',
|
category: 'sensors',
|
||||||
difficulty: 'beginner',
|
difficulty: 'beginner',
|
||||||
boardType: 'raspberry-pi-pico',
|
boardType: 'raspberry-pi-pico',
|
||||||
|
|
@ -3705,7 +3710,8 @@ void loop() {
|
||||||
{
|
{
|
||||||
id: 'esp32-dht22',
|
id: 'esp32-dht22',
|
||||||
title: 'ESP32: DHT22 Temperature & Humidity',
|
title: 'ESP32: DHT22 Temperature & Humidity',
|
||||||
description: 'Read temperature and humidity from a DHT22 sensor on GPIO4 of the ESP32. Requires Adafruit DHT library.',
|
description: 'Read temperature and humidity from a DHT22 sensor on GPIO4 of the ESP32.',
|
||||||
|
libraries: ['DHT sensor library'],
|
||||||
category: 'sensors',
|
category: 'sensors',
|
||||||
difficulty: 'beginner',
|
difficulty: 'beginner',
|
||||||
boardType: 'esp32',
|
boardType: 'esp32',
|
||||||
|
|
@ -3803,7 +3809,8 @@ void loop() {
|
||||||
{
|
{
|
||||||
id: 'esp32-mpu6050',
|
id: 'esp32-mpu6050',
|
||||||
title: 'ESP32: MPU-6050 Accelerometer',
|
title: 'ESP32: MPU-6050 Accelerometer',
|
||||||
description: 'Read 3-axis acceleration and gyroscope data from an MPU-6050 over I2C (SDA=D21, SCL=D22). Requires the Adafruit MPU6050 library.',
|
description: 'Read 3-axis acceleration and gyroscope data from an MPU-6050 over I2C (SDA=D21, SCL=D22).',
|
||||||
|
libraries: ['Adafruit MPU6050', 'Adafruit Unified Sensor'],
|
||||||
category: 'sensors',
|
category: 'sensors',
|
||||||
difficulty: 'intermediate',
|
difficulty: 'intermediate',
|
||||||
boardType: 'esp32',
|
boardType: 'esp32',
|
||||||
|
|
@ -3908,6 +3915,7 @@ void loop() {
|
||||||
id: 'esp32-servo',
|
id: 'esp32-servo',
|
||||||
title: 'ESP32: Servo Motor + Potentiometer',
|
title: 'ESP32: Servo Motor + Potentiometer',
|
||||||
description: 'Control a servo motor angle directly with a potentiometer. The servo follows the pot position in real time.',
|
description: 'Control a servo motor angle directly with a potentiometer. The servo follows the pot position in real time.',
|
||||||
|
libraries: ['ESP32Servo'],
|
||||||
category: 'robotics',
|
category: 'robotics',
|
||||||
difficulty: 'beginner',
|
difficulty: 'beginner',
|
||||||
boardType: 'esp32',
|
boardType: 'esp32',
|
||||||
|
|
@ -4004,6 +4012,7 @@ void loop() {
|
||||||
id: 'c3-dht22',
|
id: 'c3-dht22',
|
||||||
title: 'ESP32-C3: DHT22 Temperature & Humidity',
|
title: 'ESP32-C3: DHT22 Temperature & Humidity',
|
||||||
description: 'Read temperature and humidity with a DHT22 sensor on GPIO3 of the ESP32-C3 RISC-V board.',
|
description: 'Read temperature and humidity with a DHT22 sensor on GPIO3 of the ESP32-C3 RISC-V board.',
|
||||||
|
libraries: ['DHT sensor library'],
|
||||||
category: 'sensors',
|
category: 'sensors',
|
||||||
difficulty: 'beginner',
|
difficulty: 'beginner',
|
||||||
boardType: 'esp32-c3',
|
boardType: 'esp32-c3',
|
||||||
|
|
@ -4148,6 +4157,7 @@ void loop() {
|
||||||
id: 'c3-servo',
|
id: 'c3-servo',
|
||||||
title: 'ESP32-C3: Servo Motor Sweep',
|
title: 'ESP32-C3: Servo Motor Sweep',
|
||||||
description: 'Sweep a servo motor from 0° to 180° and back on the ESP32-C3 using GPIO10 (PWM).',
|
description: 'Sweep a servo motor from 0° to 180° and back on the ESP32-C3 using GPIO10 (PWM).',
|
||||||
|
libraries: ['ESP32Servo'],
|
||||||
category: 'robotics',
|
category: 'robotics',
|
||||||
difficulty: 'beginner',
|
difficulty: 'beginner',
|
||||||
boardType: 'esp32-c3',
|
boardType: 'esp32-c3',
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
* Displays the examples gallery
|
* Displays the examples gallery
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { ExamplesGallery } from '../components/examples/ExamplesGallery';
|
import { ExamplesGallery } from '../components/examples/ExamplesGallery';
|
||||||
import { AppHeader } from '../components/layout/AppHeader';
|
import { AppHeader } from '../components/layout/AppHeader';
|
||||||
|
|
@ -13,6 +13,7 @@ import { useEditorStore } from '../store/useEditorStore';
|
||||||
import { useSimulatorStore } from '../store/useSimulatorStore';
|
import { useSimulatorStore } from '../store/useSimulatorStore';
|
||||||
import { useVfsStore } from '../store/useVfsStore';
|
import { useVfsStore } from '../store/useVfsStore';
|
||||||
import { isBoardComponent } from '../utils/boardPinMapping';
|
import { isBoardComponent } from '../utils/boardPinMapping';
|
||||||
|
import { getInstalledLibraries, installLibrary } from '../services/libraryService';
|
||||||
import type { ExampleProject } from '../data/examples';
|
import type { ExampleProject } from '../data/examples';
|
||||||
import type { BoardKind } from '../types/board';
|
import type { BoardKind } from '../types/board';
|
||||||
|
|
||||||
|
|
@ -27,9 +28,36 @@ export const ExamplesPage: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { setCode } = useEditorStore();
|
const { setCode } = useEditorStore();
|
||||||
const { setComponents, setWires, setBoardType, activeBoardId, boards, addBoard, removeBoard, setActiveBoardId } = useSimulatorStore();
|
const { setComponents, setWires, setBoardType, activeBoardId, boards, addBoard, removeBoard, setActiveBoardId } = useSimulatorStore();
|
||||||
|
const [installing, setInstalling] = useState<{ total: number; done: number; current: string } | null>(null);
|
||||||
|
|
||||||
const handleLoadExample = (example: ExampleProject) => {
|
/** Install any missing libraries required by the example (non-blocking UI). */
|
||||||
console.log('Loading example:', example.title);
|
const ensureLibraries = async (libs: string[]): Promise<void> => {
|
||||||
|
if (libs.length === 0) return;
|
||||||
|
try {
|
||||||
|
const installed = await getInstalledLibraries();
|
||||||
|
const installedNames = new Set(
|
||||||
|
installed.map((l) => (l.library?.name ?? l.name ?? '').toLowerCase())
|
||||||
|
);
|
||||||
|
const missing = libs.filter((l) => !installedNames.has(l.toLowerCase()));
|
||||||
|
if (missing.length === 0) return;
|
||||||
|
|
||||||
|
setInstalling({ total: missing.length, done: 0, current: missing[0] });
|
||||||
|
for (let i = 0; i < missing.length; i++) {
|
||||||
|
setInstalling({ total: missing.length, done: i, current: missing[i] });
|
||||||
|
await installLibrary(missing[i]);
|
||||||
|
}
|
||||||
|
setInstalling(null);
|
||||||
|
} catch {
|
||||||
|
// If install fails (e.g. offline), continue anyway — compile will show the error
|
||||||
|
setInstalling(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLoadExample = async (example: ExampleProject) => {
|
||||||
|
// Auto-install required libraries before loading
|
||||||
|
if (example.libraries && example.libraries.length > 0) {
|
||||||
|
await ensureLibraries(example.libraries);
|
||||||
|
}
|
||||||
|
|
||||||
if (example.boards && example.boards.length > 0) {
|
if (example.boards && example.boards.length > 0) {
|
||||||
// ── Multi-board loading ───────────────────────────────────────────────
|
// ── Multi-board loading ───────────────────────────────────────────────
|
||||||
|
|
@ -150,6 +178,36 @@ export const ExamplesPage: React.FC = () => {
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', minHeight: '100vh', background: '#1e1e1e' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', minHeight: '100vh', background: '#1e1e1e' }}>
|
||||||
<AppHeader />
|
<AppHeader />
|
||||||
<ExamplesGallery onLoadExample={handleLoadExample} />
|
<ExamplesGallery onLoadExample={handleLoadExample} />
|
||||||
|
|
||||||
|
{/* Library install overlay */}
|
||||||
|
{installing && (
|
||||||
|
<div style={{
|
||||||
|
position: 'fixed', inset: 0, zIndex: 9999,
|
||||||
|
background: 'rgba(0,0,0,0.7)', backdropFilter: 'blur(4px)',
|
||||||
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
background: '#1e1e1e', border: '1px solid #333', borderRadius: 12,
|
||||||
|
padding: '28px 36px', textAlign: 'center', maxWidth: 360,
|
||||||
|
}}>
|
||||||
|
<div style={{ fontSize: 14, color: '#ccc', marginBottom: 12 }}>
|
||||||
|
Installing libraries ({installing.done + 1}/{installing.total})
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 16, color: '#00e5ff', fontWeight: 600, marginBottom: 16 }}>
|
||||||
|
{installing.current}
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
height: 4, borderRadius: 2, background: '#333', overflow: 'hidden',
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
height: '100%', borderRadius: 2, background: '#00b8d4',
|
||||||
|
width: `${((installing.done + 1) / installing.total) * 100}%`,
|
||||||
|
transition: 'width 0.3s ease',
|
||||||
|
}} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue