alignment and now
This commit is contained in:
@@ -130,6 +130,7 @@
|
|||||||
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
|
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
|
||||||
|
|
||||||
import { formatZoned, getZonedHour } from '$lib/utils/date';
|
import { formatZoned, getZonedHour } from '$lib/utils/date';
|
||||||
|
import { useNow } from '$lib/utils/now.svelte';
|
||||||
|
|
||||||
import { CHART_COLORS } from './data';
|
import { CHART_COLORS } from './data';
|
||||||
|
|
||||||
@@ -224,6 +225,10 @@
|
|||||||
class: className = ''
|
class: className = ''
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
|
// Minute-resolution clock shared with the rest of the app; read in draw() so
|
||||||
|
// the current-time marker stays put as time passes.
|
||||||
|
const clock = useNow();
|
||||||
|
|
||||||
// ─── Constants ──────────────────────────────────────────────────────────────
|
// ─── Constants ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const PAD_BOTTOM = 34;
|
const PAD_BOTTOM = 34;
|
||||||
@@ -1386,9 +1391,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Current time marker
|
// Current time marker. Reading the shared clock (rather than Date.now())
|
||||||
|
// makes the enclosing draw effect re-run every minute, so the line moves.
|
||||||
if (showNow) {
|
if (showNow) {
|
||||||
const now = Date.now() / 1000;
|
const now = clock.current.getTime() / 1000;
|
||||||
if (now >= viewStart && now <= viewEnd) {
|
if (now >= viewStart && now <= viewEnd) {
|
||||||
const x = xPix(now);
|
const x = xPix(now);
|
||||||
ctx.strokeStyle = CHART_COLORS.currentTimeLine;
|
ctx.strokeStyle = CHART_COLORS.currentTimeLine;
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { browser } from '$app/environment';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared "now" clock.
|
||||||
|
*
|
||||||
|
* One timer, aligned to the wall-clock minute, drives every current-time marker
|
||||||
|
* in the app (the hourly table's NOW line, the meteogram's time marker) so they
|
||||||
|
* stay accurate without a page reload.
|
||||||
|
*
|
||||||
|
* The timer only runs while at least one component is subscribed, and re-aligns
|
||||||
|
* after each tick so it can't drift or fire twice within a minute (e.g. after
|
||||||
|
* the tab has been suspended).
|
||||||
|
*/
|
||||||
|
|
||||||
|
let current = $state(new Date());
|
||||||
|
let subscribers = 0;
|
||||||
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||||
|
|
||||||
|
const MINUTE_MS = 60_000;
|
||||||
|
|
||||||
|
function scheduleTick() {
|
||||||
|
timer = setTimeout(
|
||||||
|
() => {
|
||||||
|
current = new Date();
|
||||||
|
scheduleTick();
|
||||||
|
},
|
||||||
|
MINUTE_MS - (Date.now() % MINUTE_MS)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reactive current time, refreshed on every minute boundary while the calling
|
||||||
|
* component is mounted. On the server it just reports render time.
|
||||||
|
*/
|
||||||
|
export function useNow(): { readonly current: Date } {
|
||||||
|
if (browser) {
|
||||||
|
$effect(() => {
|
||||||
|
if (subscribers++ === 0) {
|
||||||
|
current = new Date();
|
||||||
|
scheduleTick();
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
if (--subscribers === 0) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
get current() {
|
||||||
|
return browser ? current : new Date();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -489,8 +489,8 @@
|
|||||||
/* full state only: gap above the icons, and the day / night icon offsets
|
/* full state only: gap above the icons, and the day / night icon offsets
|
||||||
that tighten the pair around the cell centre */
|
that tighten the pair around the cell centre */
|
||||||
--icon-gap-full: 2px;
|
--icon-gap-full: 2px;
|
||||||
--day-icon-nudge: 2px;
|
--day-icon-nudge: 3px;
|
||||||
--night-icon-nudge: 4px;
|
--night-icon-nudge: 0px;
|
||||||
/* compact only: signed shift that lands the lone icon dead centre in the
|
/* compact only: signed shift that lands the lone icon dead centre in the
|
||||||
square tile (measured against the tile's mid-line) */
|
square tile (measured against the tile's mid-line) */
|
||||||
--icon-lift: 1px;
|
--icon-lift: 1px;
|
||||||
@@ -534,7 +534,7 @@
|
|||||||
--tmax-nudge: 0px;
|
--tmax-nudge: 0px;
|
||||||
--tmin-nudge: 8px;
|
--tmin-nudge: 8px;
|
||||||
--icon-gap-full: 6px;
|
--icon-gap-full: 6px;
|
||||||
--day-icon-nudge: 3px;
|
--day-icon-nudge: 4px;
|
||||||
--night-icon-nudge: 6px;
|
--night-icon-nudge: 6px;
|
||||||
--pb-full: 10px;
|
--pb-full: 10px;
|
||||||
--pb-min: 3px;
|
--pb-min: 3px;
|
||||||
@@ -769,11 +769,11 @@
|
|||||||
and melts away completely when compact so the day icon re-centers. */
|
and melts away completely when compact so the day icon re-centers. */
|
||||||
.night-icon {
|
.night-icon {
|
||||||
display: block;
|
display: block;
|
||||||
width: calc(var(--icon) * 0.42 * var(--rel));
|
width: calc(var(--icon) * 0.55 * var(--rel));
|
||||||
height: calc(var(--icon) * 0.42 * var(--rel));
|
height: calc(var(--icon) * 0.55 * var(--rel));
|
||||||
opacity: var(--rel);
|
opacity: var(--rel);
|
||||||
margin-left: calc(-4px * var(--rel));
|
margin-left: calc(-4px * var(--rel));
|
||||||
margin-bottom: calc(6px * var(--rel));
|
margin-bottom: calc(-4px * var(--rel));
|
||||||
transform: translateX(calc(-1 * var(--night-icon-nudge) * var(--k)));
|
transform: translateX(calc(-1 * var(--night-icon-nudge) * var(--k)));
|
||||||
}
|
}
|
||||||
/* weekday centered when full, pushed to the edges when compact */
|
/* weekday centered when full, pushed to the edges when compact */
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
} from '$lib/stores/settings';
|
} from '$lib/stores/settings';
|
||||||
|
|
||||||
import { formatUtcOffset, formatZoned, getZonedHour, isSameDayInZone } from '$lib/utils/date';
|
import { formatUtcOffset, formatZoned, getZonedHour, isSameDayInZone } from '$lib/utils/date';
|
||||||
|
import { useNow } from '$lib/utils/now.svelte';
|
||||||
|
|
||||||
import { groupHover, setGroupHover } from '$lib/charts';
|
import { groupHover, setGroupHover } from '$lib/charts';
|
||||||
import * as m from '$lib/paraglide/messages';
|
import * as m from '$lib/paraglide/messages';
|
||||||
@@ -74,7 +75,10 @@
|
|||||||
$storedVariablePrefs.table?.[key] ?? defaultVariablePrefs.table[key] ?? true
|
$storedVariablePrefs.table?.[key] ?? defaultVariablePrefs.table[key] ?? true
|
||||||
);
|
);
|
||||||
|
|
||||||
const today = new Date();
|
// Ticks every minute, so the NOW line/label and the highlighted current-hour
|
||||||
|
// column keep up with the clock instead of freezing at page load.
|
||||||
|
const clock = useNow();
|
||||||
|
let today = $derived(clock.current);
|
||||||
const tempUnit = $derived(getTempUnit(units));
|
const tempUnit = $derived(getTempUnit(units));
|
||||||
const windUnit = $derived(getWindUnit(units));
|
const windUnit = $derived(getWindUnit(units));
|
||||||
const precipUnit = $derived(getPrecipUnit(units));
|
const precipUnit = $derived(getPrecipUnit(units));
|
||||||
|
|||||||
Reference in New Issue
Block a user