re-add alt-text

This commit is contained in:
Min Idzelis 2025-04-23 21:41:09 +00:00
parent 6cb7fffe91
commit 0795f8a761
8 changed files with 39 additions and 33 deletions

View File

@ -1,26 +1,27 @@
<script lang="ts">
import { shortcuts } from '$lib/actions/shortcut';
import { zoomImageAction, zoomed } from '$lib/actions/zoom-image';
import FaceEditor from '$lib/components/asset-viewer/face-editor/face-editor.svelte';
import BrokenAsset from '$lib/components/assets/broken-asset.svelte';
import { photoViewerImgElement } from '$lib/stores/assets-store.svelte';
import { isFaceEditMode } from '$lib/stores/face-edit.svelte';
import { boundingBoxesArray } from '$lib/stores/people.store';
import { alwaysLoadOriginalFile } from '$lib/stores/preferences.store';
import { SlideshowLook, SlideshowState, slideshowLookCssMapping, slideshowStore } from '$lib/stores/slideshow.store';
import { photoZoomState } from '$lib/stores/zoom-image.store';
import { getAssetOriginalUrl, getAssetThumbnailUrl, handlePromiseError } from '$lib/utils';
import { canCopyImageToClipboard, copyImageToClipboard, isWebCompatibleImage } from '$lib/utils/asset-utils';
import { handleError } from '$lib/utils/handle-error';
import { getBoundingBox } from '$lib/utils/people-utils';
import { getAltText } from '$lib/utils/thumbnail-util';
import { toTimelineAsset } from '$lib/utils/timeline-util';
import { AssetMediaSize, type AssetResponseDto, type SharedLinkResponseDto } from '@immich/sdk';
import { onDestroy, onMount } from 'svelte';
import { t } from 'svelte-i18n';
import { type SwipeCustomEvent, swipe } from 'svelte-gestures';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
import { NotificationType, notificationController } from '../shared-components/notification/notification';
import { handleError } from '$lib/utils/handle-error';
import FaceEditor from '$lib/components/asset-viewer/face-editor/face-editor.svelte';
import { photoViewerImgElement } from '$lib/stores/assets-store.svelte';
import { isFaceEditMode } from '$lib/stores/face-edit.svelte';
interface Props {
asset: AssetResponseDto;
@ -194,7 +195,7 @@
bind:clientWidth={containerWidth}
bind:clientHeight={containerHeight}
>
<img style="display:none" src={imageLoaderUrl} alt={$getAltText(asset)} {onload} {onerror} />
<img style="display:none" src={imageLoaderUrl} alt="" {onload} {onerror} />
{#if !imageLoaded}
<div id="spinner" class="flex h-full items-center justify-center">
<LoadingSpinner />
@ -210,7 +211,7 @@
{#if $slideshowState !== SlideshowState.None && $slideshowLook === SlideshowLook.BlurredBackground}
<img
src={assetFileUrl}
alt={$getAltText(asset)}
alt=""
class="absolute top-0 left-0 -z-10 object-cover h-full w-full blur-lg"
draggable="false"
/>
@ -218,7 +219,7 @@
<img
bind:this={$photoViewerImgElement}
src={assetFileUrl}
alt={$getAltText(asset)}
alt={$getAltText(toTimelineAsset(asset))}
class="h-full w-full {$slideshowState === SlideshowState.None
? 'object-contain'
: slideshowLookCssMapping[$slideshowLook]}"

View File

@ -4,7 +4,7 @@
import { locale, playVideoThumbnailOnHover } from '$lib/stores/preferences.store';
import { getAssetPlaybackUrl, getAssetThumbnailUrl, isSharedLink } from '$lib/utils';
import { timeToSeconds } from '$lib/utils/date-time';
// import { getAltText } from '$lib/utils/thumbnail-util';
import { getAltText } from '$lib/utils/thumbnail-util';
import { AssetMediaSize } from '@immich/sdk';
import {
mdiArchiveArrowDownOutline,
@ -21,7 +21,6 @@
import { mobileDevice } from '$lib/stores/mobile-device.svelte';
import { getFocusable } from '$lib/utils/focus-util';
import { currentUrlReplaceAssetId } from '$lib/utils/navigation';
import { getAltTextForTimelineAsset } from '$lib/utils/thumbnail-util';
import { TUNABLES } from '$lib/utils/tunables';
import { onMount } from 'svelte';
import type { ClassValue } from 'svelte/elements';
@ -368,12 +367,11 @@
</div>
{/if}
</div>
<!-- altText={$getAltText(asset)} -->
<ImageThumbnail
class={imageClass}
{brokenAssetClass}
url={getAssetThumbnailUrl({ id: asset.id, size: AssetMediaSize.Thumbnail, cacheKey: asset.thumbhash })}
altText={getAltTextForTimelineAsset(asset)}
altText={$getAltText(asset)}
widthStyle="{width}px"
heightStyle="{height}px"
curve={selected}

View File

@ -5,6 +5,7 @@
import { memoryStore } from '$lib/stores/memory.store.svelte';
import { getAssetThumbnailUrl, memoryLaneTitle } from '$lib/utils';
import { getAltText } from '$lib/utils/thumbnail-util';
import { toTimelineAsset } from '$lib/utils/timeline-util';
import { mdiChevronLeft, mdiChevronRight } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';
@ -82,7 +83,7 @@
<img
class="h-full w-full rounded-xl object-cover"
src={getAssetThumbnailUrl(memory.assets[0].id)}
alt={$t('memory_lane_title', { values: { title: $getAltText(memory.assets[0]) } })}
alt={$t('memory_lane_title', { values: { title: $getAltText(toTimelineAsset(memory.assets[0])) } })}
draggable="false"
/>
<p class="absolute bottom-2 left-4 z-10 text-lg text-white max-md:text-sm">

View File

@ -3,6 +3,7 @@
import { getAssetThumbnailUrl } from '$lib/utils';
import { getAssetResolution, getFileSize } from '$lib/utils/asset-utils';
import { getAltText } from '$lib/utils/thumbnail-util';
import { toTimelineAsset } from '$lib/utils/timeline-util';
import { type AssetResponseDto, getAllAlbums } from '@immich/sdk';
import { mdiHeart, mdiImageMultipleOutline, mdiMagnifyPlus } from '@mdi/js';
import { t } from 'svelte-i18n';
@ -36,7 +37,7 @@
<!-- THUMBNAIL-->
<img
src={getAssetThumbnailUrl(asset.id)}
alt={$getAltText(asset)}
alt={$getAltText(toTimelineAsset(asset))}
title={assetData}
class="h-60 object-cover rounded-t-xl w-full"
draggable="false"

View File

@ -15,7 +15,6 @@ import {
getTimeBucket,
getTimeBuckets,
TimeBucketSize,
type AssetResponseDto,
type AssetStackResponseDto,
} from '@immich/sdk';
import { clamp, debounce, isEqual, throttle } from 'lodash-es';
@ -85,6 +84,11 @@ export type TimelineAsset = {
duration: string | null;
projectionType: string | null;
livePhotoVideoId: string | null;
text: {
city: string | null;
country: string | null;
people: string[];
};
};
class IntersectingAsset {
// --- public ---
@ -1089,7 +1093,7 @@ export class AssetStore {
);
if (bucketResponse) {
if (this.#options.timelineAlbumId) {
const bucketAssets = await getTimeBucket(
const albumAssets = await getTimeBucket(
{
albumId: this.#options.timelineAlbumId,
timeBucket: bucketDate,
@ -1098,7 +1102,7 @@ export class AssetStore {
},
{ signal },
);
for (const { id } of bucketAssets) {
for (const { id } of albumAssets) {
this.albumAssets.add(id);
}
}

View File

@ -1,5 +1,4 @@
import type { TimelineAsset } from '$lib/stores/assets-store.svelte';
import { AssetTypeEnum, type AssetResponseDto } from '@immich/sdk';
import { t } from 'svelte-i18n';
import { derived } from 'svelte/store';
import { fromLocalDateTime } from './timeline-util';
@ -44,24 +43,18 @@ export const getAltTextForTimelineAsset = (_: TimelineAsset) => {
};
export const getAltText = derived(t, ($t) => {
return (asset: AssetResponseDto | null | undefined) => {
if (!asset) {
return '';
}
if (asset.exifInfo?.description) {
return asset.exifInfo.description;
}
return (asset: TimelineAsset) => {
const date = fromLocalDateTime(asset.localDateTime).toLocaleString({ dateStyle: 'long' });
const hasPlace = !!asset.exifInfo?.city && !!asset.exifInfo?.country;
const names = asset.people?.filter((p) => p.name).map((p) => p.name) ?? [];
const { city, country, people: names } = asset.text;
const hasPlace = city && country;
const peopleCount = names.length;
const isVideo = asset.type === AssetTypeEnum.Video;
const isVideo = asset.isVideo;
const values = {
date,
city: asset.exifInfo?.city,
country: asset.exifInfo?.country,
city,
country,
person1: names[0],
person2: names[1],
person3: names[2],

View File

@ -114,6 +114,14 @@ export const toTimelineAsset = (unknownAsset: AssetResponseDto | TimelineAsset):
const assetResponse = unknownAsset as AssetResponseDto;
const { width, height } = getAssetRatio(assetResponse);
const ratio = width / height;
const city = assetResponse.exifInfo?.city;
const country = assetResponse.exifInfo?.country;
const people = assetResponse.people?.map((person) => person.name) || [];
const text = {
city: city || null,
country: country || null,
people,
};
return {
id: assetResponse.id,
ownerId: assetResponse.ownerId,
@ -129,6 +137,7 @@ export const toTimelineAsset = (unknownAsset: AssetResponseDto | TimelineAsset):
duration: assetResponse.duration || null,
projectionType: assetResponse.exifInfo?.projectionType || null,
livePhotoVideoId: assetResponse.livePhotoVideoId || null,
text,
};
};
export const isTimelineAsset = (arg: AssetResponseDto | TimelineAsset): arg is TimelineAsset =>

View File

@ -1,4 +1,5 @@
import { browser } from '$app/environment';
function getBoolean(string: string | null, fallback: boolean) {
if (string === null) {
return fallback;
@ -11,13 +12,11 @@ function getNumber(string: string | null, fallback: number) {
}
return Number.parseInt(string);
}
const storage = browser
? localStorage
: {
getItem: () => null,
};
export const TUNABLES = {
LAYOUT: {
WASM: getBoolean(storage.getItem('LAYOUT.WASM'), false),