Add initial portal frontend for configuration managemetn
This commit is contained in:
@@ -55,9 +55,10 @@ class CalculatorStandardsConfigService {
|
|||||||
return Date.now() - this.lastRefreshedAt >= this.refreshInterval;
|
return Date.now() - this.lastRefreshedAt >= this.refreshInterval;
|
||||||
},
|
},
|
||||||
isIncomplete: function () {
|
isIncomplete: function () {
|
||||||
return !this.data.datasetId || !this.data.config
|
return !this.data.id || !this.data.datasetId || !this.data.config
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
|
id: null as string | null,
|
||||||
datasetId: null as string | null,
|
datasetId: null as string | null,
|
||||||
config: null as StandardsConfig | null
|
config: null as StandardsConfig | null
|
||||||
},
|
},
|
||||||
@@ -65,10 +66,10 @@ class CalculatorStandardsConfigService {
|
|||||||
|
|
||||||
constructor(private name: string) { }
|
constructor(private name: string) { }
|
||||||
|
|
||||||
async get(): Promise<StandardsConfig> {
|
async get(): Promise<StandardsConfig & { id: string }> {
|
||||||
await this.refresh({ onlyIfStale: true });
|
await this.refresh({ onlyIfStale: true });
|
||||||
if (!this.cache.data.config) throw new CalculatorUnavailableError("Unable to determine the calculator's standards configuration");
|
if (!this.cache.data.id || !this.cache.data.config) throw new CalculatorUnavailableError("Unable to determine the calculator's standards configuration");
|
||||||
return this.cache.data.config;
|
return { id: this.cache.data.id, ...this.cache.data.config };
|
||||||
}
|
}
|
||||||
|
|
||||||
async refresh(opt: { onlyIfStale?: boolean } = {}): Promise<void> {
|
async refresh(opt: { onlyIfStale?: boolean } = {}): Promise<void> {
|
||||||
@@ -79,6 +80,7 @@ class CalculatorStandardsConfigService {
|
|||||||
.innerJoin("standards_configs", "standards_configs.id", "calculators.standards_config_id")
|
.innerJoin("standards_configs", "standards_configs.id", "calculators.standards_config_id")
|
||||||
.innerJoin("standards_datasets", "standards_datasets.id", "standards_configs.dataset_id")
|
.innerJoin("standards_datasets", "standards_datasets.id", "standards_configs.dataset_id")
|
||||||
.select([
|
.select([
|
||||||
|
"standards_configs.id as id",
|
||||||
"standards_configs.dataset_id as datasetId",
|
"standards_configs.dataset_id as datasetId",
|
||||||
"standards_configs.params as params",
|
"standards_configs.params as params",
|
||||||
"standards_datasets.data as data",
|
"standards_datasets.data as data",
|
||||||
@@ -89,6 +91,7 @@ class CalculatorStandardsConfigService {
|
|||||||
Value.Assert(StandardsParamsSchema, result.params);
|
Value.Assert(StandardsParamsSchema, result.params);
|
||||||
Value.Assert(StandardsDataSchema, result.data);
|
Value.Assert(StandardsDataSchema, result.data);
|
||||||
|
|
||||||
|
this.cache.data.id = result.id;
|
||||||
this.cache.data.datasetId = result.datasetId;
|
this.cache.data.datasetId = result.datasetId;
|
||||||
this.cache.data.config = {
|
this.cache.data.config = {
|
||||||
data: result.data,
|
data: result.data,
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {
|
import {
|
||||||
Standards,
|
Standards,
|
||||||
StandardsConfigSchema,
|
StandardsParamsSchema,
|
||||||
type StandardsConfig,
|
StandardsDataSchema,
|
||||||
|
type StandardsParams,
|
||||||
|
type StandardsData,
|
||||||
} from "@blade-and-brawn/calculator";
|
} from "@blade-and-brawn/calculator";
|
||||||
import {
|
import {
|
||||||
Activity,
|
Activity,
|
||||||
@@ -16,100 +18,325 @@
|
|||||||
import { Value } from "@sinclair/typebox/value";
|
import { Value } from "@sinclair/typebox/value";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
let activeTab = $state("standards");
|
type ConfigSummary = { id: string; name: string; datasetId: string };
|
||||||
|
type DatasetSummary = { id: string; name: string };
|
||||||
|
type Editor = {
|
||||||
|
configId: string | null;
|
||||||
|
name: string;
|
||||||
|
datasetId: string;
|
||||||
|
params: StandardsParams;
|
||||||
|
data: StandardsData;
|
||||||
|
};
|
||||||
|
type Snapshot = { name: string; datasetId: string; params: StandardsParams };
|
||||||
|
|
||||||
let input = $state({
|
const DRAFT_KEY = "calculator-draft";
|
||||||
activity: Activity.BenchPress,
|
|
||||||
metrics: {
|
let activeTab = $state("standards");
|
||||||
|
let activity = $state(Activity.BenchPress);
|
||||||
|
let metrics = $state({
|
||||||
gender: Gender.Male,
|
gender: Gender.Male,
|
||||||
age: null as number | null,
|
age: null as number | null,
|
||||||
weight: null as number | null, // lb
|
weight: null as number | null, // lb
|
||||||
},
|
|
||||||
standardsConfig: null as StandardsConfig | null,
|
|
||||||
});
|
});
|
||||||
|
let configs = $state<ConfigSummary[]>([]);
|
||||||
|
let datasets = $state<DatasetSummary[]>([]);
|
||||||
|
let liveConfigId = $state<string | null>(null);
|
||||||
|
|
||||||
const selected = $derived({
|
let editor = $state<Editor | null>(null);
|
||||||
activity: input.activity,
|
let savedSnapshot = $state<Snapshot | null>(null);
|
||||||
metrics: {
|
|
||||||
gender: input.metrics.gender,
|
let loadError = $state<string | null>(null);
|
||||||
age: input.metrics.age ?? 0,
|
let saveError = $state<string | null>(null);
|
||||||
weight: lbToKg(input.metrics.weight ?? 0),
|
let saving = $state(false);
|
||||||
} as Metrics,
|
let settingLive = $state(false);
|
||||||
standardsConfig: input.standardsConfig,
|
|
||||||
});
|
|
||||||
|
|
||||||
const allStandards = $derived(
|
const allStandards = $derived(
|
||||||
selected.standardsConfig
|
editor
|
||||||
? new Standards($state.snapshot(selected.standardsConfig))
|
? new Standards($state.snapshot({ data: editor.data, params: editor.params }))
|
||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
let standardsConfigLoadError = $state<string | null>(null);
|
const isDirty = $derived.by(() => {
|
||||||
|
if (!editor || !savedSnapshot) return false;
|
||||||
|
const current: Snapshot = {
|
||||||
|
name: editor.name,
|
||||||
|
datasetId: editor.datasetId,
|
||||||
|
params: editor.params,
|
||||||
|
};
|
||||||
|
return JSON.stringify(current) !== JSON.stringify(savedSnapshot);
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedMetrics = $derived({
|
||||||
|
gender: metrics.gender,
|
||||||
|
age: metrics.age ?? 0,
|
||||||
|
weight: lbToKg(metrics.weight ?? 0),
|
||||||
|
} as Metrics);
|
||||||
|
|
||||||
|
function errorMessage(err: unknown): string {
|
||||||
|
const value = (err as { value?: { error?: string } })?.value;
|
||||||
|
return value?.error ?? (err instanceof Error ? err.message : "Unknown error");
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveDraft() {
|
||||||
|
if (editor) localStorage.setItem(DRAFT_KEY, JSON.stringify(editor));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshLists() {
|
||||||
|
const [configsRes, datasetsRes] = await Promise.all([
|
||||||
|
api.standards.configs.get(),
|
||||||
|
api.standards.datasets.get(),
|
||||||
|
]);
|
||||||
|
if (configsRes.error) throw configsRes.error;
|
||||||
|
if (datasetsRes.error) throw datasetsRes.error;
|
||||||
|
configs = configsRes.data;
|
||||||
|
datasets = datasetsRes.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadConfig(id: string, opt: { skipDirtyCheck?: boolean } = {}) {
|
||||||
|
if (!opt.skipDirtyCheck && isDirty && !confirm("Discard unsaved changes?")) return;
|
||||||
|
|
||||||
|
saveError = null;
|
||||||
|
const res = await api.standards.configs({ id }).get();
|
||||||
|
if (res.error) {
|
||||||
|
saveError = errorMessage(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Value.Assert(StandardsParamsSchema, res.data.params);
|
||||||
|
Value.Assert(StandardsDataSchema, res.data.data);
|
||||||
|
|
||||||
|
editor = {
|
||||||
|
configId: res.data.id,
|
||||||
|
name: res.data.name,
|
||||||
|
datasetId: res.data.datasetId,
|
||||||
|
params: res.data.params,
|
||||||
|
data: res.data.data,
|
||||||
|
};
|
||||||
|
savedSnapshot = {
|
||||||
|
name: res.data.name,
|
||||||
|
datasetId: res.data.datasetId,
|
||||||
|
params: structuredClone(res.data.params),
|
||||||
|
};
|
||||||
|
saveDraft();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onDatasetChange(datasetId: string) {
|
||||||
|
if (!editor) return;
|
||||||
|
saveError = null;
|
||||||
|
const res = await api.standards.datasets({ id: datasetId }).get();
|
||||||
|
if (res.error) {
|
||||||
|
saveError = errorMessage(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Value.Assert(StandardsDataSchema, res.data.data);
|
||||||
|
editor.datasetId = datasetId;
|
||||||
|
editor.data = res.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function persist(mode: "update" | "create") {
|
||||||
|
if (!editor || !editor.name) return;
|
||||||
|
saveError = null;
|
||||||
|
saving = true;
|
||||||
|
try {
|
||||||
|
if (mode === "update" && editor.configId) {
|
||||||
|
const res = await api.standards.configs({ id: editor.configId }).put({
|
||||||
|
name: editor.name,
|
||||||
|
datasetId: editor.datasetId,
|
||||||
|
params: editor.params,
|
||||||
|
});
|
||||||
|
if (res.error) throw res.error;
|
||||||
|
} else {
|
||||||
|
const res = await api.standards.configs.post({
|
||||||
|
name: editor.name,
|
||||||
|
datasetId: editor.datasetId,
|
||||||
|
params: editor.params,
|
||||||
|
});
|
||||||
|
if (res.error) throw res.error;
|
||||||
|
editor.configId = res.data.id;
|
||||||
|
}
|
||||||
|
savedSnapshot = {
|
||||||
|
name: editor.name,
|
||||||
|
datasetId: editor.datasetId,
|
||||||
|
params: $state.snapshot(editor.params),
|
||||||
|
};
|
||||||
|
saveDraft();
|
||||||
|
await refreshLists();
|
||||||
|
} catch (err) {
|
||||||
|
saveError = errorMessage(err);
|
||||||
|
} finally {
|
||||||
|
saving = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
return persist(editor?.configId ? "update" : "create");
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveAsNew() {
|
||||||
|
return persist("create");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function discard() {
|
||||||
|
if (!editor?.configId) return;
|
||||||
|
await loadConfig(editor.configId, { skipDirtyCheck: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setLive() {
|
||||||
|
if (!editor?.configId || isDirty) return;
|
||||||
|
saveError = null;
|
||||||
|
settingLive = true;
|
||||||
|
try {
|
||||||
|
const res = await api.calculator.standards.config.switch.post({
|
||||||
|
standardsConfigId: editor.configId,
|
||||||
|
});
|
||||||
|
if (res.error) throw res.error;
|
||||||
|
liveConfigId = editor.configId;
|
||||||
|
} catch (err) {
|
||||||
|
saveError = errorMessage(err);
|
||||||
|
} finally {
|
||||||
|
settingLive = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
try {
|
try {
|
||||||
const savedInputStringified = localStorage.getItem("input");
|
await refreshLists();
|
||||||
if (savedInputStringified) {
|
|
||||||
input = JSON.parse(savedInputStringified);
|
const liveRes = await api.calculator.standards.config.get();
|
||||||
|
if (liveRes.error) throw liveRes.error;
|
||||||
|
Value.Assert(StandardsParamsSchema, liveRes.data.params);
|
||||||
|
Value.Assert(StandardsDataSchema, liveRes.data.data);
|
||||||
|
liveConfigId = liveRes.data.id;
|
||||||
|
|
||||||
|
const draftStringified = localStorage.getItem(DRAFT_KEY);
|
||||||
|
if (draftStringified) {
|
||||||
|
const draft = JSON.parse(draftStringified) as Editor;
|
||||||
|
editor = draft;
|
||||||
|
if (draft.configId) {
|
||||||
|
const configRes = await api.standards.configs({ id: draft.configId }).get();
|
||||||
|
if (!configRes.error) {
|
||||||
|
savedSnapshot = {
|
||||||
|
name: configRes.data.name,
|
||||||
|
datasetId: configRes.data.datasetId,
|
||||||
|
params: configRes.data.params,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const res = await api.calculator.standards.config.get();
|
await loadConfig(liveRes.data.id, { skipDirtyCheck: true });
|
||||||
if (res.error) throw res.error;
|
|
||||||
Value.Assert(StandardsConfigSchema, res.data);
|
|
||||||
input.standardsConfig = res.data;
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("failed to load standards config", err);
|
loadError = errorMessage(err);
|
||||||
const value = (err as { value?: { error?: string } })?.value;
|
|
||||||
standardsConfigLoadError =
|
|
||||||
value?.error ??
|
|
||||||
(err instanceof Error ? err.message : "Unknown error");
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
localStorage.setItem("input", JSON.stringify(input));
|
if (editor) saveDraft();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if !(input.standardsConfig && allStandards)}
|
{#if !(editor && allStandards)}
|
||||||
<div class="w-full flex justify-center pt-10">
|
<div class="w-full flex justify-center pt-10">
|
||||||
{#if standardsConfigLoadError}
|
{#if loadError}
|
||||||
<div role="alert" class="alert alert-error max-w-lg">
|
<div role="alert" class="alert alert-error max-w-lg">
|
||||||
<span>
|
<span>Failed to load the calculator: {loadError}</span>
|
||||||
Failed to load the standards configuration: {standardsConfigLoadError}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="loading loading-spinner loading-lg"></span>
|
<span class="loading loading-spinner loading-lg"></span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
{@const standardsConfig = input.standardsConfig}
|
{@const currentEditor = editor}
|
||||||
<section class="w-full flex justify-between gap-2 pt-10 items-center">
|
<div
|
||||||
|
class="w-full -mt-10 -mx-10 px-6 py-2 bg-base-200 border-b border-base-300 flex items-center gap-2 flex-wrap text-sm"
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
class="select select-sm"
|
||||||
|
value={currentEditor.configId ?? ""}
|
||||||
|
onchange={(e) => loadConfig(e.currentTarget.value)}
|
||||||
|
>
|
||||||
|
{#each configs as config}
|
||||||
|
<option value={config.id}>
|
||||||
|
{config.name || "(untitled)"}{config.id === liveConfigId ? " (live)" : ""}
|
||||||
|
</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<details class="dropdown">
|
||||||
|
<summary
|
||||||
|
class="btn btn-ghost btn-sm gap-1 font-normal opacity-70 list-none [&::-webkit-details-marker]:hidden"
|
||||||
|
>
|
||||||
|
Edit ▾
|
||||||
|
</summary>
|
||||||
|
<div class="dropdown-content z-10 menu bg-base-100 rounded-box shadow p-4 gap-3 w-64 mt-2">
|
||||||
|
<label class="w-full">
|
||||||
|
<span class="label mb-1">Name</span>
|
||||||
|
<input
|
||||||
|
class="input input-sm w-full"
|
||||||
|
bind:value={currentEditor.name}
|
||||||
|
placeholder="Config name"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label class="w-full">
|
||||||
|
<span class="label mb-1">Dataset</span>
|
||||||
|
<select
|
||||||
|
class="select select-sm w-full"
|
||||||
|
value={currentEditor.datasetId}
|
||||||
|
onchange={(e) => onDatasetChange(e.currentTarget.value)}
|
||||||
|
>
|
||||||
|
{#each datasets as dataset}
|
||||||
|
<option value={dataset.id}>{dataset.name}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
{#if isDirty}
|
||||||
|
<span class="badge badge-warning badge-sm">Unsaved</span>
|
||||||
|
{:else if currentEditor.configId === liveConfigId}
|
||||||
|
<span class="badge badge-success badge-sm">Live</span>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="flex-1"></div>
|
||||||
|
|
||||||
|
<button class="btn btn-ghost btn-sm" disabled={!isDirty} onclick={discard}>
|
||||||
|
Discard
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-outline btn-sm"
|
||||||
|
disabled={settingLive || isDirty || currentEditor.configId === liveConfigId}
|
||||||
|
onclick={setLive}
|
||||||
|
>
|
||||||
|
{settingLive ? "Setting live..." : "Set as live"}
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline btn-sm" disabled={saving || !currentEditor.name} onclick={saveAsNew}>
|
||||||
|
Save as new
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-primary btn-sm" disabled={saving || !isDirty} onclick={save}>
|
||||||
|
{saving ? "Saving..." : "Save"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if saveError}
|
||||||
|
<div role="alert" class="alert alert-error w-full mt-4"><span>{saveError}</span></div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<section class="w-full flex flex-col gap-4 mt-6">
|
||||||
|
<div class="flex justify-between gap-2 items-center flex-wrap">
|
||||||
<fieldset class="fieldset bg-base-200 p-6 max-w-lg">
|
<fieldset class="fieldset bg-base-200 p-6 max-w-lg">
|
||||||
<legend class="fieldset-legend text-lg font-semibold">
|
<legend class="fieldset-legend text-lg font-semibold">Data</legend>
|
||||||
Data
|
|
||||||
</legend>
|
|
||||||
|
|
||||||
<div class="flex gap-4">
|
<div class="flex gap-4">
|
||||||
<fieldset class="fieldset bg-base-300 p-3 max-w-xl">
|
<fieldset class="fieldset bg-base-300 p-3 max-w-xl">
|
||||||
<legend class="fieldset-legend text-sm font-semibold">
|
<legend class="fieldset-legend text-sm font-semibold">General</legend>
|
||||||
General
|
|
||||||
</legend>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-4">
|
<div class="grid grid-cols-1 gap-4">
|
||||||
<label>
|
<label>
|
||||||
<span class="label mb-1">Activity</span>
|
<span class="label mb-1">Activity</span>
|
||||||
<select
|
<select class="select w-full" name="activities" bind:value={activity}>
|
||||||
class="select w-full"
|
{#each Object.values(Activity) as a}
|
||||||
name="activities"
|
<option value={a}>
|
||||||
bind:value={input.activity}
|
{allStandards.byActivity(a).getMetadata().name}
|
||||||
>
|
|
||||||
{#each Object.values(Activity) as activity}
|
|
||||||
<option value={activity}>
|
|
||||||
{allStandards
|
|
||||||
.byActivity(activity)
|
|
||||||
.getMetadata().name}
|
|
||||||
</option>
|
</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
@@ -122,28 +349,20 @@
|
|||||||
min="1"
|
min="1"
|
||||||
max="100"
|
max="100"
|
||||||
placeholder="5"
|
placeholder="5"
|
||||||
bind:value={
|
bind:value={currentEditor.params.global.maxLevel}
|
||||||
standardsConfig.params.global.maxLevel
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset class="fieldset bg-base-300 p-3 max-w-3xs">
|
<fieldset class="fieldset bg-base-300 p-3 max-w-3xs">
|
||||||
<legend class="fieldset-legend text-sm font-semibold">
|
<legend class="fieldset-legend text-sm font-semibold">Data Generation</legend>
|
||||||
Data Generation
|
|
||||||
</legend>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
bind:checked={
|
bind:checked={currentEditor.params.activity[activity].enableGeneration}
|
||||||
standardsConfig.params.activity[
|
|
||||||
selected.activity
|
|
||||||
].enableGeneration
|
|
||||||
}
|
|
||||||
class="toggle toggle-lg m-auto"
|
class="toggle toggle-lg m-auto"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
@@ -156,32 +375,10 @@
|
|||||||
max="1"
|
max="1"
|
||||||
step=".05"
|
step=".05"
|
||||||
placeholder=".1"
|
placeholder=".1"
|
||||||
bind:value={
|
bind:value={currentEditor.params.activity[activity].weightModifier}
|
||||||
standardsConfig.params.activity[
|
disabled={!currentEditor.params.activity[activity].enableGeneration}
|
||||||
selected.activity
|
|
||||||
].weightModifier
|
|
||||||
}
|
|
||||||
disabled={!standardsConfig.params.activity[
|
|
||||||
selected.activity
|
|
||||||
].enableGeneration}
|
|
||||||
/>
|
/>
|
||||||
</label>
|
</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>
|
<label>
|
||||||
<span class="label mb-1">Difficulty modifier</span>
|
<span class="label mb-1">Difficulty modifier</span>
|
||||||
<input
|
<input
|
||||||
@@ -190,14 +387,8 @@
|
|||||||
min="-1"
|
min="-1"
|
||||||
max="1"
|
max="1"
|
||||||
step=".05"
|
step=".05"
|
||||||
bind:value={
|
bind:value={currentEditor.params.activity[activity].difficultyModifier}
|
||||||
standardsConfig.params.activity[
|
disabled={!currentEditor.params.activity[activity].enableGeneration}
|
||||||
selected.activity
|
|
||||||
].difficultyModifier
|
|
||||||
}
|
|
||||||
disabled={!standardsConfig.params.activity[
|
|
||||||
selected.activity
|
|
||||||
].enableGeneration}
|
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
@@ -208,14 +399,8 @@
|
|||||||
min="-1"
|
min="-1"
|
||||||
max="1"
|
max="1"
|
||||||
step=".05"
|
step=".05"
|
||||||
bind:value={
|
bind:value={currentEditor.params.activity[activity].ageModifier}
|
||||||
standardsConfig.params.activity[
|
disabled={!currentEditor.params.activity[activity].enableGeneration}
|
||||||
selected.activity
|
|
||||||
].ageModifier
|
|
||||||
}
|
|
||||||
disabled={!standardsConfig.params.activity[
|
|
||||||
selected.activity
|
|
||||||
].enableGeneration}
|
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
@@ -226,23 +411,13 @@
|
|||||||
min="1"
|
min="1"
|
||||||
max="100"
|
max="100"
|
||||||
step="1"
|
step="1"
|
||||||
bind:value={
|
bind:value={currentEditor.params.activity[activity].peakAge}
|
||||||
standardsConfig.params.activity[
|
disabled={!currentEditor.params.activity[activity].enableGeneration}
|
||||||
selected.activity
|
|
||||||
].peakAge
|
|
||||||
}
|
|
||||||
disabled={!standardsConfig.params.activity[
|
|
||||||
selected.activity
|
|
||||||
].enableGeneration}
|
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<fieldset class="fieldset bg-base-200 p-3 max-w-xl">
|
<fieldset class="fieldset bg-base-200 p-3 max-w-xl">
|
||||||
<legend
|
<legend class="fieldset-legend text-sm font-semibold">Stretch</legend>
|
||||||
class="fieldset-legend text-sm font-semibold"
|
|
||||||
>
|
|
||||||
Stretch
|
|
||||||
</legend>
|
|
||||||
<label>
|
<label>
|
||||||
<span class="label mb-1">Lower</span>
|
<span class="label mb-1">Lower</span>
|
||||||
<input
|
<input
|
||||||
@@ -251,14 +426,8 @@
|
|||||||
min="0"
|
min="0"
|
||||||
max="10"
|
max="10"
|
||||||
step="1"
|
step="1"
|
||||||
bind:value={
|
bind:value={currentEditor.params.activity[activity].stretch.lower}
|
||||||
standardsConfig.params.activity[
|
disabled={!currentEditor.params.activity[activity].enableGeneration}
|
||||||
selected.activity
|
|
||||||
].stretch.lower
|
|
||||||
}
|
|
||||||
disabled={!standardsConfig.params.activity[
|
|
||||||
selected.activity
|
|
||||||
].enableGeneration}
|
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
@@ -269,14 +438,8 @@
|
|||||||
min="0"
|
min="0"
|
||||||
max="10"
|
max="10"
|
||||||
step="1"
|
step="1"
|
||||||
bind:value={
|
bind:value={currentEditor.params.activity[activity].stretch.upper}
|
||||||
standardsConfig.params.activity[
|
disabled={!currentEditor.params.activity[activity].enableGeneration}
|
||||||
selected.activity
|
|
||||||
].stretch.upper
|
|
||||||
}
|
|
||||||
disabled={!standardsConfig.params.activity[
|
|
||||||
selected.activity
|
|
||||||
].enableGeneration}
|
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
@@ -287,17 +450,11 @@
|
|||||||
|
|
||||||
{#if activeTab === "standards"}
|
{#if activeTab === "standards"}
|
||||||
<fieldset class="fieldset bg-base-200 p-6 max-w-lg">
|
<fieldset class="fieldset bg-base-200 p-6 max-w-lg">
|
||||||
<legend class="fieldset-legend text-lg font-semibold">
|
<legend class="fieldset-legend text-lg font-semibold">Metrics</legend>
|
||||||
Metrics
|
|
||||||
</legend>
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<label>
|
<label>
|
||||||
<span class="label mb-1">Gender</span>
|
<span class="label mb-1">Gender</span>
|
||||||
<select
|
<select class="select w-full" name="genders" bind:value={metrics.gender}>
|
||||||
class="select w-full"
|
|
||||||
name="genders"
|
|
||||||
bind:value={input.metrics.gender}
|
|
||||||
>
|
|
||||||
{#each Object.values(Gender) as gender}
|
{#each Object.values(Gender) as gender}
|
||||||
<option value={gender}>{gender}</option>
|
<option value={gender}>{gender}</option>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -312,7 +469,7 @@
|
|||||||
min="0"
|
min="0"
|
||||||
max="100"
|
max="100"
|
||||||
placeholder="18"
|
placeholder="18"
|
||||||
bind:value={input.metrics.age}
|
bind:value={metrics.age}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
@@ -324,12 +481,13 @@
|
|||||||
min="0"
|
min="0"
|
||||||
max="600"
|
max="600"
|
||||||
placeholder="170"
|
placeholder="170"
|
||||||
bind:value={input.metrics.weight}
|
bind:value={metrics.weight}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
@@ -356,7 +514,11 @@
|
|||||||
|
|
||||||
{#if activeTab === "standards"}
|
{#if activeTab === "standards"}
|
||||||
<StandardsTable
|
<StandardsTable
|
||||||
selected={{ ...selected, standardsConfig }}
|
selected={{
|
||||||
|
activity,
|
||||||
|
metrics: selectedMetrics,
|
||||||
|
standardsConfig: { data: currentEditor.data, params: currentEditor.params },
|
||||||
|
}}
|
||||||
{allStandards}
|
{allStandards}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
Reference in New Issue
Block a user