chore(web): migration svelte 5 syntax (#13883)

This commit is contained in:
Alex 2024-11-14 08:43:25 -06:00 committed by GitHub
parent 9203a61709
commit 0b3742cf13
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
310 changed files with 6435 additions and 4176 deletions

View file

@ -7,29 +7,49 @@
import { onMount } from 'svelte';
import { fade } from 'svelte/transition';
export let url: string;
export let altText: string | undefined;
export let title: string | null = null;
export let heightStyle: string | undefined = undefined;
export let widthStyle: string;
export let base64ThumbHash: string | null = null;
export let curve = false;
export let shadow = false;
export let circle = false;
export let hidden = false;
export let border = false;
export let preload = true;
export let hiddenIconClass = 'text-white';
export let onComplete: (() => void) | undefined = undefined;
interface Props {
url: string;
altText: string | undefined;
title?: string | null;
heightStyle?: string | undefined;
widthStyle: string;
base64ThumbHash?: string | null;
curve?: boolean;
shadow?: boolean;
circle?: boolean;
hidden?: boolean;
border?: boolean;
preload?: boolean;
hiddenIconClass?: string;
onComplete?: (() => void) | undefined;
onClick?: (() => void) | undefined;
}
let {
url,
altText,
title = null,
heightStyle = undefined,
widthStyle,
base64ThumbHash = null,
curve = false,
shadow = false,
circle = false,
hidden = false,
border = false,
preload = true,
hiddenIconClass = 'text-white',
onComplete = undefined,
}: Props = $props();
let {
IMAGE_THUMBNAIL: { THUMBHASH_FADE_DURATION },
} = TUNABLES;
let loaded = false;
let errored = false;
let loaded = $state(false);
let errored = $state(false);
let img: HTMLImageElement;
let img = $state<HTMLImageElement>();
const setLoaded = () => {
loaded = true;
@ -40,20 +60,22 @@
onComplete?.();
};
onMount(() => {
if (img.complete) {
if (img?.complete) {
setLoaded();
}
});
$: optionalClasses = [
curve && 'rounded-xl',
circle && 'rounded-full',
shadow && 'shadow-lg',
(circle || !heightStyle) && 'aspect-square',
border && 'border-[3px] border-immich-dark-primary/80 hover:border-immich-primary',
]
.filter(Boolean)
.join(' ');
let optionalClasses = $derived(
[
curve && 'rounded-xl',
circle && 'rounded-full',
shadow && 'shadow-lg',
(circle || !heightStyle) && 'aspect-square',
border && 'border-[3px] border-immich-dark-primary/80 hover:border-immich-primary',
]
.filter(Boolean)
.join(' '),
);
</script>
{#if errored}
@ -61,8 +83,8 @@
{:else}
<img
bind:this={img}
on:load={setLoaded}
on:error={setErrored}
onload={setLoaded}
onerror={setErrored}
loading={preload ? 'eager' : 'lazy'}
style:width={widthStyle}
style:height={heightStyle}

View file

@ -31,62 +31,89 @@
import { TUNABLES } from '$lib/utils/tunables';
import { thumbhash } from '$lib/actions/thumbhash';
export let asset: AssetResponseDto;
export let dateGroup: DateGroup | undefined = undefined;
export let assetStore: AssetStore | undefined = undefined;
export let groupIndex = 0;
export let thumbnailSize: number | undefined = undefined;
export let thumbnailWidth: number | undefined = undefined;
export let thumbnailHeight: number | undefined = undefined;
export let selected = false;
export let selectionCandidate = false;
export let disabled = false;
export let readonly = false;
export let showArchiveIcon = false;
export let showStackedIcon = true;
export let disableMouseOver = false;
export let intersectionConfig: {
root?: HTMLElement;
bottom?: string;
top?: string;
left?: string;
priority?: number;
interface Props {
asset: AssetResponseDto;
dateGroup?: DateGroup | undefined;
assetStore?: AssetStore | undefined;
groupIndex?: number;
thumbnailSize?: number | undefined;
thumbnailWidth?: number | undefined;
thumbnailHeight?: number | undefined;
selected?: boolean;
selectionCandidate?: boolean;
disabled?: boolean;
} = {};
readonly?: boolean;
showArchiveIcon?: boolean;
showStackedIcon?: boolean;
disableMouseOver?: boolean;
intersectionConfig?: {
root?: HTMLElement;
bottom?: string;
top?: string;
left?: string;
priority?: number;
disabled?: boolean;
};
retrieveElement?: boolean;
onIntersected?: (() => void) | undefined;
onClick?: ((asset: AssetResponseDto) => void) | undefined;
onRetrieveElement?: ((elment: HTMLElement) => void) | undefined;
onSelect?: ((asset: AssetResponseDto) => void) | undefined;
onMouseEvent?: ((event: { isMouseOver: boolean; selectedGroupIndex: number }) => void) | undefined;
class?: string;
}
export let retrieveElement: boolean = false;
export let onIntersected: (() => void) | undefined = undefined;
export let onClick: ((asset: AssetResponseDto) => void) | undefined = undefined;
export let onRetrieveElement: ((elment: HTMLElement) => void) | undefined = undefined;
export let onSelect: ((asset: AssetResponseDto) => void) | undefined = undefined;
export let onMouseEvent: ((event: { isMouseOver: boolean; selectedGroupIndex: number }) => void) | undefined =
undefined;
let className = '';
export { className as class };
let {
asset,
dateGroup = undefined,
assetStore = undefined,
groupIndex = 0,
thumbnailSize = undefined,
thumbnailWidth = undefined,
thumbnailHeight = undefined,
selected = false,
selectionCandidate = false,
disabled = false,
readonly = false,
showArchiveIcon = false,
showStackedIcon = true,
disableMouseOver = false,
intersectionConfig = {},
retrieveElement = false,
onIntersected = undefined,
onClick = undefined,
onRetrieveElement = undefined,
onSelect = undefined,
onMouseEvent = undefined,
class: className = '',
}: Props = $props();
let {
IMAGE_THUMBNAIL: { THUMBHASH_FADE_DURATION },
} = TUNABLES;
const componentId = generateId();
let element: HTMLElement | undefined;
let mouseOver = false;
let intersecting = false;
let lastRetrievedElement: HTMLElement | undefined;
let loaded = false;
let element: HTMLElement | undefined = $state();
let mouseOver = $state(false);
let intersecting = $state(false);
let lastRetrievedElement: HTMLElement | undefined = $state();
let loaded = $state(false);
$: if (!retrieveElement) {
lastRetrievedElement = undefined;
}
$: if (retrieveElement && element && lastRetrievedElement !== element) {
lastRetrievedElement = element;
onRetrieveElement?.(element);
}
$effect(() => {
if (!retrieveElement) {
lastRetrievedElement = undefined;
}
});
$effect(() => {
if (retrieveElement && element && lastRetrievedElement !== element) {
lastRetrievedElement = element;
onRetrieveElement?.(element);
}
});
$: width = thumbnailSize || thumbnailWidth || 235;
$: height = thumbnailSize || thumbnailHeight || 235;
$: display = intersecting;
let width = $derived(thumbnailSize || thumbnailWidth || 235);
let height = $derived(thumbnailSize || thumbnailHeight || 235);
let display = $derived(intersecting);
const onIconClickedHandler = (e?: MouseEvent) => {
e?.stopPropagation();
@ -197,15 +224,15 @@
class="group"
class:cursor-not-allowed={disabled}
class:cursor-pointer={!disabled}
on:mouseenter={onMouseEnter}
on:mouseleave={onMouseLeave}
on:keypress={(evt) => {
onmouseenter={onMouseEnter}
onmouseleave={onMouseLeave}
onkeypress={(evt) => {
if (evt.key === 'Enter') {
callClickHandlers();
}
}}
tabindex={0}
on:click={handleClick}
onclick={handleClick}
role="link"
>
{#if mouseOver && !disableMouseOver}
@ -216,7 +243,7 @@
style:width="{width}px"
style:height="{height}px"
href={currentUrlReplaceAssetId(asset.id)}
on:click={(evt) => evt.preventDefault()}
onclick={(evt) => evt.preventDefault()}
tabindex={0}
aria-label="Thumbnail URL"
>
@ -227,7 +254,7 @@
{#if !readonly && (mouseOver || selected || selectionCandidate)}
<button
type="button"
on:click={onIconClickedHandler}
onclick={onIconClickedHandler}
class="absolute p-2 focus:outline-none"
class:cursor-not-allowed={disabled}
role="checkbox"

View file

@ -7,31 +7,47 @@
import { generateId } from '$lib/utils/generate-id';
import { onDestroy } from 'svelte';
export let assetStore: AssetStore | undefined = undefined;
export let url: string;
export let durationInSeconds = 0;
export let enablePlayback = false;
export let playbackOnIconHover = false;
export let showTime = true;
export let curve = false;
export let playIcon = mdiPlayCircleOutline;
export let pauseIcon = mdiPauseCircleOutline;
interface Props {
assetStore?: AssetStore | undefined;
url: string;
durationInSeconds?: number;
enablePlayback?: boolean;
playbackOnIconHover?: boolean;
showTime?: boolean;
curve?: boolean;
playIcon?: string;
pauseIcon?: string;
}
let {
assetStore = undefined,
url,
durationInSeconds = 0,
enablePlayback = $bindable(false),
playbackOnIconHover = false,
showTime = true,
curve = false,
playIcon = mdiPlayCircleOutline,
pauseIcon = mdiPauseCircleOutline,
}: Props = $props();
const componentId = generateId();
let remainingSeconds = durationInSeconds;
let loading = true;
let error = false;
let player: HTMLVideoElement;
let remainingSeconds = $state(durationInSeconds);
let loading = $state(true);
let error = $state(false);
let player: HTMLVideoElement | undefined = $state();
$: if (!enablePlayback) {
// Reset remaining time when playback is disabled.
remainingSeconds = durationInSeconds;
$effect(() => {
if (!enablePlayback) {
// Reset remaining time when playback is disabled.
remainingSeconds = durationInSeconds;
if (player) {
// Cancel video buffering.
player.src = '';
if (player) {
// Cancel video buffering.
player.src = '';
}
}
}
});
const onMouseEnter = () => {
if (assetStore) {
assetStore.taskManager.queueScrollSensitiveTask({
@ -78,8 +94,8 @@
</span>
{/if}
<!-- svelte-ignore a11y-no-static-element-interactions -->
<span class="pr-2 pt-2" on:mouseenter={onMouseEnter} on:mouseleave={onMouseLeave}>
<!-- svelte-ignore a11y_no_static_element_interactions -->
<span class="pr-2 pt-2" onmouseenter={onMouseEnter} onmouseleave={onMouseLeave}>
{#if enablePlayback}
{#if loading}
<LoadingSpinner />
@ -103,15 +119,19 @@
autoplay
loop
src={url}
on:play={() => {
onplay={() => {
loading = false;
error = false;
}}
on:error={() => {
onerror={() => {
if (!player?.src) {
// Do not show error when the URL is empty.
return;
}
error = true;
loading = false;
}}
on:timeupdate={({ currentTarget }) => {
ontimeupdate={({ currentTarget }) => {
const remaining = currentTarget.duration - currentTarget.currentTime;
remainingSeconds = Math.min(
Math.ceil(Number.isNaN(remaining) ? Number.POSITIVE_INFINITY : remaining),