/** * ExampleEditorPage — route `/example/:exampleId`. * * Paralelo a ProjectByIdPage (`/project/`) but for the built-in * example projects. Loads the example into the editor + simulator * stores AND keeps the URL pinned to `/example/` while the user * runs / edits. That makes example links: * * - Shareable: copy the URL, send it, recipient lands on the same * example pre-loaded. * - Bookmarkable: a tab title and back-button history that point * at the example, not at a generic `/editor`. * - SEO-friendly: each example gets its own URL the same way * /examples/ already gave it a landing page. The two co- * exist on purpose — `/examples/` (plural) is the marketing * landing with preview + description, `/example/` (singular) * is the live editor with the example pre-loaded. * * If the user starts editing and clicks "Save", the pro overlay's * save modal asks for a name and creates a NEW project (no project * id is set on useProjectStore, so it can't overwrite anything). */ import { useEffect, useRef, useState } from 'react'; import { useParams } from 'react-router-dom'; import { exampleProjects } from '../data/examples'; import { loadExample, type LibraryInstallProgress } from '../utils/loadExample'; import { EditorPage } from './EditorPage'; import { AppHeader } from '../components/layout/AppHeader'; import { useSEO } from '../utils/useSEO'; const DOMAIN = 'https://velxio.dev'; export const ExampleEditorPage: React.FC = () => { const { exampleId } = useParams<{ exampleId: string }>(); const [ready, setReady] = useState(false); const [error, setError] = useState(false); const [installing, setInstalling] = useState(null); // Guard so React strict-mode (which fires effects twice in dev) doesn't // run loadExample twice — and so the user can keep editing without the // example reloading on every store-triggered re-render. const loadedIdRef = useRef(null); const example = exampleId ? exampleProjects.find((e) => e.id === exampleId) : null; useSEO({ title: example ? `${example.title} — Velxio Arduino Simulator` : 'Example — Velxio', description: example?.description ?? 'Arduino example running on Velxio.', url: example ? `${DOMAIN}/example/${example.id}` : `${DOMAIN}/examples`, }); useEffect(() => { if (!exampleId) { setError(true); return; } if (!example) { setError(true); return; } if (loadedIdRef.current === exampleId) return; loadedIdRef.current = exampleId; let cancelled = false; setReady(false); setError(false); (async () => { try { await loadExample(example, setInstalling); } catch { // loadExample's internal failures (library install network errors) // are swallowed inside ensureLibraries — anything that DOES bubble // up here means the stores are partially populated. Surfacing a // clean error is more useful than rendering an empty editor. if (!cancelled) setError(true); return; } if (!cancelled) setReady(true); })(); return () => { cancelled = true; }; }, [exampleId, example]); if (error) { return (
404
Example "{exampleId}" not found.
Browse all examples
); } if (!ready) { return (
Loading example…
{installing && (
Installing {installing.current} ({installing.done + 1}/{installing.total})
)}
); } return ; };