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:
parent
9539a361e4
commit
46c716d450
5 changed files with 49 additions and 45 deletions
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue