map fade
This commit is contained in:
@@ -142,7 +142,8 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Routes that are not finished on arrival: the five that fetch a forecast
|
// Routes that are not finished on arrival: the five that fetch a forecast
|
||||||
// after mounting, and the redirect stubs, which render nothing at all and
|
// after mounting, the maps page (whose map is a cross-origin iframe that has
|
||||||
|
// to load first), and the redirect stubs, which render nothing at all and
|
||||||
// bounce to a located URL from `onMount`. Knowing this up front is what makes
|
// bounce to a located URL from `onMount`. Knowing this up front is what makes
|
||||||
// the wait reliable - the layout clears the ready flag before the swap rather
|
// the wait reliable - the layout clears the ready flag before the swap rather
|
||||||
// than trusting the incoming page to have done it.
|
// than trusting the incoming page to have done it.
|
||||||
@@ -153,6 +154,7 @@
|
|||||||
'/weather/compare',
|
'/weather/compare',
|
||||||
'/weather/seasonal',
|
'/weather/seasonal',
|
||||||
'/weather/historical',
|
'/weather/historical',
|
||||||
|
'/weather/maps',
|
||||||
'/weather/week/[location]',
|
'/weather/week/[location]',
|
||||||
'/weather/14-day/[location]',
|
'/weather/14-day/[location]',
|
||||||
'/weather/compare/[location]',
|
'/weather/compare/[location]',
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
import { reportPageReady } from '$lib/stores/page-transition.svelte';
|
||||||
import { storedLocation, storedModel, storedTheme } from '$lib/stores/settings';
|
import { storedLocation, storedModel, storedTheme } from '$lib/stores/settings';
|
||||||
|
|
||||||
import { mapsDomainForModel } from '$lib/utils/maps-domain';
|
import { mapsDomainForModel } from '$lib/utils/maps-domain';
|
||||||
@@ -16,6 +17,15 @@
|
|||||||
let hashOverride = $state<string | null>(null);
|
let hashOverride = $state<string | null>(null);
|
||||||
let iframeEl = $state<HTMLIFrameElement | null>(null);
|
let iframeEl = $state<HTMLIFrameElement | null>(null);
|
||||||
let mapReady = $state(false);
|
let mapReady = $state(false);
|
||||||
|
let frameLoaded = $state(false);
|
||||||
|
|
||||||
|
// The map is a cross-origin iframe: until it has loaded there is nothing here
|
||||||
|
// but an empty panel, and a page transition that ends before then cross-fades
|
||||||
|
// the old page into that blank. Reporting readiness holds the transition (and
|
||||||
|
// then the loading overlay) until there is actually a map to fade into.
|
||||||
|
// `om-maps:ready` is the good signal; the iframe's own load event is the
|
||||||
|
// backstop, so a map that fails to boot still releases the page.
|
||||||
|
reportPageReady(() => mapReady || frameLoaded);
|
||||||
|
|
||||||
const postToMap = (message: Record<string, unknown>) => {
|
const postToMap = (message: Record<string, unknown>) => {
|
||||||
iframeEl?.contentWindow?.postMessage(message, MAPS_ORIGIN);
|
iframeEl?.contentWindow?.postMessage(message, MAPS_ORIGIN);
|
||||||
@@ -95,6 +105,7 @@
|
|||||||
allowfullscreen
|
allowfullscreen
|
||||||
allow="cross-origin-isolated"
|
allow="cross-origin-isolated"
|
||||||
referrerpolicy="no-referrer"
|
referrerpolicy="no-referrer"
|
||||||
|
onload={() => (frameLoaded = true)}
|
||||||
class="block h-full w-full border-0"
|
class="block h-full w-full border-0"
|
||||||
sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
|
sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
|
||||||
></iframe>
|
></iframe>
|
||||||
|
|||||||
@@ -197,35 +197,41 @@
|
|||||||
and archive pages: the controls then cannot move the heading when they
|
and archive pages: the controls then cannot move the heading when they
|
||||||
change size. -->
|
change size. -->
|
||||||
<div class="flex w-full flex-wrap items-center gap-3 sm:w-auto lg:absolute lg:top-0 lg:right-0">
|
<div class="flex w-full flex-wrap items-center gap-3 sm:w-auto lg:absolute lg:top-0 lg:right-0">
|
||||||
<!-- Range buttons reslice the already-fetched horizon (no refetch). Kept
|
<!-- Range buttons reslice the already-fetched horizon (no refetch). While a
|
||||||
mounted and merely hidden while the forecast is on its way: mounting
|
forecast is on its way they stay mounted but invisible, because
|
||||||
them on arrival re-flowed the row and nudged the heading. -->
|
mounting them on arrival re-flowed the row and nudged the heading.
|
||||||
<div
|
Two exceptions, both of which would reserve a gap for something that
|
||||||
class="flex w-full gap-1 rounded-lg border border-border bg-card p-1 sm:w-auto"
|
is never coming: below sm the group is a full-width row of its own,
|
||||||
class:invisible={!result}
|
and behind the paywall there is no forecast on its way at all. -->
|
||||||
role="group"
|
{#if $isSupporter}
|
||||||
aria-label={m.seasonal_range_aria()}
|
<div
|
||||||
aria-hidden={!result}
|
class="flex w-full gap-1 rounded-lg border border-border bg-card p-1 sm:w-auto {result
|
||||||
>
|
? ''
|
||||||
{#each RANGES as range, i (range.label)}
|
: 'hidden sm:flex sm:invisible'}"
|
||||||
{@const disabled = !result || (range.days !== Infinity && range.days > horizonDays)}
|
role="group"
|
||||||
<button
|
aria-label={m.seasonal_range_aria()}
|
||||||
type="button"
|
aria-hidden={!result}
|
||||||
class="flex-1 cursor-pointer rounded-md px-3 py-1.5 text-xs font-semibold whitespace-nowrap transition-colors sm:flex-none {rangeIndex ===
|
>
|
||||||
i
|
{#each RANGES as range, i (range.label)}
|
||||||
? 'bg-primary text-primary-foreground'
|
{@const disabled = !result || (range.days !== Infinity && range.days > horizonDays)}
|
||||||
: 'text-muted-foreground hover:bg-muted hover:text-foreground'} {disabled
|
<button
|
||||||
? 'cursor-not-allowed opacity-40'
|
type="button"
|
||||||
: ''}"
|
class="flex-1 cursor-pointer rounded-md px-3 py-1.5 text-xs font-semibold whitespace-nowrap transition-colors sm:flex-none {rangeIndex ===
|
||||||
aria-pressed={rangeIndex === i}
|
i
|
||||||
{disabled}
|
? 'bg-primary text-primary-foreground'
|
||||||
tabindex={result ? undefined : -1}
|
: 'text-muted-foreground hover:bg-muted hover:text-foreground'} {disabled
|
||||||
onclick={() => (rangeIndex = i)}
|
? 'cursor-not-allowed opacity-40'
|
||||||
>
|
: ''}"
|
||||||
{range.label}
|
aria-pressed={rangeIndex === i}
|
||||||
</button>
|
{disabled}
|
||||||
{/each}
|
tabindex={result ? undefined : -1}
|
||||||
</div>
|
onclick={() => (rangeIndex = i)}
|
||||||
|
>
|
||||||
|
{range.label}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<ModelSelector
|
<ModelSelector
|
||||||
selectedModel={seasonalModel}
|
selectedModel={seasonalModel}
|
||||||
groups={seasonalModelGroups}
|
groups={seasonalModelGroups}
|
||||||
|
|||||||
Reference in New Issue
Block a user