Just some linting

This commit is contained in:
Gender Shrapnel 2022-07-27 04:19:02 +02:00
parent 6b799308da
commit d9a97521a1
Signed by: modzero
GPG Key ID: 4E11A06C6D1E5213
8 changed files with 329 additions and 298 deletions

View File

@ -4,7 +4,7 @@
"beforeBuildCommand": "npm run build", "beforeBuildCommand": "npm run build",
"beforeDevCommand": "npm run dev", "beforeDevCommand": "npm run dev",
"devPath": "http://localhost:5173", "devPath": "http://localhost:5173",
"distDir": "../.svelte-kit/output/dist" "distDir": "../.svelte-kit/output/client"
}, },
"package": { "package": {
"productName": "ziemniak", "productName": "ziemniak",

View File

@ -1,84 +1,90 @@
import { invalidate } from '$app/navigation'; import { invalidate } from "$app/navigation";
// this action (https://svelte.dev/tutorial/actions) allows us to // this action (https://svelte.dev/tutorial/actions) allows us to
// progressively enhance a <form> that already works without JS // progressively enhance a <form> that already works without JS
export function enhance( export function enhance(
form: HTMLFormElement, form: HTMLFormElement,
{ {
pending, pending,
error, error,
result result,
}: { }: {
pending?: ({ data, form }: { data: FormData; form: HTMLFormElement }) => void; pending?: ({
error?: ({ data,
data, form,
form, }: {
response, data: FormData;
error form: HTMLFormElement;
}: { }) => void;
data: FormData; error?: ({
form: HTMLFormElement; data,
response: Response | null; form,
error: Error | null; response,
}) => void; error,
result?: ({ }: {
data, data: FormData;
form, form: HTMLFormElement;
response response: Response | null;
}: { error: Error | null;
data: FormData; }) => void;
response: Response; result?: ({
form: HTMLFormElement; data,
}) => void; form,
} = {} response,
}: {
data: FormData;
response: Response;
form: HTMLFormElement;
}) => void;
} = {}
) { ) {
let current_token: unknown; let current_token: unknown;
async function handle_submit(e: SubmitEvent) { async function handle_submit(e: SubmitEvent) {
const token = (current_token = {}); const token = (current_token = {});
e.preventDefault(); e.preventDefault();
const data = new FormData(form); const data = new FormData(form);
if (pending) pending({ data, form }); if (pending) pending({ data, form });
try { try {
const response = await fetch(form.action, { const response = await fetch(form.action, {
method: form.method, method: form.method,
headers: { headers: {
accept: 'application/json' accept: "application/json",
}, },
body: data body: data,
}); });
if (token !== current_token) return; if (token !== current_token) return;
if (response.ok) { if (response.ok) {
if (result) result({ data, form, response }); if (result) result({ data, form, response });
const url = new URL(form.action); const url = new URL(form.action);
url.search = url.hash = ''; url.search = url.hash = "";
invalidate(url.href); invalidate(url.href);
} else if (error) { } else if (error) {
error({ data, form, error: null, response }); error({ data, form, error: null, response });
} else { } else {
console.error(await response.text()); console.error(await response.text());
} }
} catch (e: unknown) { } catch (e: unknown) {
if (error && e instanceof Error) { if (error && e instanceof Error) {
error({ data, form, error: e, response: null }); error({ data, form, error: e, response: null });
} else { } else {
throw e; throw e;
} }
} }
} }
form.addEventListener('submit', handle_submit); form.addEventListener("submit", handle_submit);
return { return {
destroy() { destroy() {
form.removeEventListener('submit', handle_submit); form.removeEventListener("submit", handle_submit);
} },
}; };
} }

View File

@ -9,7 +9,7 @@
import type { UnlistenFn } from "@tauri-apps/api/event"; import type { UnlistenFn } from "@tauri-apps/api/event";
import { invoke } from "@tauri-apps/api"; import { invoke } from "@tauri-apps/api";
import { listen } from "@tauri-apps/api/event"; import { listen } from "@tauri-apps/api/event";
let timerTickUnlisten: Promise<UnlistenFn> | null = null; let timerTickUnlisten: Promise<UnlistenFn> | null = null;
let timerDoneUnlisten: Promise<UnlistenFn> | null = null; let timerDoneUnlisten: Promise<UnlistenFn> | null = null;

View File

@ -11,12 +11,16 @@
const base = "https://api.svelte.dev"; const base = "https://api.svelte.dev";
export function api(method: string, resource: string, data?: Record<string, unknown>) { export function api(
return fetch(`${base}/${resource}`, { method: string,
method, resource: string,
headers: { data?: Record<string, unknown>
'content-type': 'application/json' ) {
}, return fetch(`${base}/${resource}`, {
body: data && JSON.stringify(data) method,
}); headers: {
} "content-type": "application/json",
},
body: data && JSON.stringify(data),
});
}

View File

@ -1,187 +1,208 @@
<script lang="ts"> <script lang="ts">
import { enhance } from '$lib/form'; import { enhance } from "$lib/form";
import { scale } from 'svelte/transition'; import { scale } from "svelte/transition";
import { flip } from 'svelte/animate'; import { flip } from "svelte/animate";
type Todo = { type Todo = {
uid: string; uid: string;
created_at: Date; created_at: Date;
text: string; text: string;
done: boolean; done: boolean;
pending_delete: boolean; pending_delete: boolean;
}; };
export let todos: Todo[]; export let todos: Todo[];
</script> </script>
<svelte:head> <svelte:head>
<title>Todos</title> <title>Todos</title>
<meta name="description" content="A todo list app" /> <meta name="description" content="A todo list app" />
</svelte:head> </svelte:head>
<div class="todos"> <div class="todos">
<h1>Todos</h1> <h1>Todos</h1>
<form <form
class="new" class="new"
action="/todos" action="/todos"
method="post" method="post"
use:enhance={{ use:enhance={{
result: async ({ form }) => { result: async ({ form }) => {
form.reset(); form.reset();
} },
}} }}
> >
<input name="text" aria-label="Add todo" placeholder="+ tap to add a todo" /> <input
</form> name="text"
aria-label="Add todo"
placeholder="+ tap to add a todo"
/>
</form>
{#each todos as todo (todo.uid)} {#each todos as todo (todo.uid)}
<div <div
class="todo" class="todo"
class:done={todo.done} class:done={todo.done}
transition:scale|local={{ start: 0.7 }} transition:scale|local={{ start: 0.7 }}
animate:flip={{ duration: 200 }} animate:flip={{ duration: 200 }}
> >
<form <form
action="/todos?_method=PATCH" action="/todos?_method=PATCH"
method="post" method="post"
use:enhance={{ use:enhance={{
pending: ({ data }) => { pending: ({ data }) => {
todo.done = !!data.get('done'); todo.done = !!data.get("done");
} },
}} }}
> >
<input type="hidden" name="uid" value={todo.uid} /> <input type="hidden" name="uid" value={todo.uid} />
<input type="hidden" name="done" value={todo.done ? '' : 'true'} /> <input type="hidden" name="done" value={todo.done ? "" : "true"} />
<button class="toggle" aria-label="Mark todo as {todo.done ? 'not done' : 'done'}" /> <button
</form> class="toggle"
aria-label="Mark todo as {todo.done ? 'not done' : 'done'}"
/>
</form>
<form class="text" action="/todos?_method=PATCH" method="post" use:enhance> <form
<input type="hidden" name="uid" value={todo.uid} /> class="text"
<input aria-label="Edit todo" type="text" name="text" value={todo.text} /> action="/todos?_method=PATCH"
<button class="save" aria-label="Save todo" /> method="post"
</form> use:enhance
>
<input type="hidden" name="uid" value={todo.uid} />
<input
aria-label="Edit todo"
type="text"
name="text"
value={todo.text}
/>
<button class="save" aria-label="Save todo" />
</form>
<form <form
action="/todos?_method=DELETE" action="/todos?_method=DELETE"
method="post" method="post"
use:enhance={{ use:enhance={{
pending: () => (todo.pending_delete = true) pending: () => (todo.pending_delete = true),
}} }}
> >
<input type="hidden" name="uid" value={todo.uid} /> <input type="hidden" name="uid" value={todo.uid} />
<button class="delete" aria-label="Delete todo" disabled={todo.pending_delete} /> <button
</form> class="delete"
</div> aria-label="Delete todo"
{/each} disabled={todo.pending_delete}
/>
</form>
</div>
{/each}
</div> </div>
<style> <style>
.todos { .todos {
width: 100%; width: 100%;
max-width: var(--column-width); max-width: var(--column-width);
margin: var(--column-margin-top) auto 0 auto; margin: var(--column-margin-top) auto 0 auto;
line-height: 1; line-height: 1;
} }
.new { .new {
margin: 0 0 0.5rem 0; margin: 0 0 0.5rem 0;
} }
input { input {
border: 1px solid transparent; border: 1px solid transparent;
} }
input:focus-visible { input:focus-visible {
box-shadow: inset 1px 1px 6px rgba(0, 0, 0, 0.1); box-shadow: inset 1px 1px 6px rgba(0, 0, 0, 0.1);
border: 1px solid #ff3e00 !important; border: 1px solid #ff3e00 !important;
outline: none; outline: none;
} }
.new input { .new input {
font-size: 28px; font-size: 28px;
width: 100%; width: 100%;
padding: 0.5em 1em 0.3em 1em; padding: 0.5em 1em 0.3em 1em;
box-sizing: border-box; box-sizing: border-box;
background: rgba(255, 255, 255, 0.05); background: rgba(255, 255, 255, 0.05);
border-radius: 8px; border-radius: 8px;
text-align: center; text-align: center;
} }
.todo { .todo {
display: grid; display: grid;
grid-template-columns: 2rem 1fr 2rem; grid-template-columns: 2rem 1fr 2rem;
grid-gap: 0.5rem; grid-gap: 0.5rem;
align-items: center; align-items: center;
margin: 0 0 0.5rem 0; margin: 0 0 0.5rem 0;
padding: 0.5rem; padding: 0.5rem;
background-color: white; background-color: white;
border-radius: 8px; border-radius: 8px;
filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.1)); filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.1));
transform: translate(-1px, -1px); transform: translate(-1px, -1px);
transition: filter 0.2s, transform 0.2s; transition: filter 0.2s, transform 0.2s;
} }
.done { .done {
transform: none; transform: none;
opacity: 0.4; opacity: 0.4;
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.1)); filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.1));
} }
form.text { form.text {
position: relative; position: relative;
display: flex; display: flex;
align-items: center; align-items: center;
flex: 1; flex: 1;
} }
.todo input { .todo input {
flex: 1; flex: 1;
padding: 0.5em 2em 0.5em 0.8em; padding: 0.5em 2em 0.5em 0.8em;
border-radius: 3px; border-radius: 3px;
} }
.todo button { .todo button {
width: 2em; width: 2em;
height: 2em; height: 2em;
border: none; border: none;
background-color: transparent; background-color: transparent;
background-position: 50% 50%; background-position: 50% 50%;
background-repeat: no-repeat; background-repeat: no-repeat;
} }
button.toggle { button.toggle {
border: 1px solid rgba(0, 0, 0, 0.2); border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 50%; border-radius: 50%;
box-sizing: border-box; box-sizing: border-box;
background-size: 1em auto; background-size: 1em auto;
} }
.done .toggle { .done .toggle {
background-image: url("data:image/svg+xml,%3Csvg width='22' height='16' viewBox='0 0 22 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 1.5L7.4375 14.5L1.5 8.5909' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); background-image: url("data:image/svg+xml,%3Csvg width='22' height='16' viewBox='0 0 22 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 1.5L7.4375 14.5L1.5 8.5909' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
} }
.delete { .delete {
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.5 5V22H19.5V5H4.5Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M10 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M14 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M2 5H22' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M8 5L9.6445 2H14.3885L16 5H8Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E%0A"); background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.5 5V22H19.5V5H4.5Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M10 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M14 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M2 5H22' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M8 5L9.6445 2H14.3885L16 5H8Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E%0A");
opacity: 0.2; opacity: 0.2;
} }
.delete:hover, .delete:hover,
.delete:focus { .delete:focus {
transition: opacity 0.2s; transition: opacity 0.2s;
opacity: 1; opacity: 1;
} }
.save { .save {
position: absolute; position: absolute;
right: 0; right: 0;
opacity: 0; opacity: 0;
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 2H3.5C2.67158 2 2 2.67157 2 3.5V20.5C2 21.3284 2.67158 22 3.5 22H20.5C21.3284 22 22 21.3284 22 20.5V3.5C22 2.67157 21.3284 2 20.5 2Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M17 2V11H7.5V2H17Z' fill='white' stroke='white' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M13.5 5.5V7.5' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3Cpath d='M5.99844 2H18.4992' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E%0A"); background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 2H3.5C2.67158 2 2 2.67157 2 3.5V20.5C2 21.3284 2.67158 22 3.5 22H20.5C21.3284 22 22 21.3284 22 20.5V3.5C22 2.67157 21.3284 2 20.5 2Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M17 2V11H7.5V2H17Z' fill='white' stroke='white' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M13.5 5.5V7.5' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3Cpath d='M5.99844 2H18.4992' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E%0A");
} }
.todo input:focus + .save, .todo input:focus + .save,
.save:focus { .save:focus {
transition: opacity 0.2s; transition: opacity 0.2s;
opacity: 1; opacity: 1;
} }
</style> </style>

View File

@ -1,67 +1,67 @@
import { api } from './_api'; import { api } from "./_api";
import type { RequestHandler } from './__types'; import type { RequestHandler } from "./__types";
export const GET: RequestHandler = async ({ locals }) => { export const GET: RequestHandler = async ({ locals }) => {
// locals.userid comes from src/hooks.js // locals.userid comes from src/hooks.js
const response = await api('GET', `todos/${locals.userid}`); const response = await api("GET", `todos/${locals.userid}`);
if (response.status === 404) { if (response.status === 404) {
// user hasn't created a todo list. // user hasn't created a todo list.
// start with an empty array // start with an empty array
return { return {
body: { body: {
todos: [] todos: [],
} },
}; };
} }
if (response.status === 200) { if (response.status === 200) {
return { return {
body: { body: {
todos: await response.json() todos: await response.json(),
} },
}; };
} }
return { return {
status: response.status status: response.status,
}; };
}; };
export const POST: RequestHandler = async ({ request, locals }) => { export const POST: RequestHandler = async ({ request, locals }) => {
const form = await request.formData(); const form = await request.formData();
await api('POST', `todos/${locals.userid}`, { await api("POST", `todos/${locals.userid}`, {
text: form.get('text') text: form.get("text"),
}); });
return {}; return {};
}; };
// If the user has JavaScript disabled, the URL will change to // If the user has JavaScript disabled, the URL will change to
// include the method override unless we redirect back to /todos // include the method override unless we redirect back to /todos
const redirect = { const redirect = {
status: 303, status: 303,
headers: { headers: {
location: '/todos' location: "/todos",
} },
}; };
export const PATCH: RequestHandler = async ({ request, locals }) => { export const PATCH: RequestHandler = async ({ request, locals }) => {
const form = await request.formData(); const form = await request.formData();
await api('PATCH', `todos/${locals.userid}/${form.get('uid')}`, { await api("PATCH", `todos/${locals.userid}/${form.get("uid")}`, {
text: form.has('text') ? form.get('text') : undefined, text: form.has("text") ? form.get("text") : undefined,
done: form.has('done') ? !!form.get('done') : undefined done: form.has("done") ? !!form.get("done") : undefined,
}); });
return redirect; return redirect;
}; };
export const DELETE: RequestHandler = async ({ request, locals }) => { export const DELETE: RequestHandler = async ({ request, locals }) => {
const form = await request.formData(); const form = await request.formData();
await api('DELETE', `todos/${locals.userid}/${form.get('uid')}`); await api("DELETE", `todos/${locals.userid}/${form.get("uid")}`);
return redirect; return redirect;
}; };

View File

@ -1,20 +1,20 @@
import adapter from '@sveltejs/adapter-auto'; import adapter from "@sveltejs/adapter-auto";
import preprocess from 'svelte-preprocess'; import preprocess from "svelte-preprocess";
/** @type {import('@sveltejs/kit').Config} */ /** @type {import('@sveltejs/kit').Config} */
const config = { const config = {
// Consult https://github.com/sveltejs/svelte-preprocess // Consult https://github.com/sveltejs/svelte-preprocess
// for more information about preprocessors // for more information about preprocessors
preprocess: preprocess(), preprocess: preprocess(),
kit: { kit: {
adapter: adapter(), adapter: adapter(),
// Override http methods in the Todo forms // Override http methods in the Todo forms
methodOverride: { methodOverride: {
allowed: ['PATCH', 'DELETE'] allowed: ["PATCH", "DELETE"],
} },
} },
}; };
export default config; export default config;

View File

@ -1,13 +1,13 @@
{ {
"extends": "./.svelte-kit/tsconfig.json", "extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"allowJs": true, "allowJs": true,
"checkJs": true, "checkJs": true,
"esModuleInterop": true, "esModuleInterop": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"skipLibCheck": true, "skipLibCheck": true,
"sourceMap": true, "sourceMap": true,
"strict": true "strict": true
} }
} }