diff options
| author | rxliuli <rxliuli@gmail.com> | 2025-11-04 05:03:50 +0800 |
|---|---|---|
| committer | rxliuli <rxliuli@gmail.com> | 2025-11-04 05:03:50 +0800 |
| commit | bce557cc2dc767628bed6aac87301a1be7c5431b (patch) | |
| tree | b51a051228d01fe3306cd7626d4a96768aadb944 /src/App.svelte | |
init commit
Diffstat (limited to 'src/App.svelte')
| -rw-r--r-- | src/App.svelte | 161 |
1 files changed, 161 insertions, 0 deletions
diff --git a/src/App.svelte b/src/App.svelte new file mode 100644 index 0000000..846f1df --- /dev/null +++ b/src/App.svelte @@ -0,0 +1,161 @@ +<script lang="ts"> + import { onMount } from 'svelte'; + + import { BUILD } from '~/config/build'; + import { getJet } from '~/jet'; + import { makeErrorPageIntent } from '~/jet/intents/error-page-intent-controller'; + import { getLocale } from '~/utils/locale'; + + // Types + import type { Page } from './jet/models/page'; + + // Components + import Fonts from '~/components/structure/Fonts.svelte'; + import Footer from '~/components/structure/Footer.svelte'; + import Navigation from '~/components/navigation/Navigation.svelte'; + import NavigationSkeleton from '~/components/navigation/Skeleton.svelte'; + import PageResolver from '~/components/PageResolver.svelte'; + + const locale = getLocale(); + const jet = getJet(); + + $: language = locale.language; + + export let page: Promise<Page> | Page = new Promise(() => {}); + export let isFirstPage: boolean = true; + + $: pageWithRejectionErrorPage = transformRejectionIntoErrorPage(page); + + // Critically, this function is not async. We want to preserve the behavior + // where if page is not a promise than neither is + // pageWithRejectionErrorPage. + function transformRejectionIntoErrorPage( + page: Promise<Page> | Page, + ): Promise<Page> | Page { + if (!(page instanceof Promise)) { + return page; + } + + // The async IIFE allows this function to return synchronously. + return (async (): Promise<Page> => { + try { + return await page; + } catch (error) { + return jet.dispatch( + makeErrorPageIntent({ + // This allows the error page to pick the right platform + // and display the correct mesage (ex. "Page not found" for + // a 404) + error: error instanceof Error ? error : null, + }), + ); + } + })(); + } + + // NOTE: The use of page instead of pageWithRejectionErrorPage here is very + // intentional. Since pageWithRejectionErrorPage is reactive, it will + // be undefined in this initializer. This is intentionally not + // not derived (eg. defined as $: webNavigation = ...), since we only + // want to update it _after_ the page promise resolves (so the nav + // doesn't disappear on navigation). But then for SSR, there are no + // promises, so we need a sync value here so the nav renders, which + // is why we have the initializer. + let webNavigation = page instanceof Promise ? null : page.webNavigation; + $: { + if (pageWithRejectionErrorPage instanceof Promise) { + // Clientside once the new page resolves, update the navigation + // (in case it changed) + pageWithRejectionErrorPage.then((page: Page) => { + webNavigation = page.webNavigation; + }); + } else { + // Sometimes clientside a promise is not passed to updateApp, so + // we need to handle a WebRenderablePage (possible with a + // different webNavigation). + webNavigation = pageWithRejectionErrorPage.webNavigation; + } + } + + onMount(() => { + //@ts-ignore + window.__ASOTW = { + version: BUILD, + }; + }); +</script> + +<svelte:head> + <meta name="version" content={BUILD} /> +</svelte:head> + +<Fonts {language} /> + +{#if import.meta.env.DEV} + {#await import('~/components/ArtworkBreakpointLogger.svelte') then { default: ArtworkBreakpointLogger }} + <ArtworkBreakpointLogger /> + {/await} +{/if} + +<div class="app-container" data-testid="app-container"> + <div class="navigation-container"> + {#if webNavigation} + <Navigation {webNavigation} /> + {:else} + <NavigationSkeleton /> + {/if} + </div> + + <div + style="display: flex; + position: relative; + flex-direction: column; + min-height: 100vh; + " + > + <main class="page-container"> + <PageResolver page={pageWithRejectionErrorPage} {isFirstPage} /> + </main> + + <Footer /> + </div> +</div> + +<style lang="scss"> + @use '@amp/web-shared-styles/sasskit-stylekit/ac-sasskit-config'; + @use '@amp/web-shared-styles/app/core/viewports' as *; + @use '@amp/web-shared-styles/app/core/globalvars' as *; + + .app-container { + min-height: 100vh; + min-height: 100dvh; + display: grid; + grid-template-areas: + 'structure-header' + 'structure-main-section'; + grid-template-columns: minmax(0, 1fr); + grid-gap: 0; + grid-template-rows: 44px auto; + + @media (--sidebar-visible) { + grid-template-rows: auto; + grid-template-columns: 260px minmax(0, 1fr); + } + + @media (--sidebar-large-visible) { + grid-template-columns: $global-sidebar-width-large minmax(0, 1fr); + } + } + + .navigation-container { + @media (--range-small-up) { + height: 100vh; + position: sticky; + top: 0; + } + } + + .page-container { + flex-grow: 1; + } +</style> |
