Add skeleton loading to most common pages
Diff
chartered-frontend/src/app.pcss | 12 ++++++++++++
chartered-frontend/src/components/FeaturedCrate.svelte | 35 +++++++++++++++++++++++++++--------
chartered-frontend/src/routes/(authed)/+page.svelte | 31 ++++++++++++++++++++++---------
chartered-frontend/src/routes/(authed)/ssh-keys/+page.svelte | 13 ++++++++-----
chartered-frontend/src/routes/(authed)/ssh-keys/SingleSshKey.svelte | 49 +++++++++++++++++++++++++++++++++++++++++--------
chartered-frontend/src/routes/(authed)/crates/[organisation]/+page.svelte | 30 +++++++++++++++++++++++++++---
chartered-frontend/src/routes/(authed)/organisations/list/+page.svelte | 30 +++++++++++++++++++++++++++---
chartered-frontend/src/routes/(authed)/crates/[organisation]/[crate]/+page.svelte | 132 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
8 files changed, 244 insertions(+), 88 deletions(-)
@@ -55,8 +55,20 @@
.btn-blue-outline {
@apply w-fit mt-3 text-blue-700 dark:text-inherit hover:text-white border border-blue-700 hover:bg-blue-700 dark:bg-transparent focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 dark:border-blue-600 dark:hover:bg-blue-600 focus:outline-none dark:focus:ring-blue-800 transition duration-150 ease-out;
}
.btn-skeleton-outline {
@apply w-fit mt-3 border border-slate-700 bg-transparent font-medium rounded-lg text-sm px-5 py-2.5 pointer-events-none;
}
}
.text-highlight {
@apply text-blue-700 dark:text-blue-600;
}
.skeleton {
@apply h-2 bg-slate-700 rounded animate-pulse;
}
.skeleton-highlight {
@apply h-2 bg-blue-700 rounded animate-pulse;
}
@@ -5,19 +5,34 @@
/**
* The crate to show in the card
*/
export let crate: Crate;
export let crate: Crate | null;
</script>
<a href={`/crates/${crate.organisation}/${crate.name}`} class="card flex items-center mb-3">
<div class="flex-grow">
<div class="font-semibold">
{crate.organisation}/<span class="text-highlight">{crate.name}</span>
{#if crate}
<a href={`/crates/${crate.organisation}/${crate.name}`} class="card flex items-center mb-3">
<div class="flex-grow">
<div class="font-semibold">
{crate.organisation}/<span class="text-highlight">{crate.name}</span>
</div>
<div class="text-xs flex items-center space-x-1">
<slot />
</div>
</div>
<div class="text-xs flex items-center space-x-1">
<slot />
<Icon name="chevron-right" strokeWidth="3px" />
</a>
{:else}
<div class="card flex items-center mb-3">
<div class="flex-grow">
<div class="flex items-end pb-2 h-6">
<div style={`width: ${Math.random() * 4 + 4}rem`} class="skeleton" />
<div style={`width: ${Math.random() * 4 + 4}rem`} class="ml-2 skeleton-highlight" />
</div>
<div class="text-xs flex items-center space-x-1">
<slot />
</div>
</div>
</div>
<Icon name="chevron-right" strokeWidth="3px" />
</a>
{/if}
@@ -1,9 +1,8 @@
<script type="typescript">
import Icon from '../../components/Icon.svelte';
import rustLogo from '../../img/rust.svg';
import { request } from '../../stores/auth';
import RelativeTime from '../../components/RelativeTime.svelte';
import Spinner from '../../components/Spinner.svelte';
import type { MostDownloaded, RecentlyCreated, RecentlyUpdated } from '../../types/featured_crate';
import FeaturedCrate from '../../components/FeaturedCrate.svelte';
import ErrorAlert from '../../components/ErrorAlert.svelte';
@@ -39,9 +38,14 @@
<h3 class="text-3xl mb-2">Newly created</h3>
{#await recentlyCreatedPromise}
<div class="relative h-12">
<Spinner />
</div>
{#each [1, 2, 3, 4, 5] as _}
<FeaturedCrate crate={null}>
<div class="my-0.5 flex items-center space-x-1">
<Icon name="calendar" />
<div class="skeleton w-16" />
</div>
</FeaturedCrate>
{/each}
{:then recentlyCreated}
{#each recentlyCreated.crates as crate}
<FeaturedCrate {crate}>
@@ -58,9 +62,11 @@
<h3 class="text-3xl mb-2">Recently updated</h3>
{#await recentlyUpdatedPromise}
<div class="relative h-12">
<Spinner />
</div>
{#each [1, 2, 3, 4, 5] as _}
<FeaturedCrate crate={null}>
<div class="skeleton w-9 my-1" />
</FeaturedCrate>
{/each}
{:then recentlyUpdated}
{#each recentlyUpdated.versions as crate}
<FeaturedCrate {crate}>
@@ -76,9 +82,14 @@
<h3 class="text-3xl mb-2">Most downloaded</h3>
{#await mostDownloadedPromise}
<div class="relative h-12">
<Spinner />
</div>
{#each [1, 2, 3, 4, 5] as _}
<FeaturedCrate crate={null}>
<div class="my-0.5 flex items-center space-x-1">
<Icon name="download" />
<div class="skeleton w-8" />
</div>
</FeaturedCrate>
{/each}
{:then mostDownloaded}
{#each mostDownloaded.crates as crate}
<FeaturedCrate {crate}>
@@ -1,6 +1,5 @@
<script type="typescript">
import { request } from '../../../stores/auth';
import Spinner from '../../../components/Spinner.svelte';
import ErrorAlert from '../../../components/ErrorAlert.svelte';
import SingleSshKey from './SingleSshKey.svelte';
import type { SshKeys, SshKey } from '../../../types/ssh_keys';
@@ -45,8 +44,12 @@
<main class="container mx-auto p-10 pt-0">
{#await sshKeysPromise}
<div class="relative h-4">
<Spinner />
<div class="card mb-4 p-2">
<div class="overflow-scroll">
<div class="w-fit min-w-full">
<SingleSshKey sshKey={null} />
</div>
</div>
</div>
{:then sshKeys}
<div class:hidden={sshKeys.keys.length === 0} class="card mb-4 p-2">
@@ -60,11 +63,11 @@
</div>
</div>
</div>
<CreateKeyForm on:complete={reloadSshKeys} />
{:catch e}
<ErrorAlert showClose={false}>{e}</ErrorAlert>
{/await}
<CreateKeyForm on:complete={reloadSshKeys} />
</main>
{#if deleting}
@@ -7,7 +7,7 @@
/**
* The SSH key to draw up some information about
*/
export let sshKey: SshKey;
export let sshKey: SshKey | null;
// build the event dispatcher for alerting the main view that the user wants to
// delete the current key
@@ -16,21 +16,44 @@
<div class="p-2 flex items-center">
<div class="flex-grow">
<h3 class="text-lg">{sshKey.name}</h3>
<h3 class="text-lg">
{#if sshKey}
{sshKey.name}
{:else}
<div class="skeleton w-24 mt-3" />
{/if}
</h3>
<code class="text-xs">{sshKey.fingerprint}</code>
{#if sshKey}
<code class="text-xs">
{sshKey.fingerprint}
</code>
{:else}
<div class="flex">
{#each [...Array(32).keys()] as _}
<div class="skeleton w-4 mt-4 mr-1.5" />
{/each}
</div>
{/if}
<div class="text-xs pt-0.5 pb-1">
<span>Added <RelativeTime time={sshKey.created_at} /></span>
<span class="ml-2">
Last used
{#if sshKey.last_used_at}
<RelativeTime time={sshKey.last_used_at} />
{:else}
never
{/if}
</span>
{#if sshKey}
<span>Added <RelativeTime time={sshKey.created_at} /></span>
<span class="ml-2">
Last used
{#if sshKey.last_used_at}
<RelativeTime time={sshKey.last_used_at} />
{:else}
never
{/if}
</span>
{:else}
<div class="flex mt-3 mb-1">
<div class="skeleton w-32" />
<div class="skeleton w-24 ml-2" />
</div>
{/if}
</div>
</div>
@@ -1,7 +1,6 @@
<script type="typescript">
import { page } from '$app/stores';
import { request } from '../../../../stores/auth';
import Spinner from '../../../../components/Spinner.svelte';
import ErrorAlert from '../../../../components/ErrorAlert.svelte';
import Icon from '../../../../components/Icon.svelte';
import type { OrganisationDetail } from '../../../../types/organisations';
@@ -70,8 +69,13 @@
<h2>
{#await organisationPromise}
<div class="relative h-4 w-4">
<Spinner />
<div class="h-6">
<div class="skeleton inline-block mr-2 w-24" />
<div class="skeleton inline-block mr-2 w-8" />
<div class="skeleton inline-block mr-2 w-16" />
<div class="skeleton inline-block mr-2 w-12" />
<div class="skeleton inline-block mr-2 w-9" />
<div class="skeleton inline-block w-10" />
</div>
{:then organisation}
{#if organisation.description}
@@ -110,8 +114,24 @@
<div class="mt-4">
{#await organisationPromise}
<div class="relative h-14">
<Spinner />
<div class="grid gap-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
{#each [1, 2, 3] as _}
<div class="card">
<div class="card-header flex items-center my-4">
<div class="skeleton mr-2 w-24" />
<div class="skeleton-highlight w-16" />
</div>
<div class="card-body">
<div class="skeleton inline-block mr-2 w-24" />
<div class="skeleton inline-block mr-2 w-8" />
<div class="skeleton inline-block mr-2 w-16" />
<div class="skeleton inline-block mr-2 w-12" />
<div class="skeleton inline-block mr-2 w-9" />
<div class="skeleton inline-block w-10" />
</div>
</div>
{/each}
</div>
{:then organisation}
{#if currentTab === Tab.CRATES}
@@ -1,5 +1,4 @@
<script type="typescript">
import Spinner from '../../../../components/Spinner.svelte';
import { request } from '../../../../stores/auth';
import type { OrganisationList } from '../../../../types/organisations';
import ErrorAlert from '../../../../components/ErrorAlert.svelte';
@@ -26,8 +25,29 @@
<main class="container mx-auto p-10 pt-0">
{#await organisationsPromise}
<div class="relative h-4">
<Spinner />
<div class="mb-4 grid md:grid-cols-2 lg:grid-cols-4 gap-2">
{#each [1, 2, 3] as _}
<div class="card flex space-x-2 items-center">
<div class="flex-grow h-full">
<div class="card-header">
<div class="skeleton-highlight w-32" />
</div>
<div class="card-body">
<div class="skeleton inline-block mr-2 w-24" />
<div class="skeleton inline-block mr-2 w-8" />
<div class="skeleton inline-block mr-2 w-16" />
<div class="skeleton inline-block mr-2 w-12" />
<div class="skeleton inline-block mr-2 w-9" />
<div class="skeleton inline-block w-10" />
</div>
</div>
<div class="min-w-[48px]">
<img alt="Placeholder" class="rounded-[50%]" src="http://placekitten.com/48/48" />
</div>
</div>
{/each}
</div>
{:then organisations}
<div
@@ -60,9 +80,9 @@
one.
</div>
{/if}
<a href="/organisations/create" class="inline-flex items-center btn-blue-outline"> + Create </a>
{:catch e}
<ErrorAlert showClose={false}>{e}</ErrorAlert>
{/await}
<a href="/organisations/create" class="inline-flex items-center btn-blue-outline"> + Create </a>
</main>
@@ -57,7 +57,36 @@
>
</h1>
{#await cratePromise then crate}
{#await cratePromise}
<div class="h-6">
<div class="skeleton inline-block mr-2 w-12" />
<div class="skeleton inline-block mr-2 w-24" />
<div class="skeleton inline-block mr-2 w-16" />
<div class="skeleton inline-block mr-2 w-32" />
<div class="skeleton inline-block mr-2 w-12" />
<div class="skeleton inline-block w-10" />
</div>
<div class="space-x-2">
<div class="card-header-button btn-skeleton-outline">
<div class="skeleton inline-block w-10" />
</div>
<div class="card-header-button btn-skeleton-outline">
<div class="skeleton inline-block w-10" />
</div>
<div class="card-header-button btn-skeleton-outline">
<div class="skeleton inline-block w-10" />
</div>
</div>
{:then crate}
<h2>
{#if crate.description}
{crate.description}
@@ -94,57 +123,80 @@
</header>
<main class="container mx-auto p-10 pt-0 grid grid-cols-12 gap-6 relative">
{#await cratePromise}
<Spinner />
{:then crate}
<div class="card col-span-full lg:col-span-9 p-0">
<div class="border-b border-gray-200 dark:border-gray-700">
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center text-gray-500 dark:text-gray-400">
{#each allTabs as tab}
<li class="mr-2">
<button
on:click={() => (currentTab = tab.id)}
class:!border-blue-500={currentTab === tab.id}
class:text-blue-500={currentTab === tab.id}
aria-current={currentTab === tab.id ? 'page' : false}
class="inline-flex items-center space-x-2 p-4 rounded-t-lg border-b-2 border-transparent"
>
<Icon name={tab.icon} /> <span>{tab.name}</span>
</button>
</li>
{/each}
</ul>
</div>
<div class="card col-span-full lg:col-span-9 p-0">
<div class="border-b border-gray-200 dark:border-gray-700">
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center text-gray-500 dark:text-gray-400">
{#each allTabs as tab}
<li class="mr-2">
<button
on:click={() => (currentTab = tab.id)}
class:!border-blue-500={currentTab === tab.id}
class:text-blue-500={currentTab === tab.id}
aria-current={currentTab === tab.id ? 'page' : false}
class="inline-flex items-center space-x-2 p-4 rounded-t-lg border-b-2 border-transparent"
>
<Icon name={tab.icon} /> <span>{tab.name}</span>
</button>
</li>
{/each}
</ul>
</div>
{#if currentTab === Tab.README}
<article
class="mt-8 px-6 pb-6 prose dark:prose-invert text-inherit max-w-full prose-headings:text-inherit hover:prose-a:text-blue-600 prose-a:text-blue-500 prose-code:p-1 prose-code:bg-slate-100 dark:prose-code:bg-slate-700 prose-code:rounded-lg prose-pre:bg-slate-100 dark:prose-pre:bg-slate-700 leading-6 before:prose-code:content-none after:prose-code:content-none prose-code:text-pink-400 prose-code:font-normal prose-strong:text-inherit prose-img:inline prose-img:my-0"
>
{#if currentTab === Tab.README}
<article
class="mt-8 px-6 pb-6 prose dark:prose-invert text-inherit max-w-full prose-headings:text-inherit hover:prose-a:text-blue-600 prose-a:text-blue-500 prose-code:p-1 prose-code:bg-slate-100 dark:prose-code:bg-slate-700 prose-code:rounded-lg prose-pre:bg-slate-100 dark:prose-pre:bg-slate-700 leading-6 before:prose-code:content-none after:prose-code:content-none prose-code:text-pink-400 prose-code:font-normal prose-strong:text-inherit prose-img:inline prose-img:my-0"
>
{#await cratePromise}
<div class="skeleton inline-block mr-2 w-24" />
<br /><br />
{#each [...Array(200).keys()] as _}
<div
style={`width: ${Math.max(Math.random() * 24, 4)}rem`}
class={`skeleton inline-block mr-2`}
/>
{/each}
{:then crate}
<SvelteMarkdown source={crate.readme} />
</article>
{:else if currentTab === Tab.MEMBERS}
<MemberTab />
{:else if currentTab === Tab.VERSIONS}
<div class="divide-y divide-gray-200 dark:divide-gray-700">
{/await}
</article>
{:else if currentTab === Tab.MEMBERS}
<MemberTab />
{:else if currentTab === Tab.VERSIONS}
<div class="divide-y divide-gray-200 dark:divide-gray-700">
{#await cratePromise}
<div class="relative h-20"><Spinner /></div>
{:then crate}
{#each crate.versions as version}
<VersionTab {version} class="p-6" />
{/each}
</div>
{/if}
</div>
{/await}
</div>
{/if}
</div>
<div class="col-span-full lg:col-span-3">
<div class="card p-0">
<h1 class="text-xl p-3 border-b border-gray-200 dark:border-gray-700 font-medium">Dependencies</h1>
<div class="col-span-full lg:col-span-3">
<div class="card p-0">
<h1 class="text-xl p-3 border-b border-gray-200 dark:border-gray-700 font-medium">Dependencies</h1>
<div class="divide-y divide-gray-200 dark:divide-gray-700">
<div class="divide-y divide-gray-200 dark:divide-gray-700">
{#await cratePromise}
{#each [...Array(Math.floor(Math.random() * 8 + 5)).keys()] as _}
<div class="flex items-center py-5">
<div
style={`width: ${Math.max(Math.random() * 12, 4)}rem`}
class={`skeleton inline-block ml-3 mr-2`}
/>
<div class={`skeleton inline-block mr-3 w-12`} />
</div>
{/each}
{:then crate}
{#each crate.versions[0].deps as dependency}
<Dependency {dependency} class="p-3" />
{/each}
</div>
{/await}
</div>
</div>
{/await}
</div>
</main>
<style lang="postcss">