1.1.0 #29
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { beforeNavigate } from "$app/navigation";
|
||||
import {
|
||||
LevelCalculator,
|
||||
type LevelCalculatorOutput,
|
||||
@@ -26,12 +27,30 @@
|
||||
levels?: LevelCalculatorOutput;
|
||||
player: Player;
|
||||
activityPerformances: ActivityPerformance[];
|
||||
/** JSON snapshot of {player, activityPerformances} as of the last successful save/load. "" means never saved. */
|
||||
savedSnapshot: string;
|
||||
saving?: boolean;
|
||||
saveError?: string;
|
||||
deleting?: boolean;
|
||||
deleteError?: string;
|
||||
}
|
||||
|
||||
function snapshotOf(
|
||||
player: Player,
|
||||
activityPerformances: ActivityPerformance[],
|
||||
): string {
|
||||
return JSON.stringify({ player, activityPerformances });
|
||||
}
|
||||
|
||||
function isDirty(calculation: CalcData): boolean {
|
||||
return (
|
||||
snapshotOf(
|
||||
calculation.player,
|
||||
calculation.activityPerformances,
|
||||
) !== calculation.savedSnapshot
|
||||
);
|
||||
}
|
||||
|
||||
interface Props {
|
||||
allStandards: Standards;
|
||||
}
|
||||
@@ -54,21 +73,25 @@
|
||||
} as const satisfies Record<Activity, keyof AssessmentRow>;
|
||||
|
||||
function assessmentToCalc(assessment: AssessmentRow): CalcData {
|
||||
return {
|
||||
key: crypto.randomUUID(),
|
||||
id: assessment.id,
|
||||
player: {
|
||||
const player: Player = {
|
||||
name: assessment.name,
|
||||
metrics: {
|
||||
age: assessment.age,
|
||||
weight: assessment.weight,
|
||||
gender: assessment.gender as Gender,
|
||||
},
|
||||
},
|
||||
activityPerformances: Object.values(Activity).map((a) => ({
|
||||
};
|
||||
const activityPerformances = Object.values(Activity).map((a) => ({
|
||||
activity: a,
|
||||
performance: (assessment[PERF_COLUMN[a]] as number | null) ?? 0,
|
||||
})),
|
||||
}));
|
||||
|
||||
return {
|
||||
key: crypto.randomUUID(),
|
||||
id: assessment.id,
|
||||
player,
|
||||
activityPerformances,
|
||||
savedSnapshot: snapshotOf(player, activityPerformances),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -104,6 +127,10 @@
|
||||
});
|
||||
if (res.error) throw res.error;
|
||||
calculation.id = res.data?.id;
|
||||
calculation.savedSnapshot = snapshotOf(
|
||||
calculation.player,
|
||||
calculation.activityPerformances,
|
||||
);
|
||||
} catch (err) {
|
||||
calculation.saveError = errorMessage(err);
|
||||
} finally {
|
||||
@@ -132,6 +159,24 @@
|
||||
|
||||
onMount(loadAssessments);
|
||||
|
||||
beforeNavigate((navigation) => {
|
||||
if (!calculations.some(isDirty)) return;
|
||||
|
||||
if (navigation.type === "leave") {
|
||||
// triggers the browser's native "leave site?" confirmation
|
||||
navigation.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!confirm(
|
||||
"You have unsaved player changes that will be lost. Leave anyway?",
|
||||
)
|
||||
) {
|
||||
navigation.cancel();
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
for (const calculation of calculations) {
|
||||
calculation.levels = levelCalculator.calculate(
|
||||
@@ -144,6 +189,7 @@
|
||||
const createCalculation = function (name: string): CalcData {
|
||||
return {
|
||||
key: crypto.randomUUID(),
|
||||
savedSnapshot: "", // never saved — always dirty until the first save
|
||||
player: {
|
||||
name: name,
|
||||
metrics: {
|
||||
@@ -260,12 +306,19 @@
|
||||
class="card card-compact bg-base-200 p-4 shadow-lg max-w-sm w-full"
|
||||
>
|
||||
<div class="card-body gap-4 p-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
class="input input-bordered input-sm w-full"
|
||||
type="text"
|
||||
bind:value={calculation.player.name}
|
||||
placeholder="Player name"
|
||||
/>
|
||||
{#if isDirty(calculation)}
|
||||
<div class="badge badge-warning badge-sm shrink-0">
|
||||
Unsaved
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<ul
|
||||
class="bg-base-100 rounded-box shadow-xs divide-y divide-base-300"
|
||||
@@ -387,7 +440,7 @@
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
onclick={() => saveCalculation(calculation)}
|
||||
disabled={calculation.saving}
|
||||
disabled={calculation.saving || !isDirty(calculation)}
|
||||
class="btn btn-primary btn-sm flex-1"
|
||||
>
|
||||
{calculation.saving ? "Saving..." : "Save"}
|
||||
|
||||
Reference in New Issue
Block a user