From 9f28b6d9160fee5eff92d1d9849191f2f12faeab Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 14 Feb 2022 16:33:38 +0000 Subject: Initial commit --- src/app.css | 107 ++++++++++++++++++++++++ src/app.d.ts | 15 ++++ src/app.html | 13 +++ src/hooks.ts | 24 ++++++ src/lib/Counter.svelte | 102 ++++++++++++++++++++++ src/lib/form.ts | 84 +++++++++++++++++++ src/lib/header/Header.svelte | 124 +++++++++++++++++++++++++++ src/lib/header/svelte-logo.svg | 1 + src/routes/__layout.svelte | 45 ++++++++++ src/routes/about.svelte | 50 +++++++++++ src/routes/index.svelte | 59 +++++++++++++ src/routes/todos/_api.ts | 22 +++++ src/routes/todos/index.svelte | 186 +++++++++++++++++++++++++++++++++++++++++ src/routes/todos/index.ts | 52 ++++++++++++ 14 files changed, 884 insertions(+) create mode 100644 src/app.css create mode 100644 src/app.d.ts create mode 100644 src/app.html create mode 100644 src/hooks.ts create mode 100644 src/lib/Counter.svelte create mode 100644 src/lib/form.ts create mode 100644 src/lib/header/Header.svelte create mode 100644 src/lib/header/svelte-logo.svg create mode 100644 src/routes/__layout.svelte create mode 100644 src/routes/about.svelte create mode 100644 src/routes/index.svelte create mode 100644 src/routes/todos/_api.ts create mode 100644 src/routes/todos/index.svelte create mode 100644 src/routes/todos/index.ts (limited to 'src') diff --git a/src/app.css b/src/app.css new file mode 100644 index 0000000..77bf6af --- /dev/null +++ b/src/app.css @@ -0,0 +1,107 @@ +@import '@fontsource/fira-mono'; + +:root { + font-family: Arial, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, + Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + --font-mono: 'Fira Mono', monospace; + --pure-white: #ffffff; + --primary-color: #b9c6d2; + --secondary-color: #d0dde9; + --tertiary-color: #edf0f8; + --accent-color: #ff3e00; + --heading-color: rgba(0, 0, 0, 0.7); + --text-color: #444444; + --background-without-opacity: rgba(255, 255, 255, 0.7); + --column-width: 42rem; + --column-margin-top: 4rem; +} + +body { + min-height: 100vh; + margin: 0; + background-color: var(--primary-color); + background: linear-gradient( + 180deg, + var(--primary-color) 0%, + var(--secondary-color) 10.45%, + var(--tertiary-color) 41.35% + ); +} + +body::before { + content: ''; + width: 80vw; + height: 100vh; + position: absolute; + top: 0; + left: 10vw; + z-index: -1; + background: radial-gradient( + 50% 50% at 50% 50%, + var(--pure-white) 0%, + rgba(255, 255, 255, 0) 100% + ); + opacity: 0.05; +} + +#svelte { + min-height: 100vh; + display: flex; + flex-direction: column; +} + +h1, +h2, +p { + font-weight: 400; + color: var(--heading-color); +} + +p { + line-height: 1.5; +} + +a { + color: var(--accent-color); + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +h1 { + font-size: 2rem; + text-align: center; +} + +h2 { + font-size: 1rem; +} + +pre { + font-size: 16px; + font-family: var(--font-mono); + background-color: rgba(255, 255, 255, 0.45); + border-radius: 3px; + box-shadow: 2px 2px 6px rgb(255 255 255 / 25%); + padding: 0.5em; + overflow-x: auto; + color: var(--text-color); +} + +input, +button { + font-size: inherit; + font-family: inherit; +} + +button:focus:not(:focus-visible) { + outline: none; +} + +@media (min-width: 720px) { + h1 { + font-size: 2.4rem; + } +} diff --git a/src/app.d.ts b/src/app.d.ts new file mode 100644 index 0000000..3ddb22d --- /dev/null +++ b/src/app.d.ts @@ -0,0 +1,15 @@ +/// + +// See https://kit.svelte.dev/docs/typescript +// for information about these interfaces +declare namespace App { + interface Locals { + userid: string; + } + + interface Platform {} + + interface Session {} + + interface Stuff {} +} diff --git a/src/app.html b/src/app.html new file mode 100644 index 0000000..e7d2cbd --- /dev/null +++ b/src/app.html @@ -0,0 +1,13 @@ + + + + + + + + %svelte.head% + + +
%svelte.body%
+ + diff --git a/src/hooks.ts b/src/hooks.ts new file mode 100644 index 0000000..d767555 --- /dev/null +++ b/src/hooks.ts @@ -0,0 +1,24 @@ +import cookie from 'cookie'; +import { v4 as uuid } from '@lukeed/uuid'; +import type { Handle } from '@sveltejs/kit'; + +export const handle: Handle = async ({ event, resolve }) => { + const cookies = cookie.parse(event.request.headers.get('cookie') || ''); + event.locals.userid = cookies.userid || uuid(); + + const response = await resolve(event); + + if (!cookies.userid) { + // if this is the first time the user has visited this app, + // set a cookie so that we recognise them when they return + response.headers.set( + 'set-cookie', + cookie.serialize('userid', event.locals.userid, { + path: '/', + httpOnly: true + }) + ); + } + + return response; +}; diff --git a/src/lib/Counter.svelte b/src/lib/Counter.svelte new file mode 100644 index 0000000..0df76f5 --- /dev/null +++ b/src/lib/Counter.svelte @@ -0,0 +1,102 @@ + + +
+ + +
+
+ + {Math.floor($displayed_count)} +
+
+ + +
+ + diff --git a/src/lib/form.ts b/src/lib/form.ts new file mode 100644 index 0000000..787a397 --- /dev/null +++ b/src/lib/form.ts @@ -0,0 +1,84 @@ +import { invalidate } from '$app/navigation'; + +// this action (https://svelte.dev/tutorial/actions) allows us to +// progressively enhance a
that already works without JS +export function enhance( + form: HTMLFormElement, + { + pending, + error, + result + }: { + pending?: ({ data, form }: { data: FormData; form: HTMLFormElement }) => void; + error?: ({ + data, + form, + response, + error + }: { + data: FormData; + form: HTMLFormElement; + response: Response | null; + error: Error | null; + }) => void; + result?: ({ + data, + form, + response + }: { + data: FormData; + response: Response; + form: HTMLFormElement; + }) => void; + } = {} +): { destroy: () => void } { + let current_token: unknown; + + async function handle_submit(e: Event) { + const token = (current_token = {}); + + e.preventDefault(); + + const data = new FormData(form); + + if (pending) pending({ data, form }); + + try { + const response = await fetch(form.action, { + method: form.method, + headers: { + accept: 'application/json' + }, + body: data + }); + + if (token !== current_token) return; + + if (response.ok) { + if (result) result({ data, form, response }); + + const url = new URL(form.action); + url.search = url.hash = ''; + invalidate(url.href); + } else if (error) { + error({ data, form, error: null, response }); + } else { + console.error(await response.text()); + } + } catch (e: any) { + if (error) { + error({ data, form, error: e, response: null }); + } else { + throw e; + } + } + } + + form.addEventListener('submit', handle_submit); + + return { + destroy() { + form.removeEventListener('submit', handle_submit); + } + }; +} diff --git a/src/lib/header/Header.svelte b/src/lib/header/Header.svelte new file mode 100644 index 0000000..9d3120f --- /dev/null +++ b/src/lib/header/Header.svelte @@ -0,0 +1,124 @@ + + +
+
+ + SvelteKit + +
+ + + +
+ +
+
+ + diff --git a/src/lib/header/svelte-logo.svg b/src/lib/header/svelte-logo.svg new file mode 100644 index 0000000..49492a8 --- /dev/null +++ b/src/lib/header/svelte-logo.svg @@ -0,0 +1 @@ +svelte-logo \ No newline at end of file diff --git a/src/routes/__layout.svelte b/src/routes/__layout.svelte new file mode 100644 index 0000000..7fef681 --- /dev/null +++ b/src/routes/__layout.svelte @@ -0,0 +1,45 @@ + + +
+ +
+ +
+ + + + diff --git a/src/routes/about.svelte b/src/routes/about.svelte new file mode 100644 index 0000000..569d3e1 --- /dev/null +++ b/src/routes/about.svelte @@ -0,0 +1,50 @@ + + + + About + + +
+

About this app

+ +

+ This is a SvelteKit app. You can make your own by typing the + following into your command line and following the prompts: +

+ + +
npm init svelte@next
+ +

+ The page you're looking at is purely static HTML, with no client-side interactivity needed. + Because of that, we don't need to load any JavaScript. Try viewing the page's source, or opening + the devtools network panel and reloading. +

+ +

+ The TODOs page illustrates SvelteKit's data loading and form handling. Try using + it with JavaScript disabled! +

+
+ + diff --git a/src/routes/index.svelte b/src/routes/index.svelte new file mode 100644 index 0000000..68311dd --- /dev/null +++ b/src/routes/index.svelte @@ -0,0 +1,59 @@ + + + + + + Home + + +
+

+
+ + + Welcome + +
+ + to your new
SvelteKit app +

+ +

+ try editing src/routes/index.svelte +

+ + +
+ + diff --git a/src/routes/todos/_api.ts b/src/routes/todos/_api.ts new file mode 100644 index 0000000..f8bcf73 --- /dev/null +++ b/src/routes/todos/_api.ts @@ -0,0 +1,22 @@ +/* + This module is used by the /todos and /todos/[uid] + endpoints to make calls to api.svelte.dev, which stores todos + for each user. The leading underscore indicates that this is + a private module, _not_ an endpoint — visiting /todos/_api + will net you a 404 response. + + (The data on the todo app will expire periodically; no + guarantees are made. Don't use it to organise your life.) +*/ + +const base = 'https://api.svelte.dev'; + +export async function api(request: Request, resource: string, data?: Record) { + return fetch(`${base}/${resource}`, { + method: request.method, + headers: { + 'content-type': 'application/json' + }, + body: data && JSON.stringify(data) + }); +} diff --git a/src/routes/todos/index.svelte b/src/routes/todos/index.svelte new file mode 100644 index 0000000..e36b6cf --- /dev/null +++ b/src/routes/todos/index.svelte @@ -0,0 +1,186 @@ + + + + Todos + + +
+

Todos

+ + { + form.reset(); + } + }} + > + + + + {#each todos as todo (todo.uid)} +
+
{ + todo.done = !!data.get('done'); + } + }} + > + + +
+ {/each} +
+ + diff --git a/src/routes/todos/index.ts b/src/routes/todos/index.ts new file mode 100644 index 0000000..129b60a --- /dev/null +++ b/src/routes/todos/index.ts @@ -0,0 +1,52 @@ +import { api } from './_api'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async ({ request, locals }) => { + // locals.userid comes from src/hooks.js + const response = await api(request, `todos/${locals.userid}`); + + if (response.status === 404) { + // user hasn't created a todo list. + // start with an empty array + return { + body: { + todos: [] + } + }; + } + + if (response.ok) { + return { + body: { + todos: await response.json() + } + }; + } + + return { + status: response.status + }; +}; + +export const post: RequestHandler = async ({ request, locals }) => { + const form = await request.formData(); + + return api(request, `todos/${locals.userid}`, { + text: form.get('text') + }); +}; + +export const patch: RequestHandler = async ({ request, locals }) => { + const form = await request.formData(); + + return api(request, `todos/${locals.userid}/${form.get('uid')}`, { + text: form.has('text') ? form.get('text') : undefined, + done: form.has('done') ? !!form.get('done') : undefined + }); +}; + +export const del: RequestHandler = async ({ request, locals }) => { + const form = await request.formData(); + + return api(request, `todos/${locals.userid}/${form.get('uid')}`); +}; -- cgit