Skip to content

Commit

Permalink
Add hidden /learn page with the beginning of a "JavaScript for Scratc…
Browse files Browse the repository at this point in the history
…hers" book
  • Loading branch information
PullJosh committed Jul 8, 2024
1 parent 9e6e132 commit 40ecfc2
Show file tree
Hide file tree
Showing 12 changed files with 493 additions and 31 deletions.
10 changes: 7 additions & 3 deletions app/(content)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import classNames from "classnames";
import { Footer } from "../../components/Footer";
import Nav from "../../components/Nav";
import Nav, { NavLink, NavLinks, NavSpace } from "../../components/Nav";
import TopBorder from "../../components/TopBorder";

interface ContentLayoutProps {
Expand All @@ -11,7 +10,12 @@ export default function ContentLayout({ children }: ContentLayoutProps) {
return (
<div className="flex min-h-screen flex-col">
<TopBorder />
<Nav title="Leopard" />
<Nav title="Leopard">
{/* <NavLinks>
<NavLink href="/learn">Learn</NavLink>
</NavLinks> */}
<NavSpace />
</Nav>
<div className="flex-grow">{children}</div>
<Footer />
</div>
Expand Down
73 changes: 73 additions & 0 deletions app/learn/differences-between-scratch-and-javascript/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { InlineBlock } from "../../../components/InlineBlock";

import dynamic from "next/dynamic";
const ReadonlyCodeDemo = dynamic(
() => import("../../../components/ReadonlyCodeDemo"),
{ ssr: false },
);

export default function LearnPage() {
return (
<div className="prose">
<h1>Major differences between Scratch and JavaScript</h1>
<p className="lead">
Scratch and JavaScript are completely different languages that serve
different purposes, so they work entirely differently.
</p>
<p>
Some Scratch blocks have identical replicas in JavaScript. For example,
the JavaScript code <code>alert(1 + 1)</code> is a lot like the Scratch
code <InlineBlock>say ((1) + (1))</InlineBlock>. But in most cases, the
comparison is not so simple.
</p>

<h3>JavaScript does not have sprites</h3>
<p>
JavaScript is used to add interactive functionality to websites. Instead
of using sprites to display content on screen, websites use a separate
language called HTML to put information on the page. HTML has many
different "elements". For example, the <code>&lt;button&gt;</code>{" "}
element is used to create a button, the <code>&lt;img /&gt;</code>{" "}
element is used to create an image, and the <code>&lt;a&gt;</code>{" "}
element to create a link.
</p>
<p>
JavaScript can take these elements and make them interactive. For
example, the following HTML code sets up a button and the JavaScript
code pops up a message when you click it.
</p>

<ReadonlyCodeDemo
html={`<button id="myButton">Click me</button>`}
js={`const myButton = document.getElementById("myButton");
myButton.addEventListener("click", () => {
alert("You clicked the button!");
});`}
/>

<p>
Additionally, if you want to change the way something looks, you can
style it using <strong className="font-semibold">another</strong>{" "}
language called CSS.
</p>

<ReadonlyCodeDemo
html={`<img\n src="http://leopardjs.com/leopard-logo.svg"\n alt="Leopard Logo"\n/>`}
css={`img {\n rotate: 45deg;\n}`}
previewCSS={`
img {
width: 100px;
margin: 32px 16;
}
`}
/>
<p>
If you're making a web application, this style of programming is
perfect. You can create pages of content with interactive buttons and
everything works wonderfully. But if you're trying to create a game,
this doesn't really work.
</p>
</div>
);
}
121 changes: 121 additions & 0 deletions app/learn/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
"use client";

import { useEffect, useRef, useState } from "react";
import { Footer } from "../../components/Footer";
import Nav, { NavLink, NavLinks, NavSpace } from "../../components/Nav";
import TopBorder from "../../components/TopBorder";

import resolveConfig from "tailwindcss/resolveConfig";
import tailwindConfig from "../../tailwind.config";
import Center from "../../components/Center";
import Link from "next/link";
import { usePathname } from "next/navigation";
import classNames from "classnames";

const { theme } = resolveConfig(tailwindConfig);

interface TOCSection {
title: string;
url: string;
children?: TOCSection[];
}

const tableOfContents: TOCSection = {
title: "JavaScript for Scratchers",
url: "/learn",
children: [
{
title: "Major differences between Scratch and JavaScript",
url: "/learn/differences-between-scratch-and-javascript",
},
],
};

interface LearnLayoutProps {
children: React.ReactNode;
}

export default function LearnLayout({ children }: LearnLayoutProps) {
const width = useWindowWidth();
const xlWidth = Number(theme.screens.xl.replace("px", ""));
const xl = width >= xlWidth;

const scrollRef = useRef<HTMLDivElement>(null);

return (
<div className="flex min-h-screen flex-col xl:grid xl:h-screen xl:grid-cols-[300px,1fr] xl:grid-rows-[auto,1fr] xl:items-stretch xl:overflow-hidden">
<div className="sticky top-0 z-30 xl:static xl:col-span-full">
<TopBorder />
<Nav
title="Leopard"
width={xl ? "full" : "default"}
scrollRef={xl ? scrollRef : undefined}
>
<NavLinks>
<NavLink href="/learn">Learn</NavLink>
</NavLinks>
<NavSpace />
</Nav>
</div>
{xl && (
<nav className="border-r border-gray-300 bg-white">
<TableOfContents />
</nav>
)}
<div className="flex flex-grow flex-col xl:overflow-auto" ref={scrollRef}>
<Center className="flex-grow">
{!xl && (
<nav className="mt-8 rounded-lg border border-gray-300 bg-white">
<TableOfContents />
</nav>
)}
<div className="mb-auto py-8">{children}</div>
</Center>
<Footer />
</div>
</div>
);
}

function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);

useEffect(() => {
function handleResize() {
setWidth(window.innerWidth);
}
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);

return width;
}

function TableOfContents() {
const pathname = usePathname();

return (
<div className="p-4">
<h2
className={classNames(
"mb-1 truncate text-lg font-semibold hover:underline",
{ "text-indigo-800": pathname === tableOfContents.url },
)}
>
<Link href={tableOfContents.url}>{tableOfContents.title}</Link>
</h2>
<ul>
{tableOfContents.children?.map((section, i) => (
<li
key={i}
className={classNames("truncate text-sm hover:underline", {
"text-indigo-800": pathname === section.url,
})}
>
<Link href={section.url}>{section.title}</Link>
</li>
))}
</ul>
</div>
);
}
42 changes: 42 additions & 0 deletions app/learn/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import Link from "next/link";

export default function LearnPage() {
return (
<>
<div className="prose">
<h1>JavaScript for Scratchers</h1>
<p className="lead">
If you already know Scratch, you might be ready to level up your
programming skills and learn JavaScript.
</p>

<h2>Why learn JavaScript?</h2>
<p>
Scratch is extremely limited. Your project is confined to a 480x360
window, you can only use the blocks that are provided to you, and
Scratch's variables, lists, and custom blocks are not powerful enough
to easily build great projects.
</p>
<p>
In contrast, JavaScript is the most popular programming language in
the world and it is vastly more capable. JavaScript is the coding
language that is used to power the interactive parts of every webpage
on the internet. And that's a big deal, because more and more of our
apps run in web browsers every year. Even many native apps are{" "}
<Link href="https://www.electronjs.org/">
powered by web technologies
</Link>
.
</p>
<p>
If you want to make a game, app, or website, JavaScript is an
excellent tool for the job. This tutorial will teach you how.
</p>

<Link href="/learn/differences-between-scratch-and-javascript">
Up next: Differences between Scratch and JavaScript
</Link>
</div>
</>
);
}
18 changes: 16 additions & 2 deletions components/Center.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,25 @@ import classNames from "classnames";
interface CenterProps {
children: React.ReactNode;
className?: string;
width?: "default" | "wide";
}

export default function Center({ children, className }: CenterProps) {
export default function Center({
children,
className,
width = "default",
}: CenterProps) {
return (
<div className={classNames("mx-auto max-w-4xl px-8", className)}>
<div
className={classNames(
"mx-auto px-8",
{
"max-w-4xl": width === "default",
"max-w-6xl": width === "wide",
},
className,
)}
>
{children}
</div>
);
Expand Down
15 changes: 11 additions & 4 deletions components/Code.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import hljs from "highlight.js/lib/core";
import html from "highlight.js/lib/languages/xml";
import css from "highlight.js/lib/languages/css";
import javascript from "highlight.js/lib/languages/javascript";

hljs.registerLanguage("html", html);
hljs.registerLanguage("css", css);
hljs.registerLanguage("javascript", javascript);

import "./Code.css";
Expand All @@ -9,12 +13,15 @@ import classNames from "classnames";
interface CodeProps {
children: string;
className?: string;
language?: "html" | "css" | "javascript";
}

export function Code({ children, className }: CodeProps) {
const highlightedCode = hljs.highlight(children, {
language: "javascript",
}).value;
export function Code({
children,
className,
language = "javascript",
}: CodeProps) {
const highlightedCode = hljs.highlight(children, { language }).value;

return (
<pre
Expand Down
17 changes: 17 additions & 0 deletions components/InlineBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"use client";

import dynamic from "next/dynamic";

const ScratchBlocks = dynamic(() => import("./ScratchBlocks"), { ssr: false });

interface InlineBlockProps {
children: string;
}

export function InlineBlock({ children }: InlineBlockProps) {
return (
<ScratchBlocks inline={true} scale={0.6} className="-my-3 align-middle">
{children}
</ScratchBlocks>
);
}
30 changes: 28 additions & 2 deletions components/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ import { RemixIcon } from "./RemixIcon";
import { ErrorIcon } from "./icons/ErrorIcon";
import { useRouter } from "next/navigation";
import { ProjectTitleEditor } from "./ProjectTitleEditor";
import { useWindowScrollPosition } from "../lib/useWindowScrollPosition";
import { useScrollPosition } from "../lib/useScrollPosition";

interface NavProps {
width?: "default" | "wide" | "full";
title?: string | null;
titleHref?: string;
children?: React.ReactNode;
scrollRef?: React.RefObject<HTMLElement>;
}

const NavContext = createContext<{
Expand All @@ -34,8 +35,9 @@ export default function Nav({
title = null,
titleHref = "/",
children = <NavSpace />,
scrollRef,
}: NavProps) {
const scrollY = useWindowScrollPosition();
const scrollY = useScrollPosition(scrollRef);

return (
<NavContext.Provider value={{ width }}>
Expand Down Expand Up @@ -113,6 +115,30 @@ export function NavSpace() {
return <div className="flex-grow" />;
}

interface NavLinksProps {
children: React.ReactNode;
}

export function NavLinks({ children }: NavLinksProps) {
return <div className="flex items-stretch px-4">{children}</div>;
}

interface NavLinkProps {
href: string;
children: React.ReactNode;
}

export function NavLink({ href, children }: NavLinkProps) {
return (
<Link
href={href}
className="flex items-center rounded-md px-3 text-gray-700 hover:underline"
>
{children}
</Link>
);
}

interface NavProjectDescriptionProps {
id: string;
title: string;
Expand Down
Loading

0 comments on commit 40ecfc2

Please sign in to comment.