web/changelogs: display skeleton when changelog is loading

This commit is contained in:
dumbmoron 2024-07-21 09:42:48 +00:00
parent f530624467
commit 0cea58922d
No known key found for this signature in database
5 changed files with 228 additions and 15 deletions

View file

@ -1,9 +1,13 @@
<script lang="ts"> <script lang="ts">
import Skeleton from "$components/misc/Skeleton.svelte";
export let version: string; export let version: string;
export let title: string; export let title: string;
export let date: string | undefined; export let date: string | undefined;
export let banner: { file: string; alt: string } | undefined; export let banner: { file: string; alt: string } | undefined;
let bannerLoaded = false;
const formatDate = (dateString: string) => { const formatDate = (dateString: string) => {
const date = new Date(dateString); const date = new Date(dateString);
@ -34,8 +38,15 @@
<img <img
src={`/update-banners/${banner.file}`} src={`/update-banners/${banner.file}`}
alt={banner.alt} alt={banner.alt}
class:loading={!bannerLoaded}
on:load={() => bannerLoaded = true}
class="changelog-banner" class="changelog-banner"
/> />
<Skeleton
class="big changelog-banner"
hidden={bannerLoaded}
/>
{/if} {/if}
<div class="contents"> <div class="contents">
<slot></slot> <slot></slot>
@ -90,7 +101,7 @@
-webkit-user-select: text -webkit-user-select: text
} }
.changelog-banner { :global(.changelog-banner) {
display: block; display: block;
object-fit: cover; object-fit: cover;
max-height: 320pt; max-height: 320pt;
@ -101,6 +112,10 @@
border-radius: var(--padding); border-radius: var(--padding);
} }
:global(.changelog-banner.loading) {
display: none;
}
.changelog-content { .changelog-content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View file

@ -0,0 +1,128 @@
<script lang="ts">
import Skeleton from "$components/misc/Skeleton.svelte";
export let version: string;
</script>
<main>
<div id="changelog-header">
<div class="changelog-info">
<div class="changelog-version">{ version }</div>
<div class="changelog-date">
<Skeleton
width="8em"
height="16px"
/>
</div>
</div>
<h1 class="changelog-title">
<Skeleton
width="28em"
height="27.59px"
/>
</h1>
</div>
<div class="changelog-content">
<Skeleton
class="big changelog-banner"
width="100%"
/>
<div class="contents">
{#each {length: 3 + Math.random() * 5} as _}
<p>
<Skeleton
width="100%"
height={(Math.random() * 84 + 16) + 'px'}
/>
</p>
{/each}
</div>
</div>
</main>
<style>
main {
overflow-x: hidden;
}
#changelog-header {
display: flex;
flex-direction: column;
align-items: start;
gap: calc(var(--padding) / 2);
padding-bottom: 1em; /* match default <p> padding */
}
.changelog-info {
display: flex;
flex-direction: row;
align-items: center;
gap: 14px;
}
.changelog-version {
padding: 3px 8px;
border-radius: 6px;
background-color: var(--secondary);
color: var(--primary);
font-size: 18px;
font-weight: 500;
}
.changelog-date {
font-size: 13px;
font-weight: 500;
color: var(--gray);
}
.changelog-title {
padding: 0;
line-height: 1.2;
font-size: 23px;
user-select: text;
-webkit-user-select: text
}
:global(#banner-skeleton) {
display: block;
max-height: 320pt;
min-height: 210pt;
width: 100%;
aspect-ratio: 16/9;
border-radius: var(--padding);
}
.changelog-content {
display: flex;
flex-direction: column;
align-items: center;
}
.contents {
width: 100%;
}
.contents,
.contents :global(*) {
line-height: 1.7;
font-size: 14.5px;
font-weight: 410;
font-family: "Noto Sans Mono Variable", "Noto Sans Mono", monospace;
user-select: text;
-webkit-user-select: text;
}
:global(ul) {
padding-inline-start: 30px;
}
:global(li) {
padding-left: 3px;
}
@media screen and (max-width: 535px) {
.contents,
.contents :global(*) {
font-size: 14px;
}
}
</style>

View file

@ -0,0 +1,64 @@
<script lang="ts">
export let width: string | undefined = undefined;
export let height: string | undefined = undefined;
export let hidden: boolean | undefined = undefined;
let _class = '';
export { _class as class };
$: style = [
width && `width: ${width}`,
height && `height: ${height}`,
hidden && `display: none`
].filter(a => a).join(';');
</script>
<div
class="skeleton {_class}"
{style}
{...$$restProps}
></div>
<style>
.skeleton {
border-radius: var(--border-radius);
background-color: var(--button);
background-image: var(--skeleton-gradient);
background-size: 200px 100%;
background-repeat: no-repeat;
display: inline-flex;
justify-content: center;
align-items: center;
animation: skeleton 1.2s ease-in-out infinite;
line-height: 1;
font-size: 1em;
text-align: center;
}
:global([data-theme=light]) .skeleton {
background-color: var(--button-hover);
}
.skeleton.big {
background-size: 700px 100%;
animation: skeleton-big 1.2s ease-in-out infinite;
}
@keyframes skeleton {
0% {
background-position: -200px 0;
}
100% {
background-position: calc(200px + 100%) 0;
}
}
@keyframes skeleton-big {
0% {
background-position: -700px 0;
}
100% {
background-position: calc(700px + 100%) 0;
}
}
</style>

View file

@ -16,4 +16,4 @@ export interface MarkdownMetadata {
export type ChangelogImport = { export type ChangelogImport = {
default: SvelteComponent, default: SvelteComponent,
metadata: ChangelogMetadata metadata: ChangelogMetadata
} | undefined; };

View file

@ -5,13 +5,15 @@
import { getAllChangelogs } from "$lib/changelogs"; import { getAllChangelogs } from "$lib/changelogs";
import type { ChangelogImport } from "$lib/types/changelogs"; import type { ChangelogImport } from "$lib/types/changelogs";
import ChangelogSkeleton from "$components/changelog/ChangelogSkeleton.svelte";
import IconChevronLeft from "@tabler/icons-svelte/IconChevronLeft.svelte"; import IconChevronLeft from "@tabler/icons-svelte/IconChevronLeft.svelte";
import IconChevronRight from "@tabler/icons-svelte/IconChevronRight.svelte"; import IconChevronRight from "@tabler/icons-svelte/IconChevronRight.svelte";
const changelogs = getAllChangelogs(); const changelogs = getAllChangelogs();
const versions = Object.keys(changelogs); const versions = Object.keys(changelogs);
let changelog: (ChangelogImport & { version: string }) | undefined; let changelog: { version: string; page: Promise<ChangelogImport> } | undefined;
let currentIndex = 0; let currentIndex = 0;
{ {
@ -24,17 +26,13 @@
const loadChangelog = async () => { const loadChangelog = async () => {
const version = versions[currentIndex]; const version = versions[currentIndex];
const log = (await changelogs[version]()) as ChangelogImport; changelog = {
if (!log) { version,
return; // FIXME: now wot page: changelogs[version]() as Promise<ChangelogImport>
} }
changelog = {
...log,
version,
};
window.location.hash = version; window.location.hash = version;
await changelog.page;
}; };
const loadNext = () => { const loadNext = () => {
@ -77,10 +75,17 @@
</button> </button>
</div> </div>
<div class="changelog-wrapper"> <div class="changelog-wrapper">
<svelte:component {#await changelog.page}
this={changelog.default} {#key changelog.version}
version={changelog.version} <ChangelogSkeleton version={changelog.version} />
/> {/key}
{:then page}
<svelte:component
this={page.default}
{...page.metadata}
version={changelog.version}
/>
{/await}
<div class="button-wrapper-mobile"> <div class="button-wrapper-mobile">
<button on:click={loadPrev} disabled={!prev}> <button on:click={loadPrev} disabled={!prev}>
<IconChevronLeft /> <IconChevronLeft />
@ -133,6 +138,7 @@
} }
.changelog-wrapper { .changelog-wrapper {
flex: 1;
max-width: 850px; max-width: 850px;
overflow-x: hidden; overflow-x: hidden;
padding: var(--padding); padding: var(--padding);