feat: implement examples gallery and editor page with routing and example loading functionality

pull/10/head
David Montero Crespo 2026-03-03 22:25:52 -03:00
parent 85cb535804
commit b7e9bf64c4
9 changed files with 1192 additions and 22 deletions

View File

@ -2,6 +2,14 @@
Local Arduino emulator with code editor and visual simulator.
## Support This Project
If you find this project helpful, please consider giving it a star! Your support helps the project grow and motivates continued development.
[![GitHub stars](https://img.shields.io/github/stars/yourusername/wokwi_clon?style=social)](https://github.com/yourusername/wokwi_clon/stargazers)
Every star counts and helps make this project better!
## Features
- ✅ Code editor with syntax highlighting (Monaco Editor)
@ -16,11 +24,11 @@ Local Arduino emulator with code editor and visual simulator.
## Screenshots
<img src="doc/img1.png" alt="Arduino Emulator - Editor and Simulator" width="800"/>
![Arduino Emulator - Editor and Simulator](doc/img1.png)
*Arduino emulator with Monaco code editor and visual simulator with wokwi-elements*
<img src="doc/img2.png" alt="Arduino Emulator - Component Properties and Wire Editing" width="800"/>
![Arduino Emulator - Component Properties and Wire Editing](doc/img2.png)
*Interactive component properties dialog and segment-based wire editing*

View File

@ -22,6 +22,23 @@ body {
border-bottom: 2px solid #007acc;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.header-title h1 {
font-size: 24px;
margin-bottom: 5px;
color: #007acc;
}
.header-title p {
font-size: 14px;
color: #aaa;
}
.app-header h1 {
font-size: 24px;
margin-bottom: 5px;
@ -33,6 +50,26 @@ body {
color: #aaa;
}
.examples-link {
padding: 10px 20px;
background-color: #007acc;
color: #fff;
text-decoration: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 8px;
}
.examples-link:hover {
background-color: #005a9e;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 122, 204, 0.3);
}
.app-container {
flex: 1;
display: flex;

View File

@ -1,27 +1,16 @@
import { CodeEditor } from './components/editor/CodeEditor';
import { EditorToolbar } from './components/editor/EditorToolbar';
import { SimulatorCanvas } from './components/simulator/SimulatorCanvas';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
import { EditorPage } from './pages/EditorPage';
import { ExamplesPage } from './pages/ExamplesPage';
import './App.css';
function App() {
return (
<div className="app">
<header className="app-header">
<h1>Arduino Emulator</h1>
<p>Local Arduino IDE & Simulator</p>
</header>
<div className="app-container">
<div className="editor-panel">
<EditorToolbar />
<div className="editor-wrapper">
<CodeEditor />
</div>
</div>
<div className="simulator-panel">
<SimulatorCanvas />
</div>
</div>
</div>
<Router>
<Routes>
<Route path="/" element={<EditorPage />} />
<Route path="/examples" element={<ExamplesPage />} />
</Routes>
</Router>
);
}

View File

@ -0,0 +1,241 @@
/**
* Examples Gallery Styles
*/
.examples-gallery {
min-height: 100vh;
background-color: #1e1e1e;
color: #fff;
padding: 40px 20px;
}
.examples-nav {
max-width: 1200px;
margin: 0 auto 20px auto;
}
.back-link {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
background-color: #2d2d2d;
color: #fff;
text-decoration: none;
border-radius: 6px;
border: 1px solid #444;
font-size: 14px;
transition: all 0.2s;
}
.back-link:hover {
background-color: #3d3d3d;
border-color: #007acc;
color: #007acc;
}
.examples-header {
text-align: center;
margin-bottom: 40px;
}
.examples-header h1 {
font-size: 48px;
margin: 0 0 10px 0;
font-weight: 300;
}
.examples-header p {
font-size: 20px;
color: #aaa;
margin: 0;
}
/* Filters */
.examples-filters {
max-width: 1200px;
margin: 0 auto 40px auto;
display: flex;
flex-direction: column;
gap: 20px;
}
.filter-group {
display: flex;
align-items: center;
gap: 15px;
}
.filter-group label {
font-size: 14px;
color: #aaa;
min-width: 80px;
}
.filter-buttons {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.filter-button {
padding: 8px 16px;
border: 1px solid #444;
background-color: #2d2d2d;
color: #fff;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
transition: all 0.2s;
}
.filter-button:hover {
background-color: #3d3d3d;
border-color: #555;
}
.filter-button.active {
background-color: #007acc;
border-color: #007acc;
color: #fff;
}
/* Examples Grid */
.examples-grid {
max-width: 1200px;
margin: 0 auto;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 24px;
}
.example-card {
background-color: #2d2d2d;
border: 1px solid #444;
border-radius: 8px;
overflow: hidden;
cursor: pointer;
transition: all 0.3s;
display: flex;
flex-direction: column;
}
.example-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
border-color: #007acc;
}
.example-thumbnail {
width: 100%;
height: 200px;
background-color: #1e1e1e;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.example-thumbnail img {
width: 100%;
height: 100%;
object-fit: cover;
}
.example-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
width: 100%;
height: 100%;
}
.example-icon {
font-size: 64px;
opacity: 0.5;
}
.example-components-count {
font-size: 12px;
color: #888;
}
.example-info {
padding: 16px;
display: flex;
flex-direction: column;
gap: 8px;
flex: 1;
}
.example-title {
font-size: 18px;
font-weight: 600;
margin: 0;
color: #fff;
}
.example-description {
font-size: 14px;
color: #aaa;
margin: 0;
line-height: 1.5;
flex: 1;
}
.example-meta {
display: flex;
align-items: center;
gap: 10px;
margin-top: auto;
}
.example-difficulty {
padding: 4px 10px;
border-radius: 12px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
color: #1e1e1e;
}
.example-category {
font-size: 12px;
color: #888;
text-transform: capitalize;
}
/* Empty State */
.examples-empty {
max-width: 1200px;
margin: 60px auto;
text-align: center;
color: #888;
font-size: 16px;
}
/* Responsive */
@media (max-width: 768px) {
.examples-header h1 {
font-size: 32px;
}
.examples-header p {
font-size: 16px;
}
.examples-grid {
grid-template-columns: 1fr;
}
.filter-group {
flex-direction: column;
align-items: flex-start;
}
.filter-group label {
min-width: auto;
}
}

View File

@ -0,0 +1,167 @@
/**
* Examples Gallery Component
*
* Displays a gallery of example Arduino projects that users can load and run
*/
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import { exampleProjects, getCategories, type ExampleProject } from '../../data/examples';
import './ExamplesGallery.css';
interface ExamplesGalleryProps {
onLoadExample: (example: ExampleProject) => void;
}
export const ExamplesGallery: React.FC<ExamplesGalleryProps> = ({ onLoadExample }) => {
const [selectedCategory, setSelectedCategory] = useState<ExampleProject['category'] | 'all'>(
'all'
);
const [selectedDifficulty, setSelectedDifficulty] = useState<
ExampleProject['difficulty'] | 'all'
>('all');
const categories = getCategories();
// Filter examples based on selected category and difficulty
const filteredExamples = exampleProjects.filter((example) => {
const categoryMatch = selectedCategory === 'all' || example.category === selectedCategory;
const difficultyMatch =
selectedDifficulty === 'all' || example.difficulty === selectedDifficulty;
return categoryMatch && difficultyMatch;
});
const getCategoryIcon = (category: ExampleProject['category']): string => {
const icons: Record<ExampleProject['category'], string> = {
basics: '💡',
sensors: '📡',
displays: '📺',
communication: '📶',
games: '🎮',
robotics: '🤖',
};
return icons[category];
};
const getDifficultyColor = (difficulty: ExampleProject['difficulty']): string => {
const colors: Record<ExampleProject['difficulty'], string> = {
beginner: '#4ade80',
intermediate: '#fbbf24',
advanced: '#f87171',
};
return colors[difficulty];
};
return (
<div className="examples-gallery">
<div className="examples-nav">
<Link to="/" className="back-link">
Back to Editor
</Link>
</div>
<div className="examples-header">
<h1>Featured Projects</h1>
<p>Explore and run example Arduino projects</p>
</div>
{/* Filters */}
<div className="examples-filters">
<div className="filter-group">
<label>Category:</label>
<div className="filter-buttons">
<button
className={`filter-button ${selectedCategory === 'all' ? 'active' : ''}`}
onClick={() => setSelectedCategory('all')}
>
All
</button>
{categories.map((category) => (
<button
key={category}
className={`filter-button ${selectedCategory === category ? 'active' : ''}`}
onClick={() => setSelectedCategory(category)}
>
{getCategoryIcon(category)} {category.charAt(0).toUpperCase() + category.slice(1)}
</button>
))}
</div>
</div>
<div className="filter-group">
<label>Difficulty:</label>
<div className="filter-buttons">
<button
className={`filter-button ${selectedDifficulty === 'all' ? 'active' : ''}`}
onClick={() => setSelectedDifficulty('all')}
>
All
</button>
<button
className={`filter-button ${selectedDifficulty === 'beginner' ? 'active' : ''}`}
onClick={() => setSelectedDifficulty('beginner')}
>
Beginner
</button>
<button
className={`filter-button ${selectedDifficulty === 'intermediate' ? 'active' : ''}`}
onClick={() => setSelectedDifficulty('intermediate')}
>
Intermediate
</button>
<button
className={`filter-button ${selectedDifficulty === 'advanced' ? 'active' : ''}`}
onClick={() => setSelectedDifficulty('advanced')}
>
Advanced
</button>
</div>
</div>
</div>
{/* Examples Grid */}
<div className="examples-grid">
{filteredExamples.map((example) => (
<div
key={example.id}
className="example-card"
onClick={() => onLoadExample(example)}
>
<div className="example-thumbnail">
{example.thumbnail ? (
<img src={example.thumbnail} alt={example.title} />
) : (
<div className="example-placeholder">
<span className="example-icon">{getCategoryIcon(example.category)}</span>
<span className="example-components-count">
{example.components.length} component{example.components.length !== 1 ? 's' : ''}
</span>
</div>
)}
</div>
<div className="example-info">
<h3 className="example-title">{example.title}</h3>
<p className="example-description">{example.description}</p>
<div className="example-meta">
<span
className="example-difficulty"
style={{ backgroundColor: getDifficultyColor(example.difficulty) }}
>
{example.difficulty}
</span>
<span className="example-category">
{getCategoryIcon(example.category)} {example.category}
</span>
</div>
</div>
</div>
))}
</div>
{filteredExamples.length === 0 && (
<div className="examples-empty">
<p>No examples found with the selected filters</p>
</div>
)}
</div>
);
};

View File

@ -0,0 +1,611 @@
/**
* Arduino Example Projects
*
* Collection of example projects that users can load and run
*/
export interface ExampleProject {
id: string;
title: string;
description: string;
category: 'basics' | 'sensors' | 'displays' | 'communication' | 'games' | 'robotics';
difficulty: 'beginner' | 'intermediate' | 'advanced';
code: string;
components: Array<{
type: string;
id: string;
x: number;
y: number;
properties: Record<string, any>;
}>;
wires: Array<{
id: string;
start: { componentId: string; pinName: string };
end: { componentId: string; pinName: string };
color: string;
}>;
thumbnail?: string;
}
export const exampleProjects: ExampleProject[] = [
{
id: 'blink-led',
title: 'Blink LED',
description: 'Classic Arduino blink example - toggle an LED on and off',
category: 'basics',
difficulty: 'beginner',
code: `// Blink LED Example
// Toggles the built-in LED on pin 13
void setup() {
pinMode(13, OUTPUT);
}
void loop() {
digitalWrite(13, HIGH);
delay(1000);
digitalWrite(13, LOW);
delay(1000);
}`,
components: [
{
type: 'wokwi-arduino-uno',
id: 'arduino-uno',
x: 100,
y: 100,
properties: {},
},
],
wires: [],
},
{
id: 'traffic-light',
title: 'Traffic Light',
description: 'Simulate a traffic light with red, yellow, and green LEDs',
category: 'basics',
difficulty: 'beginner',
code: `// Traffic Light Simulator
// Red -> Yellow -> Green -> Yellow -> Red
const int RED_PIN = 13;
const int YELLOW_PIN = 12;
const int GREEN_PIN = 11;
void setup() {
pinMode(RED_PIN, OUTPUT);
pinMode(YELLOW_PIN, OUTPUT);
pinMode(GREEN_PIN, OUTPUT);
}
void loop() {
// Red light
digitalWrite(RED_PIN, HIGH);
delay(3000);
digitalWrite(RED_PIN, LOW);
// Yellow light
digitalWrite(YELLOW_PIN, HIGH);
delay(1000);
digitalWrite(YELLOW_PIN, LOW);
// Green light
digitalWrite(GREEN_PIN, HIGH);
delay(3000);
digitalWrite(GREEN_PIN, LOW);
// Yellow light again
digitalWrite(YELLOW_PIN, HIGH);
delay(1000);
digitalWrite(YELLOW_PIN, LOW);
}`,
components: [
{
type: 'wokwi-arduino-uno',
id: 'arduino-uno',
x: 100,
y: 100,
properties: {},
},
{
type: 'wokwi-led',
id: 'led-red',
x: 400,
y: 100,
properties: { color: 'red', pin: 13 },
},
{
type: 'wokwi-led',
id: 'led-yellow',
x: 400,
y: 200,
properties: { color: 'yellow', pin: 12 },
},
{
type: 'wokwi-led',
id: 'led-green',
x: 400,
y: 300,
properties: { color: 'green', pin: 11 },
},
],
wires: [
{
id: 'wire-red',
start: { componentId: 'arduino-uno', pinName: '13' },
end: { componentId: 'led-red', pinName: 'A' },
color: '#ff0000',
},
{
id: 'wire-yellow',
start: { componentId: 'arduino-uno', pinName: '12' },
end: { componentId: 'led-yellow', pinName: 'A' },
color: '#ffaa00',
},
{
id: 'wire-green',
start: { componentId: 'arduino-uno', pinName: '11' },
end: { componentId: 'led-green', pinName: 'A' },
color: '#00ff00',
},
],
},
{
id: 'button-led',
title: 'Button Control',
description: 'Control an LED with a pushbutton',
category: 'basics',
difficulty: 'beginner',
code: `// Button LED Control
// Press button to turn LED on
const int BUTTON_PIN = 2;
const int LED_PIN = 13;
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
}
void loop() {
int buttonState = digitalRead(BUTTON_PIN);
if (buttonState == LOW) {
digitalWrite(LED_PIN, HIGH);
} else {
digitalWrite(LED_PIN, LOW);
}
}`,
components: [
{
type: 'wokwi-arduino-uno',
id: 'arduino-uno',
x: 100,
y: 100,
properties: {},
},
{
type: 'wokwi-pushbutton',
id: 'button-1',
x: 400,
y: 100,
properties: {},
},
{
type: 'wokwi-led',
id: 'led-1',
x: 400,
y: 250,
properties: { color: 'red', pin: 13 },
},
],
wires: [
{
id: 'wire-button',
start: { componentId: 'arduino-uno', pinName: '2' },
end: { componentId: 'button-1', pinName: '1.L' },
color: '#00aaff',
},
{
id: 'wire-led',
start: { componentId: 'arduino-uno', pinName: '13' },
end: { componentId: 'led-1', pinName: 'A' },
color: '#ff0000',
},
],
},
{
id: 'fade-led',
title: 'Fade LED',
description: 'Smoothly fade an LED using PWM',
category: 'basics',
difficulty: 'beginner',
code: `// Fade LED with PWM
// Smoothly fade LED brightness
const int LED_PIN = 9; // PWM pin
int brightness = 0;
int fadeAmount = 5;
void setup() {
pinMode(LED_PIN, OUTPUT);
}
void loop() {
analogWrite(LED_PIN, brightness);
brightness += fadeAmount;
if (brightness <= 0 || brightness >= 255) {
fadeAmount = -fadeAmount;
}
delay(30);
}`,
components: [
{
type: 'wokwi-arduino-uno',
id: 'arduino-uno',
x: 100,
y: 100,
properties: {},
},
{
type: 'wokwi-led',
id: 'led-1',
x: 400,
y: 150,
properties: { color: 'blue', pin: 9 },
},
],
wires: [
{
id: 'wire-led',
start: { componentId: 'arduino-uno', pinName: '9' },
end: { componentId: 'led-1', pinName: 'A' },
color: '#0000ff',
},
],
},
{
id: 'serial-hello',
title: 'Serial Hello World',
description: 'Send messages through serial communication',
category: 'communication',
difficulty: 'beginner',
code: `// Serial Communication Example
// Send messages to Serial Monitor
void setup() {
Serial.begin(9600);
Serial.println("Hello, Arduino!");
Serial.println("System initialized");
}
void loop() {
Serial.print("Uptime: ");
Serial.print(millis() / 1000);
Serial.println(" seconds");
delay(2000);
}`,
components: [
{
type: 'wokwi-arduino-uno',
id: 'arduino-uno',
x: 100,
y: 100,
properties: {},
},
],
wires: [],
},
{
id: 'rgb-led',
title: 'RGB LED Colors',
description: 'Cycle through colors with an RGB LED',
category: 'basics',
difficulty: 'intermediate',
code: `// RGB LED Color Cycling
// Display different colors
const int RED_PIN = 9;
const int GREEN_PIN = 10;
const int BLUE_PIN = 11;
void setup() {
pinMode(RED_PIN, OUTPUT);
pinMode(GREEN_PIN, OUTPUT);
pinMode(BLUE_PIN, OUTPUT);
}
void setColor(int red, int green, int blue) {
analogWrite(RED_PIN, red);
analogWrite(GREEN_PIN, green);
analogWrite(BLUE_PIN, blue);
}
void loop() {
// Red
setColor(255, 0, 0);
delay(1000);
// Green
setColor(0, 255, 0);
delay(1000);
// Blue
setColor(0, 0, 255);
delay(1000);
// Yellow
setColor(255, 255, 0);
delay(1000);
// Cyan
setColor(0, 255, 255);
delay(1000);
// Magenta
setColor(255, 0, 255);
delay(1000);
// White
setColor(255, 255, 255);
delay(1000);
}`,
components: [
{
type: 'wokwi-arduino-uno',
id: 'arduino-uno',
x: 100,
y: 100,
properties: {},
},
{
type: 'wokwi-rgb-led',
id: 'rgb-led-1',
x: 400,
y: 150,
properties: {},
},
],
wires: [
{
id: 'wire-red',
start: { componentId: 'arduino-uno', pinName: '9' },
end: { componentId: 'rgb-led-1', pinName: 'R' },
color: '#ff0000',
},
{
id: 'wire-green',
start: { componentId: 'arduino-uno', pinName: '10' },
end: { componentId: 'rgb-led-1', pinName: 'G' },
color: '#00ff00',
},
{
id: 'wire-blue',
start: { componentId: 'arduino-uno', pinName: '11' },
end: { componentId: 'rgb-led-1', pinName: 'B' },
color: '#0000ff',
},
],
},
{
id: 'simon-says',
title: 'Simon Says Game',
description: 'Memory game with LEDs and buttons',
category: 'games',
difficulty: 'advanced',
code: `// Simon Says Game
// Memory game with 4 LEDs and buttons
const int LED_PINS[] = {8, 9, 10, 11};
const int BUTTON_PINS[] = {2, 3, 4, 5};
const int NUM_LEDS = 4;
int sequence[100];
int sequenceLength = 0;
int currentStep = 0;
void setup() {
Serial.begin(9600);
for (int i = 0; i < NUM_LEDS; i++) {
pinMode(LED_PINS[i], OUTPUT);
pinMode(BUTTON_PINS[i], INPUT_PULLUP);
}
randomSeed(analogRead(A0));
newGame();
}
void newGame() {
sequenceLength = 1;
currentStep = 0;
addToSequence();
playSequence();
}
void addToSequence() {
sequence[sequenceLength - 1] = random(0, NUM_LEDS);
}
void playSequence() {
for (int i = 0; i < sequenceLength; i++) {
flashLED(sequence[i]);
delay(500);
}
}
void flashLED(int led) {
digitalWrite(LED_PINS[led], HIGH);
delay(300);
digitalWrite(LED_PINS[led], LOW);
}
void loop() {
for (int i = 0; i < NUM_LEDS; i++) {
if (digitalRead(BUTTON_PINS[i]) == LOW) {
flashLED(i);
if (i == sequence[currentStep]) {
currentStep++;
if (currentStep == sequenceLength) {
delay(1000);
sequenceLength++;
currentStep = 0;
addToSequence();
playSequence();
}
} else {
// Wrong button - game over
for (int j = 0; j < 3; j++) {
for (int k = 0; k < NUM_LEDS; k++) {
digitalWrite(LED_PINS[k], HIGH);
}
delay(200);
for (int k = 0; k < NUM_LEDS; k++) {
digitalWrite(LED_PINS[k], LOW);
}
delay(200);
}
newGame();
}
delay(300);
while (digitalRead(BUTTON_PINS[i]) == LOW);
}
}
}`,
components: [
{
type: 'wokwi-arduino-uno',
id: 'arduino-uno',
x: 100,
y: 100,
properties: {},
},
{
type: 'wokwi-led',
id: 'led-red',
x: 450,
y: 100,
properties: { color: 'red', pin: 8 },
},
{
type: 'wokwi-led',
id: 'led-green',
x: 550,
y: 100,
properties: { color: 'green', pin: 9 },
},
{
type: 'wokwi-led',
id: 'led-blue',
x: 450,
y: 200,
properties: { color: 'blue', pin: 10 },
},
{
type: 'wokwi-led',
id: 'led-yellow',
x: 550,
y: 200,
properties: { color: 'yellow', pin: 11 },
},
{
type: 'wokwi-pushbutton',
id: 'button-red',
x: 450,
y: 300,
properties: {},
},
{
type: 'wokwi-pushbutton',
id: 'button-green',
x: 550,
y: 300,
properties: {},
},
{
type: 'wokwi-pushbutton',
id: 'button-blue',
x: 450,
y: 400,
properties: {},
},
{
type: 'wokwi-pushbutton',
id: 'button-yellow',
x: 550,
y: 400,
properties: {},
},
],
wires: [
{
id: 'wire-led-red',
start: { componentId: 'arduino-uno', pinName: '8' },
end: { componentId: 'led-red', pinName: 'A' },
color: '#ff0000',
},
{
id: 'wire-led-green',
start: { componentId: 'arduino-uno', pinName: '9' },
end: { componentId: 'led-green', pinName: 'A' },
color: '#00ff00',
},
{
id: 'wire-led-blue',
start: { componentId: 'arduino-uno', pinName: '10' },
end: { componentId: 'led-blue', pinName: 'A' },
color: '#0000ff',
},
{
id: 'wire-led-yellow',
start: { componentId: 'arduino-uno', pinName: '11' },
end: { componentId: 'led-yellow', pinName: 'A' },
color: '#ffaa00',
},
{
id: 'wire-button-red',
start: { componentId: 'arduino-uno', pinName: '2' },
end: { componentId: 'button-red', pinName: '1.L' },
color: '#00aaff',
},
{
id: 'wire-button-green',
start: { componentId: 'arduino-uno', pinName: '3' },
end: { componentId: 'button-green', pinName: '1.L' },
color: '#00aaff',
},
{
id: 'wire-button-blue',
start: { componentId: 'arduino-uno', pinName: '4' },
end: { componentId: 'button-blue', pinName: '1.L' },
color: '#00aaff',
},
{
id: 'wire-button-yellow',
start: { componentId: 'arduino-uno', pinName: '5' },
end: { componentId: 'button-yellow', pinName: '1.L' },
color: '#00aaff',
},
],
},
];
// Get examples by category
export function getExamplesByCategory(category: ExampleProject['category']): ExampleProject[] {
return exampleProjects.filter((example) => example.category === category);
}
// Get example by ID
export function getExampleById(id: string): ExampleProject | undefined {
return exampleProjects.find((example) => example.id === id);
}
// Get all categories
export function getCategories(): ExampleProject['category'][] {
return ['basics', 'sensors', 'displays', 'communication', 'games', 'robotics'];
}

View File

@ -0,0 +1,41 @@
/**
* Editor Page Component
*
* Main editor and simulator page
*/
import React from 'react';
import { Link } from 'react-router-dom';
import { CodeEditor } from '../components/editor/CodeEditor';
import { EditorToolbar } from '../components/editor/EditorToolbar';
import { SimulatorCanvas } from '../components/simulator/SimulatorCanvas';
import '../App.css';
export const EditorPage: React.FC = () => {
return (
<div className="app">
<header className="app-header">
<div className="header-content">
<div className="header-title">
<h1>Arduino Emulator</h1>
<p>Local Arduino IDE & Simulator</p>
</div>
<Link to="/examples" className="examples-link">
📚 Browse Examples
</Link>
</div>
</header>
<div className="app-container">
<div className="editor-panel">
<EditorToolbar />
<div className="editor-wrapper">
<CodeEditor />
</div>
</div>
<div className="simulator-panel">
<SimulatorCanvas />
</div>
</div>
</div>
);
};

View File

@ -0,0 +1,66 @@
/**
* Examples Page Component
*
* Displays the examples gallery
*/
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { ExamplesGallery } from '../components/examples/ExamplesGallery';
import { useEditorStore } from '../store/useEditorStore';
import { useSimulatorStore } from '../store/useSimulatorStore';
import type { ExampleProject } from '../data/examples';
export const ExamplesPage: React.FC = () => {
const navigate = useNavigate();
const { setCode } = useEditorStore();
const { setComponents, setWires } = useSimulatorStore();
const handleLoadExample = (example: ExampleProject) => {
console.log('Loading example:', example.title);
// Load the code into the editor
setCode(example.code);
// Load components into the simulator
// Convert component type to metadataId (e.g., 'wokwi-led' -> 'led')
setComponents(
example.components.map((comp) => ({
id: comp.id,
metadataId: comp.type.replace('wokwi-', ''),
x: comp.x,
y: comp.y,
properties: comp.properties,
}))
);
// Load wires (need to convert to full wire format with positions)
// For now, just set empty wires - wire positions will be calculated when components are loaded
const wiresWithPositions = example.wires.map((wire) => ({
id: wire.id,
start: {
componentId: wire.start.componentId,
pinName: wire.start.pinName,
x: 0, // Will be calculated by SimulatorCanvas
y: 0,
},
end: {
componentId: wire.end.componentId,
pinName: wire.end.pinName,
x: 0,
y: 0,
},
color: wire.color,
controlPoints: [],
isValid: true,
signalType: 'digital' as const,
}));
setWires(wiresWithPositions);
// Navigate to the editor
navigate('/');
};
return <ExamplesGallery onLoadExample={handleLoadExample} />;
};

View File

@ -44,12 +44,14 @@ interface SimulatorState {
removeComponent: (id: string) => void;
updateComponent: (id: string, updates: Partial<Component>) => void;
updateComponentState: (id: string, state: boolean) => void;
setComponents: (components: Component[]) => void;
// Wire management (Phase 1)
addWire: (wire: Wire) => void;
removeWire: (wireId: string) => void;
updateWire: (wireId: string, updates: Partial<Wire>) => void;
setSelectedWire: (wireId: string | null) => void;
setWires: (wires: Wire[]) => void;
// Wire creation (Phase 2)
startWireCreation: (endpoint: WireEndpoint) => void;
@ -220,6 +222,10 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
}));
},
setComponents: (components) => {
set({ components });
},
// Wire management actions
addWire: (wire) => {
set((state) => ({
@ -246,6 +252,10 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
set({ selectedWireId: wireId });
},
setWires: (wires) => {
set({ wires });
},
// Wire creation actions (Phase 2)
startWireCreation: (endpoint) => {
set({