Initial commit

This commit is contained in:
Daniel Flanagan 2022-09-27 14:41:17 -05:00
commit 3d33e72f42
Signed by untrusted user: lytedev-divvy
GPG Key ID: 6D69CEEE4ABBCD82
24 changed files with 339 additions and 0 deletions

11
README.md Normal file
View File

@ -0,0 +1,11 @@
# fresh project
### Usage
Start the project:
```
deno task start
```
This will watch the project directory and restart as necessary.

12
components/Button.tsx Normal file
View File

@ -0,0 +1,12 @@
import { JSX } from "preact";
import { IS_BROWSER } from "$fresh/runtime.ts";
export function Button(props: JSX.HTMLAttributes<HTMLButtonElement>) {
return (
<button
{...props}
disabled={!IS_BROWSER || props.disabled}
class="px-2 py-1 border(gray-100 2) hover:bg-gray-200"
/>
);
}

10
deno.json Normal file
View File

@ -0,0 +1,10 @@
{
"tasks": {
"start": "deno run -A --watch=static/,routes/ dev.ts"
},
"importMap": "./import_map.json",
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact"
}
}

5
dev.ts Executable file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env -S deno run -A --watch=static/,routes/
import dev from "$fresh/dev.ts";
await dev(import.meta.url, "./main.ts");

44
fresh.gen.ts Normal file
View File

@ -0,0 +1,44 @@
// DO NOT EDIT. This file is generated by fresh.
// This file SHOULD be checked into source version control.
// This file is automatically updated during development when running `dev.ts`.
import config from "./deno.json" assert { type: "json" };
import * as $0 from "./routes/[name].tsx";
import * as $1 from "./routes/_404.tsx";
import * as $2 from "./routes/_500.tsx";
import * as $3 from "./routes/_middleware.ts";
import * as $4 from "./routes/about.tsx";
import * as $5 from "./routes/api/joke.ts";
import * as $6 from "./routes/api/random-uuid.ts";
import * as $7 from "./routes/countdown.tsx";
import * as $8 from "./routes/github/[username].tsx";
import * as $9 from "./routes/index.tsx";
import * as $10 from "./routes/route-config-example.tsx";
import * as $11 from "./routes/search.tsx";
import * as $$0 from "./islands/Countdown.tsx";
import * as $$1 from "./islands/Counter.tsx";
const manifest = {
routes: {
"./routes/[name].tsx": $0,
"./routes/_404.tsx": $1,
"./routes/_500.tsx": $2,
"./routes/_middleware.ts": $3,
"./routes/about.tsx": $4,
"./routes/api/joke.ts": $5,
"./routes/api/random-uuid.ts": $6,
"./routes/countdown.tsx": $7,
"./routes/github/[username].tsx": $8,
"./routes/index.tsx": $9,
"./routes/route-config-example.tsx": $10,
"./routes/search.tsx": $11,
},
islands: {
"./islands/Countdown.tsx": $$0,
"./islands/Counter.tsx": $$1,
},
baseUrl: import.meta.url,
config,
};
export default manifest;

12
import_map.json Normal file
View File

@ -0,0 +1,12 @@
{
"imports": {
"$fresh/": "https://deno.land/x/fresh@1.1.1/",
"preact": "https://esm.sh/preact@10.11.0",
"preact/": "https://esm.sh/preact@10.11.0/",
"preact-render-to-string": "https://esm.sh/*preact-render-to-string@5.2.4",
"@preact/signals": "https://esm.sh/*@preact/signals@1.0.3",
"@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.0.1",
"twind": "https://esm.sh/twind@0.16.17",
"twind/": "https://esm.sh/twind@0.16.17/"
}
}

27
islands/Countdown.tsx Normal file
View File

@ -0,0 +1,27 @@
import { useEffect, useState } from "preact/hooks";
const timeFmt = new Intl.RelativeTimeFormat("en-US");
export default function Countdown(props: { target: string }) {
const target = new Date(props.target);
const [now, setNow] = useState(new Date());
useEffect(() => {
const timer = setInterval(() => {
setNow((now) => {
if (now > target) {
clearInterval(timer);
}
return new Date();
});
}, 1000);
return () => clearInterval(timer);
}, [props.target]);
if (now > target) {
return <span>🎉</span>;
}
const secondsLeft = Math.ceil((target.getTime() - now.getTime()) / 1000);
return <span>{timeFmt.format(secondsLeft, "seconds")}</span>;
}

17
islands/Counter.tsx Normal file
View File

@ -0,0 +1,17 @@
import { useState } from "preact/hooks";
import { Button } from "../components/Button.tsx";
interface CounterProps {
start: number;
}
export default function Counter(props: CounterProps) {
const [count, setCount] = useState(props.start);
return (
<div class="flex gap-2 w-full">
<p class="flex-grow-1 font-bold text-xl">{count}</p>
<Button onClick={() => setCount(count - 1)}>-1</Button>
<Button onClick={() => setCount(count + 1)}>+1</Button>
</div>
);
}

13
main.ts Normal file
View File

@ -0,0 +1,13 @@
/// <reference no-default-lib="true" />
/// <reference lib="dom" />
/// <reference lib="dom.iterable" />
/// <reference lib="dom.asynciterable" />
/// <reference lib="deno.ns" />
import { start } from "$fresh/server.ts";
import manifest from "./fresh.gen.ts";
import twindPlugin from "$fresh/plugins/twind.ts";
import twindConfig from "./twind.config.ts";
await start(manifest, { plugins: [twindPlugin(twindConfig)] });

5
routes/[name].tsx Normal file
View File

@ -0,0 +1,5 @@
import { PageProps } from "$fresh/server.ts";
export default function Greet(props: PageProps) {
return <div>Hello {props.params.name}</div>;
}

5
routes/_404.tsx Normal file
View File

@ -0,0 +1,5 @@
import { UnknownPageProps } from "$fresh/server.ts";
export default function NotFoundPage({ url }: UnknownPageProps) {
return <p>404 not found: {url.pathname}</p>;
}

5
routes/_500.tsx Normal file
View File

@ -0,0 +1,5 @@
import { ErrorPageProps } from "$fresh/server.ts";
export default function Error500Page({ error }: ErrorPageProps) {
return <p>500 internal error: {(error as Error).message}</p>;
}

15
routes/_middleware.ts Normal file
View File

@ -0,0 +1,15 @@
import { MiddlewareHandlerContext } from "$fresh/server.ts";
interface State {
data: string;
}
export async function handler(
_: Request,
ctx: MiddlewareHandlerContext<State>,
) {
ctx.state.data = "myData";
const resp = await ctx.next();
resp.headers.set("server", "fresh server");
return resp;
}

8
routes/about.tsx Normal file
View File

@ -0,0 +1,8 @@
export default function AboutPage() {
return (
<main>
<h1>About</h1>
<p>This is the about page.</p>
</main>
);
}

21
routes/api/joke.ts Normal file
View File

@ -0,0 +1,21 @@
import { HandlerContext } from "$fresh/server.ts";
// Jokes courtesy of https://punsandoneliners.com/randomness/programmer-jokes/
const JOKES = [
"Why do Java developers often wear glasses? They can't C#.",
"A SQL query walks into a bar, goes up to two tables and says “can I join you?”",
"Wasn't hard to crack Forrest Gump's password. 1forrest1.",
"I love pressing the F5 key. It's refreshing.",
"Called IT support and a chap from Australia came to fix my network connection. I asked “Do you come from a LAN down under?”",
"There are 10 types of people in the world. Those who understand binary and those who don't.",
"Why are assembly programmers often wet? They work below C level.",
"My favourite computer based band is the Black IPs.",
"What programme do you use to predict the music tastes of former US presidential candidates? An Al Gore Rhythm.",
"An SEO expert walked into a bar, pub, inn, tavern, hostelry, public house.",
];
export const handler = (_req: Request, _ctx: HandlerContext): Response => {
const randomIndex = Math.floor(Math.random() * JOKES.length);
const body = JOKES[randomIndex];
return new Response(body);
};

10
routes/api/random-uuid.ts Normal file
View File

@ -0,0 +1,10 @@
import { Handlers } from "$fresh/server.ts";
export const handler: Handlers = {
GET(_: Request) {
const uuid = crypto.randomUUID();
return new Response(JSON.stringify(uuid), {
headers: { "Content-Type": "application/json" },
});
},
};

11
routes/countdown.tsx Normal file
View File

@ -0,0 +1,11 @@
import Countdown from "../islands/Countdown.tsx";
export default function Page() {
const date = new Date();
date.setSeconds(date.getSeconds() + 10);
return (
<p>
The big event is happening <Countdown target={date.toISOString()} />.
</p>
);
}

View File

@ -0,0 +1,33 @@
import { Handlers, PageProps } from "$fresh/server.ts";
interface User {
login: string;
name: string;
avatar_url: string;
}
export const handler: Handlers<User | null> = {
async GET(_, ctx) {
const { username } = ctx.params;
const resp = await fetch(`https://api.github.com/users/${username}`);
if (resp.status === 404) {
return ctx.render(null);
}
const user: User = await resp.json();
return ctx.render(user);
},
};
export default function Page({ data }: PageProps<User | null>) {
if (!data) {
return <h1>User not found</h1>;
}
return (
<div>
<img src={data.avatar_url} width={64} height={64} />
<h1>{data.name}</h1>
<p>{data.login}</p>
</div>
);
}

18
routes/index.tsx Normal file
View File

@ -0,0 +1,18 @@
import Counter from "../islands/Counter.tsx";
export default function Home() {
return (
<div class="p-4 mx-auto max-w-screen-md">
<img
src="/logo.svg"
class="w-32 h-32"
alt="the fresh logo: a sliced lemon dripping with juice"
/>
<p class="my-6">
Welcome to `fresh`. Try updating this message in the ./routes/index.tsx
file, and refresh.
</p>
<Counter start={3} />
</div>
);
}

View File

@ -0,0 +1,14 @@
import { RouteConfig } from "$fresh/server.ts";
export const config: RouteConfig = {
routeOverride: "/route-override-example",
};
export default function AboutPage() {
return (
<main>
<h1>RouteConfigExample</h1>
<p>This is the RouteConfigExample page. It should only be accessible from /route-override-example</p>
</main>
);
}

32
routes/search.tsx Normal file
View File

@ -0,0 +1,32 @@
import { Handlers, PageProps } from "$fresh/server.ts";
const NAMES = ["Alice", "Bob", "Charlie", "Dave", "Eve", "Frank"];
interface Data {
results: string[];
query: string;
}
export const handler: Handlers<Data> = {
GET(req, ctx) {
const url = new URL(req.url);
const query = url.searchParams.get("q") || "";
const results = NAMES.filter((name) => name.includes(query));
return ctx.render({ results, query });
},
};
export default function Page({ data }: PageProps<Data>) {
const { results, query } = data;
return (
<div>
<form>
<input type="text" name="q" value={query} />
<button type="submit">Search</button>
</form>
<ul>
{results.map((name) => <li key={name}>{name}</li>)}
</ul>
</div>
);
}

BIN
static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

6
static/logo.svg Normal file
View File

@ -0,0 +1,6 @@
<svg width="40" height="40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M34.092 8.845C38.929 20.652 34.092 27 30 30.5c1 3.5-2.986 4.222-4.5 2.5-4.457 1.537-13.512 1.487-20-5C2 24.5 4.73 16.714 14 11.5c8-4.5 16-7 20.092-2.655Z" fill="#FFDB1E"/>
<path d="M14 11.5c6.848-4.497 15.025-6.38 18.368-3.47C37.5 12.5 21.5 22.612 15.5 25c-6.5 2.587-3 8.5-6.5 8.5-3 0-2.5-4-5.183-7.75C2.232 23.535 6.16 16.648 14 11.5Z" fill="#fff" stroke="#FFDB1E"/>
<path d="M28.535 8.772c4.645 1.25-.365 5.695-4.303 8.536-3.732 2.692-6.606 4.21-7.923 4.83-.366.173-1.617-2.252-1.617-1 0 .417-.7 2.238-.934 2.326-1.365.512-4.223 1.29-5.835 1.29-3.491 0-1.923-4.754 3.014-9.122.892-.789 1.478-.645 2.283-.645-.537-.773-.534-.917.403-1.546C17.79 10.64 23 8.77 25.212 8.42c.366.014.82.35.82.629.41-.14 2.095-.388 2.503-.278Z" fill="#FFE600"/>
<path d="M14.297 16.49c.985-.747 1.644-1.01 2.099-2.526.566.121.841-.08 1.29-.701.324.466 1.657.608 2.453.701-.715.451-1.057.852-1.452 2.106-1.464-.611-3.167-.302-4.39.42Z" fill="#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

5
twind.config.ts Normal file
View File

@ -0,0 +1,5 @@
import { Options } from "$fresh/plugins/twind.ts";
export default {
selfURL: import.meta.url,
} as Options;