feat: implement examples gallery and editor page with routing and example loading functionality
parent
85cb535804
commit
b7e9bf64c4
12
README.md
12
README.md
|
|
@ -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.
|
||||
|
||||
[](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 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"/>
|
||||

|
||||
|
||||
*Interactive component properties dialog and segment-based wire editing*
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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'];
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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} />;
|
||||
};
|
||||
|
|
@ -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({
|
||||
|
|
|
|||
Loading…
Reference in New Issue