feat(fitness/body-parts): "Same as last" button + larger Copy L→R pill
- New "Same as last" pill below each step's stepper. Clicking fills the input(s) with the prior recorded value(s) — for paired steps in split mode, both L and R — and advances to the next step. Only rendered when a previous measurement exists; the placeholder already surfaces the exact number so the button text stays terse. - Copy L→R button resized to match the same-as-last pill (0.88 rem text, 0.55 × 1.1 rem padding) and given top margin. Unicode → swapped for a proper ArrowRight icon between L and R. - i18n: added `same_as_last` and split `copy_l_to_r` into `copy_l_to_r_before` / `copy_l_to_r_after` so each language keeps its natural wrapping around the arrow (EN "Copy L / R", DE "L / R übernehmen").
This commit is contained in:
@@ -4,9 +4,10 @@
|
|||||||
[x] on /fitness/measure, fill "Past measurements" in SSR only for the last 10 measurements. anything further should be fetched client side on mount to decreae initial page load time. use a "show more" button and paginate measurments.
|
[x] on /fitness/measure, fill "Past measurements" in SSR only for the last 10 measurements. anything further should be fetched client side on mount to decreae initial page load time. use a "show more" button and paginate measurments.
|
||||||
[x] on /fitness/measure (resp. their associated logging API routes), consolidate measurements by day. If we want to log another measurement, overwriting an old one, show a warning to indicate this. disparate measurements (e.g., weight and bodyfat) should not show this warning but simply be merged into one log entry for that day.
|
[x] on /fitness/measure (resp. their associated logging API routes), consolidate measurements by day. If we want to log another measurement, overwriting an old one, show a warning to indicate this. disparate measurements (e.g., weight and bodyfat) should not show this warning but simply be merged into one log entry for that day.
|
||||||
[x] on /fitness/measure in the past measurments tab, show more than "Body measurements only" if we don't have Bodyweight logged. we can be a bit more elaborate in our syntax here tbh.
|
[x] on /fitness/measure in the past measurments tab, show more than "Body measurements only" if we don't have Bodyweight logged. we can be a bit more elaborate in our syntax here tbh.
|
||||||
[ ] add a button on /fitness/measure/body-parts for each measurement directly below to say "Same value", instead of having to hit +, then - to lock in same number
|
[x] add a button on /fitness/measure/body-parts for each measurement directly below to say "Same value", instead of having to hit +, then - to lock in same number
|
||||||
[ ] BF graph (with trend line like weight graph) on /fitness/stats page. Emphasize relative changes, not absolute numbers in design (as we cannot trust those) (e.g., use start day of overview as 0% and then show +/- x % on the graph)
|
[ ] BF graph (with trend line like weight graph) on /fitness/stats page. Emphasize relative changes, not absolute numbers in design (as we cannot trust those) (e.g., use start day of overview as 0% and then show +/- x % on the graph)
|
||||||
[ ] Workshop better names than "Measure" for the /fitness/measure route. It's about body data points (i.e., non-food related). What's a better, short name than "Measure" to capture the logging of weight, body composition, body part measurements, and period tracking?
|
[ ] Workshop better names than "Measure" for the /fitness/measure route. It's about body data points (i.e., non-food related). What's a better, short name than "Measure" to capture the logging of weight, body composition, body part measurements, and period tracking?
|
||||||
|
[ ] on /fitness/stats/histoy/<part> for body measurement graphs, make the range reasonable. e.g., if we have 1 cm change, do not fill the entire y-height with 1 cm. Use reasonable padding for low ranges (i think we do something like htis already on the weight graph?)
|
||||||
|
|
||||||
## Refactor Recipe Search Component
|
## Refactor Recipe Search Component
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "homepage",
|
"name": "homepage",
|
||||||
"version": "1.46.5",
|
"version": "1.46.6",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -292,6 +292,8 @@ const translations: Translations = {
|
|||||||
exit: { en: 'Exit', de: 'Schlie\u00dfen' },
|
exit: { en: 'Exit', de: 'Schlie\u00dfen' },
|
||||||
same_both_sides: { en: 'Same on both sides', de: 'Auf beiden Seiten gleich' },
|
same_both_sides: { en: 'Same on both sides', de: 'Auf beiden Seiten gleich' },
|
||||||
copy_l_to_r: { en: 'Copy L \u2192 R', de: 'L \u2192 R \u00fcbernehmen' },
|
copy_l_to_r: { en: 'Copy L \u2192 R', de: 'L \u2192 R \u00fcbernehmen' },
|
||||||
|
copy_l_to_r_before: { en: 'Copy L', de: 'L' },
|
||||||
|
copy_l_to_r_after: { en: 'R', de: 'R \u00fcbernehmen' },
|
||||||
kbd_nav: { en: 'nav', de: 'Navigation' },
|
kbd_nav: { en: 'nav', de: 'Navigation' },
|
||||||
kbd_next: { en: 'next', de: 'weiter' },
|
kbd_next: { en: 'next', de: 'weiter' },
|
||||||
kbd_skip: { en: 'skip', de: 'auslassen' },
|
kbd_skip: { en: 'skip', de: 'auslassen' },
|
||||||
@@ -319,6 +321,7 @@ const translations: Translations = {
|
|||||||
de: 'Für dieses Datum sind bereits Werte erfasst: {fields}. Überschreiben?'
|
de: 'Für dieses Datum sind bereits Werte erfasst: {fields}. Überschreiben?'
|
||||||
},
|
},
|
||||||
overwrite_confirm: { en: 'Overwrite', de: 'Überschreiben' },
|
overwrite_confirm: { en: 'Overwrite', de: 'Überschreiben' },
|
||||||
|
same_as_last: { en: 'Same as last', de: 'Wie zuletzt' },
|
||||||
|
|
||||||
// SetTable
|
// SetTable
|
||||||
set_header: { en: 'SET', de: 'SATZ' },
|
set_header: { en: 'SET', de: 'SATZ' },
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { Minus, Plus, X, ArrowLeft, ArrowRight, Check, Ruler, CopyPlus, TrendingUp } from '@lucide/svelte';
|
import { Minus, Plus, X, ArrowLeft, ArrowRight, Check, Ruler, CopyPlus, TrendingUp, History } from '@lucide/svelte';
|
||||||
import { fly, fade } from 'svelte/transition';
|
import { fly, fade } from 'svelte/transition';
|
||||||
import { cubicOut } from 'svelte/easing';
|
import { cubicOut } from 'svelte/easing';
|
||||||
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
|
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
|
||||||
@@ -119,6 +119,20 @@
|
|||||||
}
|
}
|
||||||
/** @param {string} key */
|
/** @param {string} key */
|
||||||
function copyLtoR(key) { values[key].right = values[key].left; }
|
function copyLtoR(key) { values[key].right = values[key].left; }
|
||||||
|
|
||||||
|
/** @param {string} key */
|
||||||
|
function useLastValue(key) {
|
||||||
|
const s = steps.find((x) => x.key === key);
|
||||||
|
if (!s) return;
|
||||||
|
const last = historyFor(s).at(-1);
|
||||||
|
if (!last) return;
|
||||||
|
if (s.paired) {
|
||||||
|
if (last.left != null) values[key].left = String(last.left);
|
||||||
|
if (!values[key].same && last.right != null) values[key].right = String(last.right);
|
||||||
|
} else if (last.value != null) {
|
||||||
|
values[key] = String(last.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
/** @param {number} i */
|
/** @param {number} i */
|
||||||
function jumpTo(i) {
|
function jumpTo(i) {
|
||||||
direction = i > idx ? 1 : -1;
|
direction = i > idx ? 1 : -1;
|
||||||
@@ -463,10 +477,19 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="copy-btn" onclick={() => copyLtoR(step.key)} disabled={!pv.left}>
|
<button type="button" class="copy-btn" onclick={() => copyLtoR(step.key)} disabled={!pv.left}>
|
||||||
<CopyPlus size={13} /> {t('copy_l_to_r', lang)}
|
<CopyPlus size={15} />
|
||||||
|
<span>{t('copy_l_to_r_before', lang)}</span>
|
||||||
|
<ArrowRight size={14} />
|
||||||
|
<span>{t('copy_l_to_r_after', lang)}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if lastForStep?.left != null || lastForStep?.right != null}
|
||||||
|
<button type="button" class="same-value-btn" onclick={() => { useLastValue(step.key); next(); }}>
|
||||||
|
<History size={15} />
|
||||||
|
<span>{t('same_as_last', lang)}</span>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
<div class="same-toggle">
|
<div class="same-toggle">
|
||||||
<Toggle bind:checked={pv.same} label={t('same_both_sides', lang)} />
|
<Toggle bind:checked={pv.same} label={t('same_both_sides', lang)} />
|
||||||
</div>
|
</div>
|
||||||
@@ -483,6 +506,12 @@
|
|||||||
<Plus size={20} />
|
<Plus size={20} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
{#if lastForStep?.value != null}
|
||||||
|
<button type="button" class="same-value-btn" onclick={() => { useLastValue(step.key); next(); }}>
|
||||||
|
<History size={15} />
|
||||||
|
<span>{t('same_as_last', lang)}</span>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
{/key}
|
{/key}
|
||||||
@@ -876,13 +905,15 @@
|
|||||||
align-self: center;
|
align-self: center;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.35rem;
|
gap: 0.5rem;
|
||||||
padding: 0.25rem 0.7rem;
|
margin-top: 0.5rem;
|
||||||
|
padding: 0.55rem 1.1rem;
|
||||||
border: 1px dashed var(--color-border);
|
border: 1px dashed var(--color-border);
|
||||||
border-radius: var(--radius-pill);
|
border-radius: var(--radius-pill);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
font-size: 0.7rem;
|
font-size: 0.88rem;
|
||||||
color: var(--color-text-tertiary);
|
font-weight: 600;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 150ms;
|
transition: all 150ms;
|
||||||
}
|
}
|
||||||
@@ -893,6 +924,28 @@
|
|||||||
}
|
}
|
||||||
.copy-btn:disabled { opacity: 0.45; cursor: not-allowed; }
|
.copy-btn:disabled { opacity: 0.45; cursor: not-allowed; }
|
||||||
|
|
||||||
|
.same-value-btn {
|
||||||
|
align-self: center;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.55rem 1.1rem;
|
||||||
|
border: 1px dashed color-mix(in oklab, var(--color-primary) 40%, transparent);
|
||||||
|
border-radius: var(--radius-pill);
|
||||||
|
background: color-mix(in oklab, var(--color-primary) 5%, transparent);
|
||||||
|
font-size: 0.88rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color var(--transition-fast, 120ms), background var(--transition-fast, 120ms), color var(--transition-fast, 120ms);
|
||||||
|
}
|
||||||
|
.same-value-btn:hover {
|
||||||
|
border-style: solid;
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
background: color-mix(in oklab, var(--color-primary) 12%, transparent);
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
.same-toggle {
|
.same-toggle {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user