Compare commits
9 Commits
00a4019ae5
...
79c97ce232
| Author | SHA1 | Date | |
|---|---|---|---|
| 79c97ce232 | |||
| 68eeae18e2 | |||
| b694a10609 | |||
| 262ac50356 | |||
| fde45eb37c | |||
| c45a24066d | |||
| 5f16b55593 | |||
| ea0320e063 | |||
| c939655a4f |
@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Snippet } from "svelte";
|
import type { Snippet } from "svelte";
|
||||||
|
import LazyImage from "./LazyImage.svelte";
|
||||||
|
|
||||||
interface CardProps {
|
interface CardProps {
|
||||||
children: Snippet;
|
children: Snippet;
|
||||||
@ -10,6 +11,12 @@
|
|||||||
/** The id of the header image element for JS access. */
|
/** The id of the header image element for JS access. */
|
||||||
imgid?: string | undefined;
|
imgid?: string | undefined;
|
||||||
|
|
||||||
|
/** The aspect ratio width used to reserve image space (while its loading) */
|
||||||
|
imgwidth?: number;
|
||||||
|
|
||||||
|
/** The aspect ratio height used to reserve image space (while its loading) */
|
||||||
|
imgheight?: number;
|
||||||
|
|
||||||
/** Hide the header image element. It can be shown by removing the "hidden" property using JS and the imgid. */
|
/** Hide the header image element. It can be shown by removing the "hidden" property using JS and the imgid. */
|
||||||
imghidden?: boolean;
|
imghidden?: boolean;
|
||||||
|
|
||||||
@ -21,6 +28,8 @@
|
|||||||
children,
|
children,
|
||||||
imgsrc = undefined,
|
imgsrc = undefined,
|
||||||
imgid = undefined,
|
imgid = undefined,
|
||||||
|
imgwidth = undefined,
|
||||||
|
imgheight = undefined,
|
||||||
imghidden = false,
|
imghidden = false,
|
||||||
fullwidth = false,
|
fullwidth = false,
|
||||||
...restProps
|
...restProps
|
||||||
@ -30,14 +39,16 @@
|
|||||||
<div class="card overflow-hidden bg-white shadow {fullwidth ? 'w-full' : 'w-auto'}">
|
<div class="card overflow-hidden bg-white shadow {fullwidth ? 'w-full' : 'w-auto'}">
|
||||||
<!-- Allow empty strings for images that only appear after user action -->
|
<!-- Allow empty strings for images that only appear after user action -->
|
||||||
{#if imgsrc !== undefined}
|
{#if imgsrc !== undefined}
|
||||||
<img
|
<div style="width: auto; aspect-ratio: {imgwidth} / {imgheight};">
|
||||||
id={imgid}
|
<LazyImage
|
||||||
src={imgsrc}
|
id={imgid}
|
||||||
alt="Card header"
|
src={imgsrc}
|
||||||
draggable="false"
|
alt="Card header"
|
||||||
class="select-none shadow"
|
draggable="false"
|
||||||
hidden={imghidden}
|
class="select-none shadow"
|
||||||
/>
|
hidden={imghidden}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="p-2" {...restProps}>
|
<div class="p-2" {...restProps}>
|
||||||
{@render children()}
|
{@render children()}
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
import type { Driver } from "$lib/schema";
|
import type { Driver } from "$lib/schema";
|
||||||
import Input from "./Input.svelte";
|
import Input from "./Input.svelte";
|
||||||
import Dropdown, { type DropdownOption } from "./Dropdown.svelte";
|
import Dropdown, { type DropdownOption } from "./Dropdown.svelte";
|
||||||
|
import { DRIVER_HEADSHOT_HEIGHT, DRIVER_HEADSHOT_WIDTH } from "$lib/config";
|
||||||
|
|
||||||
interface DriverCardProps {
|
interface DriverCardProps {
|
||||||
/** The [Driver] object used to prefill values. */
|
/** The [Driver] object used to prefill values. */
|
||||||
@ -34,7 +35,7 @@
|
|||||||
driver = undefined,
|
driver = undefined,
|
||||||
disable_inputs = false,
|
disable_inputs = false,
|
||||||
require_inputs = false,
|
require_inputs = false,
|
||||||
headshot_template = "",
|
headshot_template = undefined,
|
||||||
team_select_value,
|
team_select_value,
|
||||||
team_select_options,
|
team_select_options,
|
||||||
active_value,
|
active_value,
|
||||||
@ -43,6 +44,8 @@
|
|||||||
|
|
||||||
<Card
|
<Card
|
||||||
imgsrc={driver?.headshot_url ?? headshot_template}
|
imgsrc={driver?.headshot_url ?? headshot_template}
|
||||||
|
imgwidth={DRIVER_HEADSHOT_WIDTH}
|
||||||
|
imgheight={DRIVER_HEADSHOT_HEIGHT}
|
||||||
imgid="update_driver_headshot_preview_{driver?.id ?? 'create'}"
|
imgid="update_driver_headshot_preview_{driver?.id ?? 'create'}"
|
||||||
>
|
>
|
||||||
<form method="POST" enctype="multipart/form-data">
|
<form method="POST" enctype="multipart/form-data">
|
||||||
|
|||||||
@ -76,7 +76,7 @@
|
|||||||
// Just list this so SvelteKit picks it up as dependency
|
// Just list this so SvelteKit picks it up as dependency
|
||||||
input_variable;
|
input_variable;
|
||||||
|
|
||||||
if (input) input.dispatchEvent(new Event("DropdownChange"));
|
if (input) input.dispatchEvent(new CustomEvent("DropdownChange"));
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
42
src/lib/components/LazyImage.svelte
Normal file
42
src/lib/components/LazyImage.svelte
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLImgAttributes } from "svelte/elements";
|
||||||
|
import { lazyload } from "$lib/lazyload";
|
||||||
|
|
||||||
|
interface LazyImageProps extends HTMLImgAttributes {
|
||||||
|
/** The URL to the image resource to lazyload */
|
||||||
|
src: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { src, ...restProps }: LazyImageProps = $props();
|
||||||
|
|
||||||
|
const blobToBase64 = (blob: Blob): Promise<any> => {
|
||||||
|
return new Promise((resolve, _) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onloadend = () => resolve(reader.result);
|
||||||
|
reader.readAsDataURL(blob);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadImage = async (url: string): Promise<any> => {
|
||||||
|
return await fetch(url)
|
||||||
|
.then((response) => response.blob())
|
||||||
|
.then((blob) => blobToBase64(blob));
|
||||||
|
};
|
||||||
|
|
||||||
|
const lazy_visible_handler = () => {
|
||||||
|
load = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Once the image is visible, this will be set to true, triggering the loading
|
||||||
|
let load: boolean = $state(false);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div use:lazyload onLazyVisible={lazy_visible_handler}>
|
||||||
|
{#if load}
|
||||||
|
{#await loadImage(src)}
|
||||||
|
<!-- Loading... -->
|
||||||
|
{:then data}
|
||||||
|
<img src={data} {...restProps} />
|
||||||
|
{/await}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
62
src/lib/components/LoadingIndicator.svelte
Normal file
62
src/lib/components/LoadingIndicator.svelte
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<!-- https://www.sveltelab.dev/dc0nf9id4ust2vw -->
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { navigating } from "$app/stores";
|
||||||
|
|
||||||
|
let loading: string = $state("no");
|
||||||
|
let percentage: number = $state(0);
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if ($navigating) {
|
||||||
|
loading = "yes";
|
||||||
|
} else {
|
||||||
|
loading = "closing";
|
||||||
|
setTimeout(() => {
|
||||||
|
loading = "no";
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (loading === "closing") {
|
||||||
|
percentage = 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const load = (_node: HTMLElement) => {
|
||||||
|
let timeout: NodeJS.Timeout;
|
||||||
|
const handle = () => {
|
||||||
|
if (percentage < 0.7) {
|
||||||
|
percentage += Math.random() * 0.3;
|
||||||
|
|
||||||
|
// Let's call ourselves recursively to fill the loading bar
|
||||||
|
timeout = setTimeout(handle, Math.random() * 1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handle();
|
||||||
|
|
||||||
|
return {
|
||||||
|
destroy() {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
percentage = 0;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if loading !== "no"}
|
||||||
|
<div
|
||||||
|
class="fixed inset-0 bottom-auto z-50 h-1 bg-error-500"
|
||||||
|
use:load
|
||||||
|
style:--percentage={percentage}
|
||||||
|
></div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
transform-origin: left;
|
||||||
|
transform: scaleX(calc(var(--percentage) * 100%));
|
||||||
|
transition: transform 250ms;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -6,6 +6,7 @@
|
|||||||
import type { Race } from "$lib/schema";
|
import type { Race } from "$lib/schema";
|
||||||
import Input from "./Input.svelte";
|
import Input from "./Input.svelte";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
|
import { RACE_PICTOGRAM_HEIGHT, RACE_PICTOGRAM_WIDTH } from "$lib/config";
|
||||||
|
|
||||||
interface RaceCardProps {
|
interface RaceCardProps {
|
||||||
/** The [Race] object used to prefill values. */
|
/** The [Race] object used to prefill values. */
|
||||||
@ -56,6 +57,8 @@
|
|||||||
|
|
||||||
<Card
|
<Card
|
||||||
imgsrc={race?.pictogram_url ?? pictogram_template}
|
imgsrc={race?.pictogram_url ?? pictogram_template}
|
||||||
|
imgwidth={RACE_PICTOGRAM_WIDTH}
|
||||||
|
imgheight={RACE_PICTOGRAM_HEIGHT}
|
||||||
imgid="update_race_pictogram_preview_{race?.id ?? 'create'}"
|
imgid="update_race_pictogram_preview_{race?.id ?? 'create'}"
|
||||||
>
|
>
|
||||||
<form method="POST" enctype="multipart/form-data">
|
<form method="POST" enctype="multipart/form-data">
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
import { get_by_value } from "$lib/database";
|
import { get_by_value } from "$lib/database";
|
||||||
import Dropdown, { type DropdownOption } from "./Dropdown.svelte";
|
import Dropdown, { type DropdownOption } from "./Dropdown.svelte";
|
||||||
import type { Action } from "svelte/action";
|
import type { Action } from "svelte/action";
|
||||||
|
import { DRIVER_HEADSHOT_HEIGHT, DRIVER_HEADSHOT_WIDTH } from "$lib/config";
|
||||||
|
|
||||||
interface SubstitutionCardProps {
|
interface SubstitutionCardProps {
|
||||||
/** The [Substitution] object used to prefill values. */
|
/** The [Substitution] object used to prefill values. */
|
||||||
@ -70,7 +71,7 @@
|
|||||||
`update_substitution_headshot_preview_${substitution?.id ?? "create"}`,
|
`update_substitution_headshot_preview_${substitution?.id ?? "create"}`,
|
||||||
) as HTMLImageElement;
|
) as HTMLImageElement;
|
||||||
|
|
||||||
preview.src = src;
|
if (preview) preview.src = src;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -78,6 +79,8 @@
|
|||||||
<Card
|
<Card
|
||||||
imgsrc={get_by_value(drivers, "id", substitution?.substitute ?? "")?.headshot_url ??
|
imgsrc={get_by_value(drivers, "id", substitution?.substitute ?? "")?.headshot_url ??
|
||||||
headshot_template}
|
headshot_template}
|
||||||
|
imgwidth={DRIVER_HEADSHOT_WIDTH}
|
||||||
|
imgheight={DRIVER_HEADSHOT_HEIGHT}
|
||||||
imgid="update_substitution_headshot_preview_{substitution?.id ?? 'create'}"
|
imgid="update_substitution_headshot_preview_{substitution?.id ?? 'create'}"
|
||||||
>
|
>
|
||||||
<form method="POST" enctype="multipart/form-data">
|
<form method="POST" enctype="multipart/form-data">
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
import Button from "./Button.svelte";
|
import Button from "./Button.svelte";
|
||||||
import type { Team } from "$lib/schema";
|
import type { Team } from "$lib/schema";
|
||||||
import Input from "./Input.svelte";
|
import Input from "./Input.svelte";
|
||||||
|
import { TEAM_LOGO_HEIGHT, TEAM_LOGO_WIDTH } from "$lib/config";
|
||||||
|
|
||||||
interface TeamCardProps {
|
interface TeamCardProps {
|
||||||
/** The [Team] object used to prefill values. */
|
/** The [Team] object used to prefill values. */
|
||||||
@ -30,6 +31,8 @@
|
|||||||
|
|
||||||
<Card
|
<Card
|
||||||
imgsrc={team?.logo_url ?? logo_template}
|
imgsrc={team?.logo_url ?? logo_template}
|
||||||
|
imgwidth={TEAM_LOGO_WIDTH}
|
||||||
|
imgheight={TEAM_LOGO_HEIGHT}
|
||||||
imgid="update_team_logo_preview_{team?.id ?? 'create'}"
|
imgid="update_team_logo_preview_{team?.id ?? 'create'}"
|
||||||
>
|
>
|
||||||
<form method="POST" enctype="multipart/form-data">
|
<form method="POST" enctype="multipart/form-data">
|
||||||
|
|||||||
@ -1,22 +1,35 @@
|
|||||||
import Input from "./Input.svelte";
|
|
||||||
import Button from "./Button.svelte";
|
import Button from "./Button.svelte";
|
||||||
import Card from "./Card.svelte";
|
import Card from "./Card.svelte";
|
||||||
import Search from "./Search.svelte";
|
import DriverCard from "./DriverCard.svelte";
|
||||||
import Dropdown from "./Dropdown.svelte";
|
import Dropdown from "./Dropdown.svelte";
|
||||||
// import type DropdownOption from "./Dropdown.svelte";
|
import Input from "./Input.svelte";
|
||||||
|
import LazyImage from "./LazyImage.svelte";
|
||||||
|
import LoadingIndicator from "./LoadingIndicator.svelte";
|
||||||
|
import RaceCard from "./RaceCard.svelte";
|
||||||
|
import Search from "./Search.svelte";
|
||||||
|
import SubstitutionCard from "./SubstitutionCard.svelte";
|
||||||
|
import TeamCard from "./TeamCard.svelte";
|
||||||
|
|
||||||
import MenuDrawerIcon from "./svg/MenuDrawerIcon.svelte";
|
import MenuDrawerIcon from "./svg/MenuDrawerIcon.svelte";
|
||||||
import UserIcon from "./svg/UserIcon.svelte";
|
|
||||||
import PasswordIcon from "./svg/PasswordIcon.svelte";
|
import PasswordIcon from "./svg/PasswordIcon.svelte";
|
||||||
|
import UserIcon from "./svg/UserIcon.svelte";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Input,
|
// Components
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
Search,
|
DriverCard,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
// type DropdownOption,
|
Input,
|
||||||
|
LazyImage,
|
||||||
|
LoadingIndicator,
|
||||||
|
RaceCard,
|
||||||
|
Search,
|
||||||
|
SubstitutionCard,
|
||||||
|
TeamCard,
|
||||||
|
|
||||||
|
// SVG
|
||||||
MenuDrawerIcon,
|
MenuDrawerIcon,
|
||||||
UserIcon,
|
|
||||||
PasswordIcon,
|
PasswordIcon,
|
||||||
|
UserIcon,
|
||||||
};
|
};
|
||||||
|
|||||||
11
src/lib/config.ts
Normal file
11
src/lib/config.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export const AVATAR_WIDTH: number = 256;
|
||||||
|
export const AVATAR_HEIGHT: number = 256;
|
||||||
|
|
||||||
|
export const TEAM_LOGO_WIDTH: number = 512;
|
||||||
|
export const TEAM_LOGO_HEIGHT: number = 288;
|
||||||
|
|
||||||
|
export const DRIVER_HEADSHOT_WIDTH: number = 512;
|
||||||
|
export const DRIVER_HEADSHOT_HEIGHT: number = 512;
|
||||||
|
|
||||||
|
export const RACE_PICTOGRAM_WIDTH: number = 512;
|
||||||
|
export const RACE_PICTOGRAM_HEIGHT: number = 384;
|
||||||
30
src/lib/lazyload.ts
Normal file
30
src/lib/lazyload.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// https://www.alexschnabl.com/blog/articles/lazy-loading-images-and-components-in-svelte-and-sveltekit-using-typescript
|
||||||
|
|
||||||
|
let observer: IntersectionObserver;
|
||||||
|
|
||||||
|
const getObserver = () => {
|
||||||
|
if (observer) return;
|
||||||
|
|
||||||
|
observer = new IntersectionObserver((entries: IntersectionObserverEntry[]) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
entry.target.dispatchEvent(new CustomEvent("LazyVisible"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is used as an action on lazyloaded elements
|
||||||
|
export const lazyload = (node: HTMLElement) => {
|
||||||
|
// The observer determines if the element is visible on screen
|
||||||
|
getObserver();
|
||||||
|
|
||||||
|
// If the element is visible, the "LazyVisible" event will be dispatched
|
||||||
|
observer.observe(node);
|
||||||
|
|
||||||
|
return {
|
||||||
|
destroy() {
|
||||||
|
observer.unobserve(node);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -5,7 +5,14 @@
|
|||||||
import type { LayoutData } from "./$types";
|
import type { LayoutData } from "./$types";
|
||||||
import { page } from "$app/stores";
|
import { page } from "$app/stores";
|
||||||
|
|
||||||
import { Button, MenuDrawerIcon, UserIcon, Input, PasswordIcon } from "$lib/components";
|
import {
|
||||||
|
Button,
|
||||||
|
MenuDrawerIcon,
|
||||||
|
UserIcon,
|
||||||
|
Input,
|
||||||
|
PasswordIcon,
|
||||||
|
LoadingIndicator,
|
||||||
|
} from "$lib/components";
|
||||||
import { get_avatar_preview_event_handler } from "$lib/image";
|
import { get_avatar_preview_event_handler } from "$lib/image";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -83,6 +90,8 @@
|
|||||||
// };
|
// };
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<LoadingIndicator />
|
||||||
|
|
||||||
<Drawer>
|
<Drawer>
|
||||||
{#if $drawerStore.id === "menu_drawer"}
|
{#if $drawerStore.id === "menu_drawer"}
|
||||||
<!-- Menu Drawer -->
|
<!-- Menu Drawer -->
|
||||||
|
|||||||
@ -6,6 +6,15 @@ import {
|
|||||||
form_data_get_and_remove_id,
|
form_data_get_and_remove_id,
|
||||||
} from "$lib/form";
|
} from "$lib/form";
|
||||||
import type { Team, Driver, Race, Substitution, Graphic } from "$lib/schema";
|
import type { Team, Driver, Race, Substitution, Graphic } from "$lib/schema";
|
||||||
|
import { image_to_avif } from "$lib/server/image";
|
||||||
|
import {
|
||||||
|
DRIVER_HEADSHOT_HEIGHT,
|
||||||
|
DRIVER_HEADSHOT_WIDTH,
|
||||||
|
RACE_PICTOGRAM_HEIGHT,
|
||||||
|
RACE_PICTOGRAM_WIDTH,
|
||||||
|
TEAM_LOGO_HEIGHT,
|
||||||
|
TEAM_LOGO_WIDTH,
|
||||||
|
} from "$lib/config";
|
||||||
|
|
||||||
// These "actions" run serverside only, as they're located inside +page.server.ts
|
// These "actions" run serverside only, as they're located inside +page.server.ts
|
||||||
export const actions = {
|
export const actions = {
|
||||||
@ -15,6 +24,15 @@ export const actions = {
|
|||||||
const data: FormData = form_data_clean(await request.formData());
|
const data: FormData = form_data_clean(await request.formData());
|
||||||
form_data_ensure_keys(data, ["name", "logo"]);
|
form_data_ensure_keys(data, ["name", "logo"]);
|
||||||
|
|
||||||
|
// Compress logo
|
||||||
|
const compressed: Blob = await image_to_avif(
|
||||||
|
await (data.get("logo") as File).arrayBuffer(),
|
||||||
|
TEAM_LOGO_WIDTH,
|
||||||
|
TEAM_LOGO_HEIGHT,
|
||||||
|
);
|
||||||
|
|
||||||
|
data.set("logo", compressed);
|
||||||
|
|
||||||
await locals.pb.collection("teams").create(data);
|
await locals.pb.collection("teams").create(data);
|
||||||
|
|
||||||
return { tab: 0 };
|
return { tab: 0 };
|
||||||
@ -26,6 +44,17 @@ export const actions = {
|
|||||||
const data: FormData = form_data_clean(await request.formData());
|
const data: FormData = form_data_clean(await request.formData());
|
||||||
const id: string = form_data_get_and_remove_id(data);
|
const id: string = form_data_get_and_remove_id(data);
|
||||||
|
|
||||||
|
if (data.has("logo")) {
|
||||||
|
// Compress logo
|
||||||
|
const compressed: Blob = await image_to_avif(
|
||||||
|
await (data.get("logo") as File).arrayBuffer(),
|
||||||
|
TEAM_LOGO_WIDTH,
|
||||||
|
TEAM_LOGO_HEIGHT,
|
||||||
|
);
|
||||||
|
|
||||||
|
data.set("logo", compressed);
|
||||||
|
}
|
||||||
|
|
||||||
await locals.pb.collection("teams").update(id, data);
|
await locals.pb.collection("teams").update(id, data);
|
||||||
|
|
||||||
return { tab: 0 };
|
return { tab: 0 };
|
||||||
@ -51,6 +80,15 @@ export const actions = {
|
|||||||
// The toggle switch will report "on" or nothing
|
// The toggle switch will report "on" or nothing
|
||||||
data.set("active", data.has("active") ? "true" : "false");
|
data.set("active", data.has("active") ? "true" : "false");
|
||||||
|
|
||||||
|
// Compress headshot
|
||||||
|
const compressed: Blob = await image_to_avif(
|
||||||
|
await (data.get("headshot") as File).arrayBuffer(),
|
||||||
|
DRIVER_HEADSHOT_WIDTH,
|
||||||
|
DRIVER_HEADSHOT_HEIGHT,
|
||||||
|
);
|
||||||
|
|
||||||
|
data.set("headshot", compressed);
|
||||||
|
|
||||||
await locals.pb.collection("drivers").create(data);
|
await locals.pb.collection("drivers").create(data);
|
||||||
|
|
||||||
return { tab: 1 };
|
return { tab: 1 };
|
||||||
@ -65,6 +103,17 @@ export const actions = {
|
|||||||
// The toggle switch will report "on" or nothing
|
// The toggle switch will report "on" or nothing
|
||||||
data.set("active", data.has("active") ? "true" : "false");
|
data.set("active", data.has("active") ? "true" : "false");
|
||||||
|
|
||||||
|
if (data.has("headshot")) {
|
||||||
|
// Compress headshot
|
||||||
|
const compressed: Blob = await image_to_avif(
|
||||||
|
await (data.get("headshot") as File).arrayBuffer(),
|
||||||
|
DRIVER_HEADSHOT_WIDTH,
|
||||||
|
DRIVER_HEADSHOT_HEIGHT,
|
||||||
|
);
|
||||||
|
|
||||||
|
data.set("headshot", compressed);
|
||||||
|
}
|
||||||
|
|
||||||
await locals.pb.collection("drivers").update(id, data);
|
await locals.pb.collection("drivers").update(id, data);
|
||||||
|
|
||||||
return { tab: 1 };
|
return { tab: 1 };
|
||||||
@ -88,6 +137,15 @@ export const actions = {
|
|||||||
form_data_ensure_keys(data, ["name", "step", "pictogram", "pxx", "qualidate", "racedate"]);
|
form_data_ensure_keys(data, ["name", "step", "pictogram", "pxx", "qualidate", "racedate"]);
|
||||||
form_data_fix_dates(data, ["sprintqualidate", "sprintdate", "qualidate", "racedate"]);
|
form_data_fix_dates(data, ["sprintqualidate", "sprintdate", "qualidate", "racedate"]);
|
||||||
|
|
||||||
|
// Compress pictogram
|
||||||
|
const compressed: Blob = await image_to_avif(
|
||||||
|
await (data.get("pictogram") as File).arrayBuffer(),
|
||||||
|
RACE_PICTOGRAM_WIDTH,
|
||||||
|
RACE_PICTOGRAM_HEIGHT,
|
||||||
|
);
|
||||||
|
|
||||||
|
data.set("pictogram", compressed);
|
||||||
|
|
||||||
await locals.pb.collection("races").create(data);
|
await locals.pb.collection("races").create(data);
|
||||||
|
|
||||||
return { tab: 2 };
|
return { tab: 2 };
|
||||||
@ -104,6 +162,17 @@ export const actions = {
|
|||||||
form_data_fix_dates(data, ["sprintqualidate", "sprintdate", "qualidate", "racedate"]);
|
form_data_fix_dates(data, ["sprintqualidate", "sprintdate", "qualidate", "racedate"]);
|
||||||
const id: string = form_data_get_and_remove_id(data);
|
const id: string = form_data_get_and_remove_id(data);
|
||||||
|
|
||||||
|
if (data.has("pictogram")) {
|
||||||
|
// Compress pictogram
|
||||||
|
const compressed: Blob = await image_to_avif(
|
||||||
|
await (data.get("pictogram") as File).arrayBuffer(),
|
||||||
|
RACE_PICTOGRAM_WIDTH,
|
||||||
|
RACE_PICTOGRAM_HEIGHT,
|
||||||
|
);
|
||||||
|
|
||||||
|
data.set("pictogram", compressed);
|
||||||
|
}
|
||||||
|
|
||||||
await locals.pb.collection("races").update(id, data);
|
await locals.pb.collection("races").update(id, data);
|
||||||
|
|
||||||
return { tab: 2 };
|
return { tab: 2 };
|
||||||
|
|||||||
@ -5,11 +5,8 @@
|
|||||||
|
|
||||||
// TODO: Why does this work but import { type DropdownOption } from "$lib/components" does not?
|
// TODO: Why does this work but import { type DropdownOption } from "$lib/components" does not?
|
||||||
import type { DropdownOption } from "$lib/components/Dropdown.svelte";
|
import type { DropdownOption } from "$lib/components/Dropdown.svelte";
|
||||||
|
import { TeamCard, DriverCard, RaceCard, SubstitutionCard } from "$lib/components";
|
||||||
import { get_by_value } from "$lib/database";
|
import { get_by_value } from "$lib/database";
|
||||||
import TeamCard from "$lib/components/TeamCard.svelte";
|
|
||||||
import DriverCard from "$lib/components/DriverCard.svelte";
|
|
||||||
import RaceCard from "$lib/components/RaceCard.svelte";
|
|
||||||
import SubstitutionCard from "$lib/components/SubstitutionCard.svelte";
|
|
||||||
|
|
||||||
let { data, form }: { data: PageData; form: ActionData } = $props();
|
let { data, form }: { data: PageData; form: ActionData } = $props();
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { form_data_clean, form_data_ensure_keys, form_data_get_and_remove_id } f
|
|||||||
import { error, redirect } from "@sveltejs/kit";
|
import { error, redirect } from "@sveltejs/kit";
|
||||||
import type { Actions } from "./$types";
|
import type { Actions } from "./$types";
|
||||||
import { image_to_avif } from "$lib/server/image";
|
import { image_to_avif } from "$lib/server/image";
|
||||||
|
import { AVATAR_HEIGHT, AVATAR_WIDTH } from "$lib/config";
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
create_profile: async ({ request, locals }): Promise<void> => {
|
create_profile: async ({ request, locals }): Promise<void> => {
|
||||||
@ -34,8 +35,8 @@ export const actions = {
|
|||||||
// Compress image
|
// Compress image
|
||||||
const compressed: Blob = await image_to_avif(
|
const compressed: Blob = await image_to_avif(
|
||||||
await (data.get("avatar") as File).arrayBuffer(),
|
await (data.get("avatar") as File).arrayBuffer(),
|
||||||
256,
|
AVATAR_WIDTH,
|
||||||
256,
|
AVATAR_HEIGHT,
|
||||||
);
|
);
|
||||||
|
|
||||||
// At most 20kB
|
// At most 20kB
|
||||||
|
|||||||
Reference in New Issue
Block a user