feat(web): skeleton on asset loading (#3867)

* feat(web): skeletron on asset loading

* feat: add skeleton to all asset grid views

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
JasBogans 2023-09-01 19:12:09 +02:00 committed by GitHub
parent 9539a361e4
commit 46c716d450
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 49 additions and 45 deletions

View file

@ -324,6 +324,22 @@
>
{#if element}
<slot />
<!-- skeleton -->
{#if !$assetStore.initialized}
<div class="ml-[14px] mt-5">
<div class="flex w-[120%] flex-wrap">
{#each Array(100) as _}
<div class="m-[1px] h-[10em] w-[16em] animate-pulse bg-immich-primary/20 dark:bg-immich-dark-primary/20" />
{/each}
</div>
</div>
{/if}
<!-- (optional) empty placeholder -->
{#if $assetStore.initialized && $assetStore.buckets.length === 0}
<slot name="empty" />
{/if}
<section id="virtual-timeline" style:height={$assetStore.timelineHeight + 'px'}>
{#each $assetStore.buckets as bucket, bucketIndex (bucketIndex)}
<IntersectionObserver

View file

@ -40,6 +40,7 @@ export class AssetStore {
private store$ = writable(this);
private assetToBucket: Record<string, AssetLookup> = {};
initialized = false;
timelineHeight = 0;
buckets: AssetBucket[] = [];
assets: AssetResponseDto[] = [];
@ -52,6 +53,7 @@ export class AssetStore {
subscribe = this.store$.subscribe;
async init(viewport: Viewport) {
this.initialized = false;
this.timelineHeight = 0;
this.buckets = [];
this.assets = [];
@ -63,6 +65,8 @@ export class AssetStore {
key: api.getKey(),
});
this.initialized = true;
this.buckets = buckets.map((bucket) => {
const unwrappedWidth = (3 / 2) * bucket.count * THUMBNAIL_HEIGHT * (7 / 10);
const rows = Math.ceil(unwrappedWidth / viewport.width);

View file

@ -14,25 +14,18 @@
import { AssetAction } from '$lib/constants';
import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store';
import { AssetStore } from '$lib/stores/assets.store';
import { api, TimeBucketSize } from '@api';
import { onMount } from 'svelte';
import { TimeBucketSize } from '@api';
import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
import Plus from 'svelte-material-icons/Plus.svelte';
import type { PageData } from './$types';
export let data: PageData;
let assetCount = 1;
const assetStore = new AssetStore({ size: TimeBucketSize.Month, isArchived: true });
const assetInteractionStore = createAssetInteractionStore();
const { isMultiSelectState, selectedAssets } = assetInteractionStore;
$: isAllFavorite = Array.from($selectedAssets).every((asset) => asset.isFavorite);
onMount(async () => {
const { data: stats } = await api.assetApi.getAssetStats({ isArchived: true });
assetCount = stats.total;
});
</script>
{#if $isMultiSelectState}
@ -52,10 +45,12 @@
</AssetSelectControlBar>
{/if}
<UserPageLayout user={data.user} hideNavbar={$isMultiSelectState} title={data.meta.title} scrollbar={!assetCount}>
{#if assetCount}
<AssetGrid {assetStore} {assetInteractionStore} removeAction={AssetAction.UNARCHIVE} />
{:else}
<EmptyPlaceholder text="Archive photos and videos to hide them from your Photos view" alt="Empty archive" />
{/if}
<UserPageLayout user={data.user} hideNavbar={$isMultiSelectState} title={data.meta.title}>
<AssetGrid {assetStore} {assetInteractionStore} removeAction={AssetAction.UNARCHIVE}>
<EmptyPlaceholder
text="Archive photos and videos to hide them from your Photos view"
alt="Empty archive"
slot="empty"
/>
</AssetGrid>
</UserPageLayout>

View file

@ -14,25 +14,18 @@
import { AssetAction } from '$lib/constants';
import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store';
import { AssetStore } from '$lib/stores/assets.store';
import { api, TimeBucketSize } from '@api';
import { onMount } from 'svelte';
import { TimeBucketSize } from '@api';
import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
import Plus from 'svelte-material-icons/Plus.svelte';
import type { PageData } from './$types';
export let data: PageData;
let assetCount = 1;
const assetStore = new AssetStore({ size: TimeBucketSize.Month, isFavorite: true });
const assetInteractionStore = createAssetInteractionStore();
const { isMultiSelectState, selectedAssets } = assetInteractionStore;
$: isAllArchive = Array.from($selectedAssets).every((asset) => asset.isArchived);
onMount(async () => {
const { data: stats } = await api.assetApi.getAssetStats({ isFavorite: true });
assetCount = stats.total;
});
</script>
<!-- Multiselection mode app bar -->
@ -53,10 +46,12 @@
</AssetSelectControlBar>
{/if}
<UserPageLayout user={data.user} hideNavbar={$isMultiSelectState} title={data.meta.title} scrollbar={!assetCount}>
{#if assetCount}
<AssetGrid {assetStore} {assetInteractionStore} removeAction={AssetAction.UNFAVORITE} />
{:else}
<EmptyPlaceholder text="Add favorites to quickly find your best pictures and videos" alt="Empty favorites" />
{/if}
<UserPageLayout user={data.user} hideNavbar={$isMultiSelectState} title={data.meta.title}>
<AssetGrid {assetStore} {assetInteractionStore} removeAction={AssetAction.UNFAVORITE}>
<EmptyPlaceholder
text="Add favorites to quickly find your best pictures and videos"
alt="Empty favorites"
slot="empty"
/>
</AssetGrid>
</UserPageLayout>

View file

@ -17,25 +17,18 @@
import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store';
import { AssetStore } from '$lib/stores/assets.store';
import { openFileUploadDialog } from '$lib/utils/file-uploader';
import { TimeBucketSize, api } from '@api';
import { onMount } from 'svelte';
import { TimeBucketSize } from '@api';
import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
import Plus from 'svelte-material-icons/Plus.svelte';
import type { PageData } from './$types';
export let data: PageData;
let assetCount = 1;
const assetStore = new AssetStore({ size: TimeBucketSize.Month, isArchived: false });
const assetInteractionStore = createAssetInteractionStore();
const { isMultiSelectState, selectedAssets } = assetInteractionStore;
$: isAllFavorite = Array.from($selectedAssets).every((asset) => asset.isFavorite);
onMount(async () => {
const { data: stats } = await api.assetApi.getAssetStats({ isArchived: false });
assetCount = stats.total;
});
</script>
<UserPageLayout user={data.user} hideNavbar={$isMultiSelectState} showUploadButton>
@ -59,14 +52,15 @@
{/if}
</svelte:fragment>
<svelte:fragment slot="content">
{#if assetCount}
<AssetGrid {assetStore} {assetInteractionStore} removeAction={AssetAction.ARCHIVE}>
{#if data.user.memoriesEnabled}
<MemoryLane />
{/if}
</AssetGrid>
{:else}
<EmptyPlaceholder text="CLICK TO UPLOAD YOUR FIRST PHOTO" actionHandler={() => openFileUploadDialog()} />
{/if}
<AssetGrid {assetStore} {assetInteractionStore} removeAction={AssetAction.ARCHIVE}>
{#if data.user.memoriesEnabled}
<MemoryLane />
{/if}
<EmptyPlaceholder
text="CLICK TO UPLOAD YOUR FIRST PHOTO"
actionHandler={() => openFileUploadDialog()}
slot="empty"
/>
</AssetGrid>
</svelte:fragment>
</UserPageLayout>