refactor(fitness): use:action → {@attach}, harden streamed-data error paths
Two custom Leaflet actions converted to attachments: renderMap is now
a factory returning an attachment, mountMap is the attachment itself.
Four call sites updated. use:enhance left alone — still the canonical
SvelteKit form-action API.
The stats page's three streamed Promise.resolve(...).then(...) chains
now log on rejection instead of silently swallowing errors. The muscle
heatmap {#await} block gained pending and catch branches with a
lang-aware error message.
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "homepage",
|
||||
"version": "1.52.2",
|
||||
"version": "1.52.3",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -291,20 +291,20 @@
|
||||
}
|
||||
|
||||
/**
|
||||
* Svelte use:action — renders a Leaflet map for a GPS track
|
||||
* @param {HTMLElement} node
|
||||
* @param {any} params
|
||||
* Attachment factory — renders a Leaflet map for a GPS track.
|
||||
* @param {any[]} track
|
||||
* @param {number} idx
|
||||
* @returns {import('svelte/attachments').Attachment<HTMLElement>}
|
||||
*/
|
||||
function renderMap(node, params) {
|
||||
const { track, idx } = params;
|
||||
initMapForTrack(node, track, idx);
|
||||
return {
|
||||
destroy() {
|
||||
function renderMap(track, idx) {
|
||||
return (node) => {
|
||||
initMapForTrack(node, track, idx);
|
||||
return () => {
|
||||
if (maps[idx]) {
|
||||
maps[idx].remove();
|
||||
delete maps[idx];
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -707,7 +707,7 @@
|
||||
</button>
|
||||
</div>
|
||||
{#if exData.gpsTrack?.length >= 2}
|
||||
<div class="track-map" use:renderMap={{ track: exData.gpsTrack, idx: exIdx }}></div>
|
||||
<div class="track-map" {@attach renderMap(exData.gpsTrack, exIdx)}></div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
@@ -787,7 +787,7 @@
|
||||
<span class="gps-stat elev-loss">-{elevStats.loss}{t('elevation_unit', lang)}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="track-map" use:renderMap={{ track: ex.gpsTrack, idx: exIdx }}></div>
|
||||
<div class="track-map" {@attach renderMap(ex.gpsTrack, exIdx)}></div>
|
||||
|
||||
{#if ex.gpsTrack.length >= 2}
|
||||
{@const samples = computePaceSamples(ex.gpsTrack)}
|
||||
|
||||
@@ -131,9 +131,21 @@
|
||||
let periodsData = $state([]);
|
||||
/** @type {any[]} */
|
||||
let sharedPeriodsData = $state([]);
|
||||
$effect(() => { Promise.resolve(data.nutritionStats).then(v => { ns = v ?? {}; }); });
|
||||
$effect(() => { Promise.resolve(data.periods).then(v => { periodsData = v ?? []; }); });
|
||||
$effect(() => { Promise.resolve(data.sharedPeriods).then(v => { sharedPeriodsData = v ?? []; }); });
|
||||
$effect(() => {
|
||||
Promise.resolve(data.nutritionStats)
|
||||
.then(v => { ns = v ?? {}; })
|
||||
.catch(err => console.error('nutritionStats stream failed:', err));
|
||||
});
|
||||
$effect(() => {
|
||||
Promise.resolve(data.periods)
|
||||
.then(v => { periodsData = v ?? []; })
|
||||
.catch(err => console.error('periods stream failed:', err));
|
||||
});
|
||||
$effect(() => {
|
||||
Promise.resolve(data.sharedPeriods)
|
||||
.then(v => { sharedPeriodsData = v ?? []; })
|
||||
.catch(err => console.error('sharedPeriods stream failed:', err));
|
||||
});
|
||||
|
||||
const hasSma = $derived(stats.weightChart?.sma?.some((/** @type {any} */ v) => v !== null));
|
||||
|
||||
@@ -498,8 +510,12 @@
|
||||
|
||||
<div class="section-block muscle-heatmap-block">
|
||||
<h2 class="section-title">{t('muscle_balance', lang)}</h2>
|
||||
{#await data.muscleHeatmap then muscleHeatmap}
|
||||
{#await data.muscleHeatmap}
|
||||
<div class="muscle-heatmap-pending" aria-hidden="true"></div>
|
||||
{:then muscleHeatmap}
|
||||
<MuscleHeatmap data={muscleHeatmap} />
|
||||
{:catch}
|
||||
<div class="muscle-heatmap-failed">{lang === 'de' ? 'Fehler beim Laden' : 'Failed to load'}</div>
|
||||
{/await}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -398,20 +398,19 @@
|
||||
let leafletLib = null;
|
||||
let prevTrackLen = 0;
|
||||
|
||||
/** Svelte use:action — called when the map div enters the DOM */
|
||||
function mountMap(/** @type {HTMLElement} */ node) {
|
||||
/** Attachment — initialises the Leaflet map when the div mounts. */
|
||||
/** @type {import('svelte/attachments').Attachment<HTMLElement>} */
|
||||
function mountMap(node) {
|
||||
initMap(node);
|
||||
return {
|
||||
destroy() {
|
||||
if (liveMap) {
|
||||
liveMap.remove();
|
||||
}
|
||||
liveMap = null;
|
||||
livePolyline = null;
|
||||
liveMarker = null;
|
||||
leafletLib = null;
|
||||
prevTrackLen = 0;
|
||||
return () => {
|
||||
if (liveMap) {
|
||||
liveMap.remove();
|
||||
}
|
||||
liveMap = null;
|
||||
livePolyline = null;
|
||||
liveMarker = null;
|
||||
leafletLib = null;
|
||||
prevTrackLen = 0;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1136,7 +1135,7 @@
|
||||
|
||||
{:else if workout.active && workout.mode === 'gps'}
|
||||
<div class="gps-workout">
|
||||
<div class="gps-workout-map" use:mountMap></div>
|
||||
<div class="gps-workout-map" {@attach mountMap}></div>
|
||||
|
||||
<!-- Overlay: sits on top of the map at the bottom -->
|
||||
<div class="gps-overlay" class:gps-overlay-prestart={!gpsStarted}>
|
||||
@@ -1631,7 +1630,7 @@
|
||||
<span>Voice: every {vgTriggerValue} {vgTriggerType === 'distance' ? 'km' : 'min'}</span>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="live-map" use:mountMap></div>
|
||||
<div class="live-map" {@attach mountMap}></div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user