From 64564a6fee02708d375a349d75ce49d515e66f8d Mon Sep 17 00:00:00 2001 From: schererleander Date: Wed, 25 Jun 2025 01:00:49 +0200 Subject: add markdown-based blog --- src/App.tsx | 4 +++ src/blog/3dprint.md | 66 +++++++++++++++++++++++++++++++++++++++++ src/components/Navbar.tsx | 2 +- src/pages/Blog.tsx | 50 +++++++++++++++++++++++++++++++ src/pages/Post.tsx | 75 +++++++++++++++++++++++++++++++++++++++++++++++ src/vite-env.d.ts | 4 +++ 6 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 src/blog/3dprint.md create mode 100644 src/pages/Blog.tsx create mode 100644 src/pages/Post.tsx (limited to 'src') diff --git a/src/App.tsx b/src/App.tsx index c6e88bd..60de46b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,6 +7,8 @@ import HomelabPage from './pages/Homelab'; import Footer from './components/Footer'; import PrintingPage from './pages/Printing'; import NotFoundPage from './pages/404Page'; +import Blog from './pages/Blog'; +import Post from './pages/Post'; function App() { @@ -20,6 +22,8 @@ function App() { } /> } /> } /> + } /> + } /> } /> diff --git a/src/blog/3dprint.md b/src/blog/3dprint.md new file mode 100644 index 0000000..ab3cf28 --- /dev/null +++ b/src/blog/3dprint.md @@ -0,0 +1,66 @@ +--- +title: "3D Printing" +date: "2025-06-25" +excerpt: "My 3D-printing projects: from a robotic arm to a DIY drone – including the ESP32 controller code." +cover: "/src/assets/a1.webp" +--- + +# Projects + +## Robotic Arm + +[3D model on MakerWorld](https://makerworld.com/en/models/528885-robotic-arm#profileId-445995) – modified to work with my servo motors. + +```cpp +#include +#include + +#define DEADZONE 30 +#define BASE_PIN 15 +#define SHOULDER_PIN 2 +#define ELBOW_PIN 4 +#define WRIST_PIN 16 +#define HAND_PIN 17 + +Servo base, shoulder, elbow, wrist, hand; +ControllerPtr pad; + +void onConnectedGamepad(ControllerPtr ctl) { + Serial.printf("Gamepad #%d connected\n", ctl->index()); + pad = ctl; +} + +void onDisconnectedGamepad(ControllerPtr ctl) { + Serial.printf("Gamepad disconnected\n"); +} + +int16_t mapAxis(int16_t v) { + if (abs(v) < DEADZONE) v = 0; + return map(v, -512, 512, 0, 180); +} + +void setup() { + BP32.setup(&onConnectedGamepad, &onDisconnectedGamepad); + BP32.enableNewBluetoothConnections(true); + + base.attach(BASE_PIN); + shoulder.attach(SHOULDER_PIN); + elbow.attach(ELBOW_PIN); + wrist.attach(WRIST_PIN); + hand.attach(HAND_PIN); +} + +void loop() { + BP32.update(); + if (pad && pad->isConnected()) { + base.write(mapAxis(pad->axisX())); + shoulder.write(mapAxis(-pad->axisY())); + elbow.write(mapAxis(-pad->axisRY())); + hand.write(mapAxis(pad->throttle() - pad->brake())); + + if (pad->l1()) wrist.write(0); + else if (pad->r1()) wrist.write(180); + else wrist.write(90); + } + delay(15); +} \ No newline at end of file diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 4246508..fd1b415 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -7,7 +7,7 @@ export default function Navbar() { { label: 'Gear', href: '/gear' }, { label: 'Projects', href: '/projects' }, { label: 'Homelab', href: '/homelab' }, - { label: '3D Printing', href: '/printing' } + { label: 'Blog', href: '/blog' } ]; return ( diff --git a/src/pages/Blog.tsx b/src/pages/Blog.tsx new file mode 100644 index 0000000..5db87c8 --- /dev/null +++ b/src/pages/Blog.tsx @@ -0,0 +1,50 @@ +import { useMemo } from "react"; +import matter from "gray-matter"; +import { Link } from "react-router-dom"; +import CardLink from "../components/CardLink"; + +interface PostMeta { + slug: string; + title: string; + date: string; + excerpt: string; + cover?: string; +} + +export default function Blog() { + const posts = useMemo(() => { + const files = import.meta.glob("../blog/*.md", { + eager: true, + query: "?raw", + import: "default", + }) as Record; + + return Object.entries(files) + .map(([path, raw]) => { + const { data } = matter(raw); + const slug = path.split("/").pop()!.replace(".md", ""); + return { slug, ...data } as PostMeta; + }) + .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); + }, []); + + return ( +
+

Blog

+ +
+ {posts.map((post) => ( + /* 1) Link für internes Routing */ + /* 2) CardLink bekommt genau deine Prop-Namen */ + + + + ))} +
+
+ ); +} diff --git a/src/pages/Post.tsx b/src/pages/Post.tsx new file mode 100644 index 0000000..17022d2 --- /dev/null +++ b/src/pages/Post.tsx @@ -0,0 +1,75 @@ +import { useEffect, useState } from "react"; +import { useParams, Link } from "react-router-dom"; +import matter from "gray-matter"; +import ReactMarkdown from "react-markdown"; +import CodeSnippet from "../components/CodeSnippet"; +import LinkWithIcon from "../components/LinkWithIcon"; +import NotFoundPage from "./404Page"; + +interface PostMeta { + title: string; + date: string; + cover?: string; +} + +export default function Post() { + const { slug } = useParams<{ slug: string }>(); + const [meta, setMeta] = useState(null); + const [content, setContent] = useState(""); + const [notFound, setNotFound] = useState(false); + + useEffect(() => { + import(`../blog/${slug}.md?raw`) + .then((m) => { + const { data, content } = matter(m.default); + setMeta(data as PostMeta); + setContent(content); + }) + .catch(() => setNotFound(true)); + }, [slug]); + + if (!meta) return null; + if (notFound) return ; + + return ( +
+ + ← Zurück zum Blog + + + {meta.cover && ( + {meta.title} + )} + +

{meta.title}

+

+ {new Date(meta.date).toLocaleDateString("de-DE")} +

+ +
+ ; + }, + + a({ href = "", children, ...props }) { + return ( + + {children} + + ); + }, + }} + > + {content} + +
+
+ ); +} \ No newline at end of file diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 11f02fe..913581e 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -1 +1,5 @@ /// +declare module '*.md?raw' { + const content: string; + export default content; +} -- cgit v1.3.1