Finish basic setup for configuration loading

This commit is contained in:
Dominic Ferrando
2026-07-04 19:05:38 -04:00
parent a888d24bfd
commit 338a40d947
11 changed files with 5724 additions and 272 deletions
@@ -1,8 +1,8 @@
<script lang="ts">
import {
DEFAULT_STANDARDS_CONFIG,
Standards,
type StandardsParams,
StandardsConfigSchema,
type StandardsConfig,
} from "@blade-and-brawn/calculator";
import {
Activity,
@@ -12,6 +12,9 @@
} from "@blade-and-brawn/domain";
import StandardsTable from "$lib/components/StandardsTable.svelte";
import PlayersTable from "$lib/components/PlayersTable.svelte";
import { api } from "$lib/api";
import { Value } from "@sinclair/typebox/value";
import { onMount } from "svelte";
let activeTab = $state("standards");
@@ -22,9 +25,7 @@
age: null as number | null,
weight: null as number | null, // lb
},
params: structuredClone(
DEFAULT_STANDARDS_CONFIG.params,
) as StandardsParams,
standardsConfig: null as StandardsConfig | null,
});
const selected = $derived({
@@ -34,288 +35,333 @@
age: input.metrics.age ?? 0,
weight: lbToKg(input.metrics.weight ?? 0),
} as Metrics,
params: input.params,
standardsConfig: input.standardsConfig,
});
const allStandards = $derived(
new Standards({
data: DEFAULT_STANDARDS_CONFIG.data,
params: selected.params,
}),
selected.standardsConfig
? new Standards($state.snapshot(selected.standardsConfig))
: null,
);
$effect(() => {
const savedParameters = localStorage.getItem("parameters");
if (!savedParameters) return;
let standardsConfigLoadError = $state<string | null>(null);
const parsed = JSON.parse(savedParameters);
if (parsed?.params) input = parsed;
onMount(async () => {
try {
const savedInputStringified = localStorage.getItem("input");
if (savedInputStringified) {
input = JSON.parse(savedInputStringified);
} else {
const res = await api.calculator.config.get();
if (res.error) throw res.error;
Value.Assert(StandardsConfigSchema, res.data);
input.standardsConfig = res.data;
}
} catch (err) {
console.error("failed to load standards config", err);
const value = (err as { value?: { error?: string } })?.value;
standardsConfigLoadError =
value?.error ??
(err instanceof Error ? err.message : "Unknown error");
}
});
$effect(() => {
localStorage.setItem("parameters", JSON.stringify(input));
localStorage.setItem("input", JSON.stringify(input));
});
</script>
<section class="w-full flex justify-between gap-2 pt-10 items-center">
<fieldset class="fieldset bg-base-200 p-6 max-w-lg">
<legend class="fieldset-legend text-lg font-semibold"> Data </legend>
{#if !(input.standardsConfig && allStandards)}
<div class="w-full flex justify-center pt-10">
{#if standardsConfigLoadError}
<div role="alert" class="alert alert-error max-w-lg">
<span>
Failed to load the standards configuration: {standardsConfigLoadError}
</span>
</div>
{:else}
<span class="loading loading-spinner loading-lg"></span>
{/if}
</div>
{:else}
{@const standardsConfig = input.standardsConfig}
<section class="w-full flex justify-between gap-2 pt-10 items-center">
<fieldset class="fieldset bg-base-200 p-6 max-w-lg">
<legend class="fieldset-legend text-lg font-semibold">
Data
</legend>
<div class="flex gap-4">
<fieldset class="fieldset bg-base-300 p-3 max-w-xl">
<legend class="fieldset-legend text-sm font-semibold">
General
<div class="flex gap-4">
<fieldset class="fieldset bg-base-300 p-3 max-w-xl">
<legend class="fieldset-legend text-sm font-semibold">
General
</legend>
<div class="grid grid-cols-1 gap-4">
<label>
<span class="label mb-1">Activity</span>
<select
class="select w-full"
name="activities"
bind:value={input.activity}
>
{#each Object.values(Activity) as activity}
<option value={activity}>
{allStandards
.byActivity(activity)
.getMetadata().name}
</option>
{/each}
</select>
</label>
<label>
<span class="label mb-1">Max level</span>
<input
class="input w-full"
type="number"
min="1"
max="100"
placeholder="5"
bind:value={
standardsConfig.params.global.maxLevel
}
/>
</label>
</div>
</fieldset>
<fieldset class="fieldset bg-base-300 p-3 max-w-3xs">
<legend class="fieldset-legend text-sm font-semibold">
Data Generation
</legend>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<label class="label">
<input
type="checkbox"
bind:checked={
standardsConfig.params.activity[
selected.activity
].enableGeneration
}
class="toggle toggle-lg m-auto"
/>
</label>
<label>
<span class="label mb-1">Weight significance</span>
<input
class="input w-full"
type="number"
min="-1"
max="1"
step=".05"
placeholder=".1"
bind:value={
standardsConfig.params.activity[
selected.activity
].weightModifier
}
disabled={!standardsConfig.params.activity[
selected.activity
].enableGeneration}
/>
</label>
<!-- <label> -->
<!-- <span class="label mb-1">Weight skew</span> -->
<!-- <input -->
<!-- class="input w-full" -->
<!-- type="number" -->
<!-- min="-1" -->
<!-- max="1" -->
<!-- step=".05" -->
<!-- bind:value={ -->
<!-- inputSelected.cfg.activity[selected.activity] -->
<!-- .weightSkew -->
<!-- } -->
<!-- disabled={!selected.params.activity[selected.activity] -->
<!-- .enableGeneration} -->
<!-- /> -->
<!-- </label> -->
<label>
<span class="label mb-1">Difficulty modifier</span>
<input
class="input w-full"
type="number"
min="-1"
max="1"
step=".05"
bind:value={
standardsConfig.params.activity[
selected.activity
].difficultyModifier
}
disabled={!standardsConfig.params.activity[
selected.activity
].enableGeneration}
/>
</label>
<label>
<span class="label mb-1">Age significance</span>
<input
class="input w-full"
type="number"
min="-1"
max="1"
step=".05"
bind:value={
standardsConfig.params.activity[
selected.activity
].ageModifier
}
disabled={!standardsConfig.params.activity[
selected.activity
].enableGeneration}
/>
</label>
<label>
<span class="label mb-1">Peak age</span>
<input
class="input w-full"
type="number"
min="1"
max="100"
step="1"
bind:value={
standardsConfig.params.activity[
selected.activity
].peakAge
}
disabled={!standardsConfig.params.activity[
selected.activity
].enableGeneration}
/>
</label>
<fieldset class="fieldset bg-base-200 p-3 max-w-xl">
<legend
class="fieldset-legend text-sm font-semibold"
>
Stretch
</legend>
<label>
<span class="label mb-1">Lower</span>
<input
class="input w-full"
type="number"
min="0"
max="10"
step="1"
bind:value={
standardsConfig.params.activity[
selected.activity
].stretch.lower
}
disabled={!standardsConfig.params.activity[
selected.activity
].enableGeneration}
/>
</label>
<label>
<span class="label mb-1">Upper</span>
<input
class="input w-full"
type="number"
min="0"
max="10"
step="1"
bind:value={
standardsConfig.params.activity[
selected.activity
].stretch.upper
}
disabled={!standardsConfig.params.activity[
selected.activity
].enableGeneration}
/>
</label>
</fieldset>
</div>
</fieldset>
</div>
</fieldset>
{#if activeTab === "standards"}
<fieldset class="fieldset bg-base-200 p-6 max-w-lg">
<legend class="fieldset-legend text-lg font-semibold">
Metrics
</legend>
<div class="grid grid-cols-1 gap-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<label>
<span class="label mb-1">Activity</span>
<span class="label mb-1">Gender</span>
<select
class="select w-full"
name="activities"
bind:value={input.activity}
name="genders"
bind:value={input.metrics.gender}
>
{#each Object.values(Activity) as activity}
<option value={activity}>
{allStandards
.byActivity(activity)
.getMetadata().name}
</option>
{#each Object.values(Gender) as gender}
<option value={gender}>{gender}</option>
{/each}
</select>
</label>
<label>
<span class="label mb-1">Max level</span>
<span class="label mb-1">Age</span>
<input
class="input w-full"
type="number"
min="1"
min="0"
max="100"
placeholder="5"
bind:value={input.params.global.maxLevel}
placeholder="18"
bind:value={input.metrics.age}
/>
</label>
<label>
<span class="label mb-1">Weight (lb)</span>
<input
class="input w-full"
type="number"
min="0"
max="600"
placeholder="170"
bind:value={input.metrics.weight}
/>
</label>
</div>
</fieldset>
{/if}
</section>
<fieldset class="fieldset bg-base-300 p-3 max-w-3xs">
<legend class="fieldset-legend text-sm font-semibold">
Data Generation
</legend>
<div class="divider"></div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<label class="label">
<input
type="checkbox"
bind:checked={
input.params.activity[selected.activity]
.enableGeneration
}
class="toggle toggle-lg m-auto"
/>
</label>
<label>
<span class="label mb-1">Weight significance</span>
<input
class="input w-full"
type="number"
min="-1"
max="1"
step=".05"
placeholder=".1"
bind:value={
input.params.activity[selected.activity]
.weightModifier
}
disabled={!selected.params.activity[
selected.activity
].enableGeneration}
/>
</label>
<!-- <label> -->
<!-- <span class="label mb-1">Weight skew</span> -->
<!-- <input -->
<!-- class="input w-full" -->
<!-- type="number" -->
<!-- min="-1" -->
<!-- max="1" -->
<!-- step=".05" -->
<!-- bind:value={ -->
<!-- inputSelected.cfg.activity[selected.activity] -->
<!-- .weightSkew -->
<!-- } -->
<!-- disabled={!selected.params.activity[selected.activity] -->
<!-- .enableGeneration} -->
<!-- /> -->
<!-- </label> -->
<label>
<span class="label mb-1">Difficulty modifier</span>
<input
class="input w-full"
type="number"
min="-1"
max="1"
step=".05"
bind:value={
input.params.activity[selected.activity]
.difficultyModifier
}
disabled={!selected.params.activity[
selected.activity
].enableGeneration}
/>
</label>
<label>
<span class="label mb-1">Age significance</span>
<input
class="input w-full"
type="number"
min="-1"
max="1"
step=".05"
bind:value={
input.params.activity[selected.activity]
.ageModifier
}
disabled={!selected.params.activity[
selected.activity
].enableGeneration}
/>
</label>
<label>
<span class="label mb-1">Peak age</span>
<input
class="input w-full"
type="number"
min="1"
max="100"
step="1"
bind:value={
input.params.activity[selected.activity].peakAge
}
disabled={!selected.params.activity[
selected.activity
].enableGeneration}
/>
</label>
<div role="tablist" class="tabs tabs-border">
<button
role="tab"
class="tab"
class:tab-active={activeTab === "standards"}
onclick={() => (activeTab = "standards")}
>
Standards
</button>
<fieldset class="fieldset bg-base-200 p-3 max-w-xl">
<legend class="fieldset-legend text-sm font-semibold">
Stretch
</legend>
<label>
<span class="label mb-1">Lower</span>
<input
class="input w-full"
type="number"
min="0"
max="10"
step="1"
bind:value={
input.params.activity[selected.activity]
.stretch.lower
}
disabled={!selected.params.activity[
selected.activity
].enableGeneration}
/>
</label>
<label>
<span class="label mb-1">Upper</span>
<input
class="input w-full"
type="number"
min="0"
max="10"
step="1"
bind:value={
input.params.activity[selected.activity]
.stretch.upper
}
disabled={!selected.params.activity[
selected.activity
].enableGeneration}
/>
</label>
</fieldset>
</div>
</fieldset>
</div>
</fieldset>
<button
role="tab"
class="tab"
class:tab-active={activeTab === "players"}
onclick={() => (activeTab = "players")}
>
Players
</button>
</div>
{#if activeTab === "standards"}
<fieldset class="fieldset bg-base-200 p-6 max-w-lg">
<legend class="fieldset-legend text-lg font-semibold">
Metrics
</legend>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<label>
<span class="label mb-1">Gender</span>
<select
class="select w-full"
name="genders"
bind:value={input.metrics.gender}
>
{#each Object.values(Gender) as gender}
<option value={gender}>{gender}</option>
{/each}
</select>
</label>
<label>
<span class="label mb-1">Age</span>
<input
class="input w-full"
type="number"
min="0"
max="100"
placeholder="18"
bind:value={input.metrics.age}
/>
</label>
<label>
<span class="label mb-1">Weight (lb)</span>
<input
class="input w-full"
type="number"
min="0"
max="600"
placeholder="170"
bind:value={input.metrics.weight}
/>
</label>
</div>
</fieldset>
<StandardsTable
selected={{ ...selected, standardsConfig }}
{allStandards}
/>
{/if}
</section>
<div class="divider"></div>
<div role="tablist" class="tabs tabs-border">
<button
role="tab"
class="tab"
class:tab-active={activeTab === "standards"}
onclick={() => (activeTab = "standards")}
>
Standards
</button>
<button
role="tab"
class="tab"
class:tab-active={activeTab === "players"}
onclick={() => (activeTab = "players")}
>
Players
</button>
</div>
{#if activeTab === "standards"}
<StandardsTable {selected} {allStandards} />
{/if}
{#if activeTab === "players"}
<PlayersTable {allStandards} />
{#if activeTab === "players"}
<PlayersTable {allStandards} />
{/if}
{/if}