Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(web): optimize images and modules #7088

Merged
merged 12 commits into from
Feb 18, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
675 changes: 675 additions & 0 deletions web/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"@faker-js/faker": "^8.0.0",
"@floating-ui/dom": "^1.5.1",
"@sveltejs/adapter-static": "^3.0.1",
"@sveltejs/enhanced-img": "^0.1.8",
"@sveltejs/kit": "^2.0.6",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"@testing-library/jest-dom": "^6.1.5",
Expand Down
33 changes: 22 additions & 11 deletions web/src/lib/components/album-page/album-card.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import noThumbnailUrl from '$lib/assets/no-thumbnail.png';
import noThumbnailUrl from '@assets/no-thumbnail.png?enhanced';
import { locale } from '$lib/stores/preferences.store';
import { type AlbumResponseDto, api, ThumbnailFormat, type UserResponseDto } from '@api';
import { createEventDispatcher, onMount } from 'svelte';
Expand All @@ -19,7 +19,7 @@

$: imageData = album.albumThumbnailAssetId
? api.getAssetThumbnailUrl(album.albumThumbnailAssetId, ThumbnailFormat.Webp)
: noThumbnailUrl;
: null;

const dispatchClick = createEventDispatcher<OnClick>();
const dispatchShowContextMenu = createEventDispatcher<OnShowContextMenu>();
Expand Down Expand Up @@ -48,7 +48,7 @@
dispatchShowContextMenu('showalbumcontextmenu', getContextMenuPosition(e));

onMount(async () => {
imageData = (await loadHighQualityThumbnail(album.albumThumbnailAssetId)) || noThumbnailUrl;
imageData = (await loadHighQualityThumbnail(album.albumThumbnailAssetId)) || null;
});

const getAlbumOwnerInfo = async (): Promise<UserResponseDto> => {
Expand Down Expand Up @@ -83,14 +83,25 @@
{/if}

<div class={`relative aspect-square`}>
<img
loading={preload ? 'eager' : 'lazy'}
src={imageData}
alt={album.id}
class={`z-0 h-full w-full rounded-xl object-cover transition-all duration-300 hover:shadow-lg`}
data-testid="album-image"
draggable="false"
/>
{#if album.albumThumbnailAssetId}
<img
loading={preload ? 'eager' : 'lazy'}
src={imageData}
alt={album.id}
class={`z-0 h-full w-full rounded-xl object-cover transition-all duration-300 hover:shadow-lg`}
data-testid="album-image"
draggable="false"
/>
{:else}
<enhanced:img
loading={preload ? 'eager' : 'lazy'}
src={noThumbnailUrl}
Copy link
Contributor

@benmccann benmccann Feb 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to do this and then remove the import above:

Suggested change
src={noThumbnailUrl}
src="@assets/no-thumbnail.png"

Same elsewhere

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although maybe it's be better to leave the import and then do something like <enhanced:img src={imageData || noThumbnailUrl} /> where you have only a single image. I'm not 100% sure that would work, but I think it should. I might be able to put together a fix for it if it doesn't work

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks pixelated unless I provide the dimensions with ?w=271;186, do you know a better way ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. Including the dimensions should have no effect since those are the original image dimensions. Do you need to do that when referring to it directly via the src attribute, when importing it, or both?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Providing the dimensions via the src or sizes attributes fix it :

  • sizes="min(271px,186px)"

or

  • src="$lib/assets/no-thumbnail.png?w=271;186"

(271x186 are the dimensions of the original picture)

alt={album.id}
class={`z-0 h-full w-full rounded-xl object-cover transition-all duration-300 hover:shadow-lg`}
martabal marked this conversation as resolved.
Show resolved Hide resolved
data-testid="album-image"
draggable="false"
/>
{/if}
<div class="absolute top-0 h-full w-full rounded-3xl" />
</div>

Expand Down
56 changes: 33 additions & 23 deletions web/src/lib/components/asset-viewer/detail-panel.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script lang="ts">
import { locale } from '$lib/stores/preferences.store';
import { featureFlags } from '$lib/stores/server-config.store';
import { getAssetFilename } from '$lib/utils/asset-utils';
import { delay, getAssetFilename } from '$lib/utils/asset-utils';
import { type AlbumResponseDto, type AssetResponseDto, ThumbnailFormat, api } from '@api';
import { DateTime } from 'luxon';
import { createEventDispatcher, onDestroy } from 'svelte';
Expand All @@ -24,15 +24,15 @@
import Icon from '$lib/components/elements/icon.svelte';
import PersonSidePanel from '../faces-page/person-side-panel.svelte';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
import Map from '../shared-components/map/map.svelte';
import { boundingBoxesArray } from '$lib/stores/people.store';
import { websocketStore } from '$lib/stores/websocket';
import { AppRoute, QueryParameter } from '$lib/constants';
import { AppRoute, QueryParameter, timeToLoadTheMap } from '$lib/constants';
import ChangeLocation from '../shared-components/change-location.svelte';
import { handleError } from '../../utils/handle-error';
import { user } from '$lib/stores/user.store';
import { autoGrowHeight } from '$lib/utils/autogrow';
import { clickOutside } from '$lib/utils/click-outside';
import LoadingSpinner from '../shared-components/loading-spinner.svelte';

export let asset: AssetResponseDto;
export let albums: AlbumResponseDto[] = [];
Expand Down Expand Up @@ -613,27 +613,37 @@

{#if latlng && $featureFlags.loaded && $featureFlags.map}
<div class="h-[360px]">
<Map
mapMarkers={[{ lat: latlng.lat, lon: latlng.lng, id: asset.id }]}
center={latlng}
zoom={15}
simplified
useLocationPin
>
<svelte:fragment slot="popup" let:marker>
{@const { lat, lon } = marker}
<div class="flex flex-col items-center gap-1">
<p class="font-bold">{lat.toPrecision(6)}, {lon.toPrecision(6)}</p>
<a
href="https://www.openstreetmap.org/?mlat={lat}&mlon={lon}&zoom=15#map=15/{lat}/{lon}"
target="_blank"
class="font-medium text-immich-primary"
>
Open in OpenStreetMap
</a>
{#await import('../shared-components/map/map.svelte')}
{#await !delay(timeToLoadTheMap)}
<!-- show the loading spinner only if loading the map takes too much time -->
<div class="flex items-center justify-center h-full w-full">
<LoadingSpinner />
</div>
</svelte:fragment>
</Map>
{/await}
{:then component}
<svelte:component
this={component.default}
mapMarkers={[{ lat: latlng.lat, lon: latlng.lng, id: asset.id }]}
center={latlng}
zoom={15}
simplified
useLocationPin
>
<svelte:fragment slot="popup" let:marker>
{@const { lat, lon } = marker}
<div class="flex flex-col items-center gap-1">
<p class="font-bold">{lat.toPrecision(6)}, {lon.toPrecision(6)}</p>
<a
href="https://www.openstreetmap.org/?mlat={lat}&mlon={lon}&zoom=15#map=15/{lat}/{lon}"
target="_blank"
class="font-medium text-immich-primary"
>
Open in OpenStreetMap
</a>
</div>
</svelte:fragment>
</svelte:component>
{/await}
</div>
{/if}

Expand Down
44 changes: 31 additions & 13 deletions web/src/lib/components/memory-page/memory-viewer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import { fromLocalDateTime } from '$lib/utils/timeline-util';
import { AppRoute, QueryParameter } from '$lib/constants';
import { page } from '$app/stores';
import noThumbnailUrl from '$lib/assets/no-thumbnail.png';
import noThumbnailUrl from '@assets/no-thumbnail.png?enhanced';
import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import IntersectionObserver from '$lib/components/asset-viewer/intersection-observer.svelte';
Expand Down Expand Up @@ -166,12 +166,21 @@
class:hover:opacity-70={previousMemory}
>
<button class="relative h-full w-full rounded-2xl" disabled={!previousMemory} on:click={toPreviousMemory}>
<img
class="h-full w-full rounded-2xl object-cover"
src={previousMemory ? api.getAssetThumbnailUrl(previousMemory.assets[0].id, 'JPEG') : noThumbnailUrl}
alt=""
draggable="false"
/>
{#if previousMemory}
<img
class="h-full w-full rounded-2xl object-cover"
src={api.getAssetThumbnailUrl(previousMemory.assets[0].id, 'JPEG')}
alt=""
draggable="false"
/>
{:else}
<enhanced:img
class="h-full w-full rounded-2xl object-cover"
src={noThumbnailUrl}
alt=""
draggable="false"
/>
{/if}

{#if previousMemory}
<div class="absolute bottom-4 right-4 text-left text-white">
Expand Down Expand Up @@ -229,12 +238,21 @@
class:hover:opacity-70={nextMemory}
>
<button class="relative h-full w-full rounded-2xl" on:click={toNextMemory} disabled={!nextMemory}>
<img
class="h-full w-full rounded-2xl object-cover"
src={nextMemory ? api.getAssetThumbnailUrl(nextMemory.assets[0].id, 'JPEG') : noThumbnailUrl}
alt=""
draggable="false"
/>
{#if nextMemory}
<img
class="h-full w-full rounded-2xl object-cover"
src={api.getAssetThumbnailUrl(nextMemory.assets[0].id, 'JPEG')}
alt=""
draggable="false"
/>
{:else}
<enhanced:img
class="h-full w-full rounded-2xl object-cover"
src={noThumbnailUrl}
alt=""
draggable="false"
/>
{/if}

{#if nextMemory}
<div class="absolute bottom-4 left-4 text-left text-white">
Expand Down
60 changes: 30 additions & 30 deletions web/src/lib/components/shared-components/apple-header.svelte
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
<script lang="ts">
import appleSplash20482732 from '$lib/assets/apple/apple-splash-2048-2732.png';
import appleSplash27322048 from '$lib/assets/apple/apple-splash-2732-2048.png';
import appleSplash16682388 from '$lib/assets/apple/apple-splash-1668-2388.png';
import appleSplash23881668 from '$lib/assets/apple/apple-splash-2388-1668.png';
import appleSplash15362048 from '$lib/assets/apple/apple-splash-1536-2048.png';
import appleSplash20481536 from '$lib/assets/apple/apple-splash-2048-1536.png';
import appleSplash16682224 from '$lib/assets/apple/apple-splash-1668-2224.png';
import appleSplash22241668 from '$lib/assets/apple/apple-splash-2224-1668.png';
import appleSplash16202160 from '$lib/assets/apple/apple-splash-1620-2160.png';
import appleSplash21601620 from '$lib/assets/apple/apple-splash-2160-1620.png';
import appleSplash12902796 from '$lib/assets/apple/apple-splash-1290-2796.png';
import appleSplash27961290 from '$lib/assets/apple/apple-splash-2796-1290.png';
import appleSplash11792556 from '$lib/assets/apple/apple-splash-1179-2556.png';
import appleSplash25561179 from '$lib/assets/apple/apple-splash-2556-1179.png';
import appleSplash12842778 from '$lib/assets/apple/apple-splash-1284-2778.png';
import appleSplash27781284 from '$lib/assets/apple/apple-splash-2778-1284.png';
import appleSplash11702532 from '$lib/assets/apple/apple-splash-1170-2532.png';
import appleSplash25321170 from '$lib/assets/apple/apple-splash-2532-1170.png';
import appleSplash11252436 from '$lib/assets/apple/apple-splash-1125-2436.png';
import appleSplash24361125 from '$lib/assets/apple/apple-splash-2436-1125.png';
import appleSplash12422688 from '$lib/assets/apple/apple-splash-1242-2688.png';
import appleSplash26881242 from '$lib/assets/apple/apple-splash-2688-1242.png';
import appleSplash8281792 from '$lib/assets/apple/apple-splash-828-1792.png';
import appleSplash1792828 from '$lib/assets/apple/apple-splash-1792-828.png';
import appleSplash12422208 from '$lib/assets/apple/apple-splash-1242-2208.png';
import appleSplash22081242 from '$lib/assets/apple/apple-splash-2208-1242.png';
import appleSplash7501334 from '$lib/assets/apple/apple-splash-750-1334.png';
import appleSplash1334750 from '$lib/assets/apple/apple-splash-1334-750.png';
import appleSplash6401136 from '$lib/assets/apple/apple-splash-640-1136.png';
import appleSplash1136640 from '$lib/assets/apple/apple-splash-1136-640.png';
import appleSplash20482732 from '@assets/apple/apple-splash-2048-2732.png';
import appleSplash27322048 from '@assets/apple/apple-splash-2732-2048.png';
import appleSplash16682388 from '@assets/apple/apple-splash-1668-2388.png';
import appleSplash23881668 from '@assets/apple/apple-splash-2388-1668.png';
import appleSplash15362048 from '@assets/apple/apple-splash-1536-2048.png';
import appleSplash20481536 from '@assets/apple/apple-splash-2048-1536.png';
import appleSplash16682224 from '@assets/apple/apple-splash-1668-2224.png';
import appleSplash22241668 from '@assets/apple/apple-splash-2224-1668.png';
import appleSplash16202160 from '@assets/apple/apple-splash-1620-2160.png';
import appleSplash21601620 from '@assets/apple/apple-splash-2160-1620.png';
import appleSplash12902796 from '@assets/apple/apple-splash-1290-2796.png';
import appleSplash27961290 from '@assets/apple/apple-splash-2796-1290.png';
import appleSplash11792556 from '@assets/apple/apple-splash-1179-2556.png';
import appleSplash25561179 from '@assets/apple/apple-splash-2556-1179.png';
import appleSplash12842778 from '@assets/apple/apple-splash-1284-2778.png';
import appleSplash27781284 from '@assets/apple/apple-splash-2778-1284.png';
import appleSplash11702532 from '@assets/apple/apple-splash-1170-2532.png';
import appleSplash25321170 from '@assets/apple/apple-splash-2532-1170.png';
import appleSplash11252436 from '@assets/apple/apple-splash-1125-2436.png';
import appleSplash24361125 from '@assets/apple/apple-splash-2436-1125.png';
import appleSplash12422688 from '@assets/apple/apple-splash-1242-2688.png';
import appleSplash26881242 from '@assets/apple/apple-splash-2688-1242.png';
import appleSplash8281792 from '@assets/apple/apple-splash-828-1792.png';
import appleSplash1792828 from '@assets/apple/apple-splash-1792-828.png';
import appleSplash12422208 from '@assets/apple/apple-splash-1242-2208.png';
import appleSplash22081242 from '@assets/apple/apple-splash-2208-1242.png';
import appleSplash7501334 from '@assets/apple/apple-splash-750-1334.png';
import appleSplash1334750 from '@assets/apple/apple-splash-1334-750.png';
import appleSplash6401136 from '@assets/apple/apple-splash-640-1136.png';
import appleSplash1136640 from '@assets/apple/apple-splash-1136-640.png';
</script>

<link
Expand Down
31 changes: 22 additions & 9 deletions web/src/lib/components/shared-components/change-location.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
import type { AssetResponseDto } from '@api';
import { createEventDispatcher } from 'svelte';
import ConfirmDialogue from './confirm-dialogue.svelte';
import Map from './map/map.svelte';
import LoadingSpinner from './loading-spinner.svelte';
import { delay } from '$lib/utils/asset-utils';
import { timeToLoadTheMap } from '$lib/constants';

export const title = 'Change Location';
export let asset: AssetResponseDto | undefined = undefined;

Expand Down Expand Up @@ -48,14 +51,24 @@
<div slot="prompt" class="flex flex-col w-full h-full gap-2">
<label for="datetime">Pick a location</label>
<div class="h-[500px] min-h-[300px] w-full">
<Map
mapMarkers={lat && lng && asset ? [{ id: asset.id, lat, lon: lng }] : []}
{zoom}
center={lat && lng ? { lat, lng } : undefined}
simplified={true}
clickable={true}
on:clickedPoint={({ detail: point }) => handleSelect(point)}
/>
{#await import('../shared-components/map/map.svelte')}
{#await !delay(timeToLoadTheMap)}
<!-- show the loading spinner only if loading the map takes too much time -->
<div class="flex items-center justify-center h-full w-full">
<LoadingSpinner />
</div>
{/await}
{:then component}
<svelte:component
this={component.default}
mapMarkers={lat && lng && asset ? [{ id: asset.id, lat, lon: lng }] : []}
{zoom}
center={lat && lng ? { lat, lng } : undefined}
simplified={true}
clickable={true}
on:clickedPoint={({ detail: point }) => handleSelect(point)}
/>
{/await}
</div>
</div>
</ConfirmDialogue>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import empty1Url from '$lib/assets/empty-1.svg';
import empty1Url from '@assets/empty-1.svg';

export let actionHandler: undefined | (() => unknown) = undefined;
export let text = '';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import immichLogoUrl from '$lib/assets/immich-logo.svg';
import immichLogoUrl from '@assets/immich-logo.svg';

export let draggable = false;
</script>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import { createEventDispatcher } from 'svelte';
import { goto } from '$app/navigation';
import { mdiCircleEditOutline, mdiContentCopy, mdiDelete, mdiOpenInNew } from '@mdi/js';
import noThumbnailUrl from '$lib/assets/no-thumbnail.png';
import noThumbnailUrl from '@assets/no-thumbnail.png?enhanced';
import { AppRoute } from '$lib/constants';

export let link: SharedLinkResponseDto;
Expand Down Expand Up @@ -80,7 +80,7 @@
/>
{/await}
{:else}
<img
<enhanced:img
src={noThumbnailUrl}
alt={'Album without assets'}
class="h-[100px] w-[100px] rounded-lg object-cover"
Expand Down
4 changes: 4 additions & 0 deletions web/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,11 @@ export enum ActionQueryParameterValue {

export const maximumLengthSearchPeople: number = 20;

// time to load the map before displaying the loading spinner
export const timeToLoadTheMap: number = 100;

export const timeBeforeShowLoadingSpinner: number = 100;

// should be the same values as the ones in the app.html
export enum Theme {
LIGHT = 'light',
Expand Down
8 changes: 8 additions & 0 deletions web/src/lib/utils/asset-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,11 @@ export const getSelectedAssets = (assets: Set<AssetResponseDto>, user: UserRespo
}
return ids;
};

export const delay = async (ms: number) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve('');
}, ms);
});
};