Lib: Simplify SubstitutionCard

This commit is contained in:
2025-02-05 02:33:46 +01:00
parent 042cb42e65
commit f90f5734e8
2 changed files with 153 additions and 222 deletions

View File

@ -1,191 +1,157 @@
<script lang="ts">
import { Card, Button, Dropdown } from "$lib/components";
import type { Driver, Race, Substitution } from "$lib/schema";
import { get_by_value } from "$lib/database";
import { get_by_value, get_driver_headshot_template } from "$lib/database";
import type { Action } from "svelte/action";
import { getModalStore, type ModalStore } from "@skeletonlabs/skeleton";
import { DRIVER_HEADSHOT_HEIGHT, DRIVER_HEADSHOT_WIDTH } from "$lib/config";
import { driver_dropdown_options, race_dropdown_options } from "$lib/dropdown";
import { enhance } from "$app/forms";
import { sub } from "date-fns";
interface SubstitutionCardProps {
/** Data passed from the page context */
data: any;
/** The [Substitution] object used to prefill values. */
substitution?: Substitution | undefined;
/** The drivers (to display the headshot) */
drivers: Driver[];
races: Race[];
/** Disable all inputs if [true] */
disable_inputs?: boolean;
/** Require all inputs if [true] */
require_inputs?: boolean;
/** The [src] of the driver headshot template preview */
headshot_template?: string;
/** The value this component's substitute select dropdown will bind to */
substitute_select_value: string;
/** The value this component's driver select dropdown will bind to */
driver_select_value: string;
/** The value this component's race select dropdown will bind to */
race_select_value: string;
substitution?: Substitution;
}
let {
substitution = undefined,
drivers,
races,
disable_inputs = false,
require_inputs = false,
headshot_template = "",
substitute_select_value,
driver_select_value,
race_select_value,
}: SubstitutionCardProps = $props();
const active_drivers = (drivers: Driver[]): Driver[] =>
drivers.filter((driver: Driver) => driver.active);
const inactive_drivers = (drivers: Driver[]): Driver[] =>
drivers.filter((driver: Driver) => !driver.active);
let { data, substitution = undefined }: SubstitutionCardProps = $props();
const modalStore: ModalStore = getModalStore();
if ($modalStore[0].meta) {
const meta = $modalStore[0].meta;
// Stuff thats required for the "update" card
data = meta.data;
substitution = meta.substitution;
drivers = meta.drivers;
races = meta.races;
disable_inputs = meta.disable_inputs;
substitute_select_value = meta.substitute_select_value;
driver_select_value = meta.driver_select_value;
race_select_value = meta.race_select_value;
// Stuff thats additionally required for the "create" card
require_inputs = meta.require_inputs;
headshot_template = meta.headshot_template;
}
// This action is used on the <Dropdown> element.
// It will trigger once the Dropdown's <input> elements is mounted.
// This way we'll receive a reference to the object so we can register our event handler.
const register_substitute_preview_handler: Action = (node: HTMLElement) => {
// This is executed on mount of the element specifying the "action"
const register_substitute_preview_handler: Action = (node: HTMLElement) =>
node.addEventListener("DropdownChange", update_substitute_preview);
};
// This event handler is registered to the Dropdown's <input> element through the action above.
const update_substitute_preview = (event: Event) => {
const update_substitute_preview = async (event: Event) => {
const target: HTMLInputElement = event.target as HTMLInputElement;
// The option "label" gets put into the Dropdown's input value,
// so we need to lookup the driver by "code".
const src: string = get_by_value(drivers, "code", target.value)?.headshot_url || "";
if (src) {
const preview: HTMLImageElement = document.getElementById(
`update_substitution_headshot_preview_${substitution?.id ?? "create"}`,
) as HTMLImageElement;
if (preview) preview.src = src;
}
const src: string =
get_by_value<Driver>(await data.drivers, "code", target.value)?.headshot_url ?? "";
(document.getElementById("headshot_preview") as HTMLImageElement).src = src;
};
const active_drivers = (drivers: Driver[]): Driver[] =>
drivers.filter((driver: Driver) => driver.active);
const inactive_drivers = (drivers: Driver[]): Driver[] =>
drivers.filter((driver: Driver) => !driver.active);
const required: boolean = $derived(!substitution);
const disabled: boolean = $derived(!data.admin);
const labelwidth: string = "120px";
let substitute_select_value: string = $state(substitution?.substitute ?? "");
let driver_select_value: string = $state(substitution?.for ?? "");
let race_select_value: string = $state(substitution?.race ?? "");
</script>
<Card
imgsrc={get_by_value(drivers, "id", substitution?.substitute ?? "")?.headshot_url ??
headshot_template}
imgid="update_substitution_headshot_preview_{substitution?.id ?? 'create'}"
width="w-full sm:w-auto"
imgwidth={DRIVER_HEADSHOT_WIDTH}
imgheight={DRIVER_HEADSHOT_HEIGHT}
imgonclick={(event: Event) => modalStore.close()}
>
<form method="POST" enctype="multipart/form-data" use:enhance>
<!-- This is also disabled, because the ID should only be -->
<!-- "leaked" to users that are allowed to use the inputs -->
{#if substitution && !disable_inputs}
<input name="id" type="hidden" value={substitution.id} />
{/if}
<div class="flex flex-col gap-2">
<!-- Substitute select -->
<Dropdown
name="substitute"
input_variable={substitute_select_value}
action={register_substitute_preview_handler}
options={driver_dropdown_options(inactive_drivers(drivers))}
labelwidth="120px"
disabled={disable_inputs}
required={require_inputs}
{#await data.graphics then graphics}
{#await data.drivers then drivers}
<Card
imgsrc={get_by_value<Driver>(drivers, "id", substitution?.substitute ?? "")?.headshot_url ??
get_driver_headshot_template(graphics)}
imgid="headshot_preview"
width="w-full sm:w-auto"
imgwidth={DRIVER_HEADSHOT_WIDTH}
imgheight={DRIVER_HEADSHOT_HEIGHT}
imgonclick={(event: Event) => modalStore.close()}
>
<form
method="POST"
enctype="multipart/form-data"
use:enhance
onsubmit={() => modalStore.close()}
>
Substitute
</Dropdown>
<!-- Driver select -->
<Dropdown
name="for"
input_variable={driver_select_value}
options={driver_dropdown_options(active_drivers(drivers))}
labelwidth="120px"
disabled={disable_inputs}
required={require_inputs}
>
For
</Dropdown>
<!-- Race select -->
<Dropdown
name="race"
input_variable={race_select_value}
options={race_dropdown_options(races)}
labelwidth="120px"
disabled={disable_inputs}
required={require_inputs}
>
Race
</Dropdown>
<!-- Save/Delete buttons -->
<div class="flex justify-end gap-2">
{#if substitution}
<Button
formaction="?/update_substitution"
color="secondary"
disabled={disable_inputs}
submit
width="w-1/2"
onclick={() => modalStore.close()}
>
Save Changes
</Button>
<Button
color="primary"
submit
disabled={disable_inputs}
formaction="?/delete_substitution"
width="w-1/2"
onclick={() => modalStore.close()}
>
Delete
</Button>
{:else}
<Button
formaction="?/create_substitution"
color="tertiary"
submit
width="w-full"
disabled={disable_inputs}
onclick={() => modalStore.close()}
>
Create Substitution
</Button>
<!-- This is also disabled, because the ID should only be -->
<!-- "leaked" to users that are allowed to use the inputs -->
{#if substitution && !disabled}
<input name="id" type="hidden" value={substitution.id} />
{/if}
</div>
</div>
</form>
</Card>
<div class="flex flex-col gap-2">
<!-- Substitute select -->
<Dropdown
name="substitute"
input_variable={substitute_select_value}
action={register_substitute_preview_handler}
options={driver_dropdown_options(inactive_drivers(drivers))}
{labelwidth}
{disabled}
{required}
>
Substitute
</Dropdown>
<!-- Driver select -->
<Dropdown
name="for"
input_variable={driver_select_value}
options={driver_dropdown_options(active_drivers(drivers))}
{labelwidth}
{disabled}
{required}
>
For
</Dropdown>
<!-- Race select -->
{#await data.races then races}
<Dropdown
name="race"
input_variable={race_select_value}
options={race_dropdown_options(races)}
{labelwidth}
{disabled}
{required}
>
Race
</Dropdown>
{/await}
<!-- Save/Delete buttons -->
<div class="flex justify-end gap-2">
{#if substitution}
<Button
formaction="?/update_substitution"
color="secondary"
{disabled}
submit
width="w-1/2"
>
Save Changes
</Button>
<Button
formaction="?/delete_substitution"
color="primary"
{disabled}
submit
width="w-1/2"
>
Delete
</Button>
{:else}
<Button
formaction="?/create_substitution"
color="tertiary"
{disabled}
submit
width="w-full"
>
Create Substitution
</Button>
{/if}
</div>
</div>
</form>
</Card>
{/await}
{/await}

View File

@ -1,28 +1,35 @@
<script lang="ts">
import { get_by_value, get_driver_headshot_template } from "$lib/database";
import { get_by_value } from "$lib/database";
import { getModalStore, type ModalSettings, type ModalStore } from "@skeletonlabs/skeleton";
import type { PageData } from "./$types";
import type { Driver, Race, Substitution } from "$lib/schema";
import type { Race, Substitution } from "$lib/schema";
import { Button, Table, type TableColumn } from "$lib/components";
let { data }: { data: PageData } = $props();
// TODO: Cleanup
const update_substitution_substitute_select_values: { [key: string]: string } = $state({});
const update_substitution_for_select_values: { [key: string]: string } = $state({});
const update_substitution_race_select_values: { [key: string]: string } = $state({});
data.substitutions.then((substitutions: Substitution[]) =>
substitutions.forEach((substitution: Substitution) => {
update_substitution_substitute_select_values[substitution.id] = substitution.substitute;
update_substitution_for_select_values[substitution.id] = substitution.for;
update_substitution_race_select_values[substitution.id] = substitution.race;
}),
);
update_substitution_substitute_select_values["create"] = "";
update_substitution_for_select_values["create"] = "";
update_substitution_race_select_values["create"] = "";
const modalStore: ModalStore = getModalStore();
const substitution_handler = async (event: Event, id?: string) => {
const substitution: Substitution | undefined = get_by_value(
await data.substitutions,
"id",
id ?? "Invalid",
);
const substitutions_columns: TableColumn[] = [
if (id && !substitution) return;
const modalSettings: ModalSettings = {
type: "component",
component: "substitutionCard",
meta: {
data,
substitution,
},
};
modalStore.trigger(modalSettings);
};
const substitutions_columns: TableColumn[] = $derived([
{
data_value_name: "expand",
label: "Step",
@ -49,56 +56,14 @@
valuefun: async (value: string): Promise<string> =>
get_by_value(await data.races, "id", value)?.name ?? "Invalid",
},
];
const modalStore: ModalStore = getModalStore();
const substitutions_handler = async (event: Event, id: string) => {
const substitution: Substitution | undefined = get_by_value(await data.substitutions, "id", id);
if (!substitution) return;
const modalSettings: ModalSettings = {
type: "component",
component: "substitutionCard",
meta: {
substitution: substitution,
drivers: await data.drivers,
races: await data.races,
substitute_select_value: update_substitution_substitute_select_values[substitution.id],
driver_select_value: update_substitution_for_select_values[substitution.id],
race_select_value: update_substitution_race_select_values[substitution.id],
disable_inputs: !data.admin,
},
};
modalStore.trigger(modalSettings);
};
const create_substitution_handler = async (event: Event) => {
const modalSettings: ModalSettings = {
type: "component",
component: "substitutionCard",
meta: {
drivers: await data.drivers,
races: await data.races,
substitute_select_value: update_substitution_substitute_select_values["create"],
driver_select_value: update_substitution_for_select_values["create"],
disable_inputs: !data.admin,
race_select_value: update_substitution_race_select_values["create"],
require_inputs: true,
headshot_template: get_driver_headshot_template(await data.graphics),
},
};
modalStore.trigger(modalSettings);
};
]);
</script>
<div class="pb-2">
<Button width="w-full" color="tertiary" onclick={create_substitution_handler} shadow>
<Button width="w-full" color="tertiary" onclick={substitution_handler} shadow>
<span class="font-bold">Create New Substitution</span>
</Button>
</div>
{#await data.substitutions then substitutions}
<Table data={substitutions} columns={substitutions_columns} handler={substitutions_handler} />
<Table data={substitutions} columns={substitutions_columns} handler={substitution_handler} />
{/await}