mirror of
https://github.com/immich-app/immich.git
synced 2025-07-08 02:34:12 -04:00
re-add alt-text
This commit is contained in:
parent
6cb7fffe91
commit
0795f8a761
@ -1,26 +1,27 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { shortcuts } from '$lib/actions/shortcut';
|
import { shortcuts } from '$lib/actions/shortcut';
|
||||||
import { zoomImageAction, zoomed } from '$lib/actions/zoom-image';
|
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 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 { boundingBoxesArray } from '$lib/stores/people.store';
|
||||||
import { alwaysLoadOriginalFile } from '$lib/stores/preferences.store';
|
import { alwaysLoadOriginalFile } from '$lib/stores/preferences.store';
|
||||||
import { SlideshowLook, SlideshowState, slideshowLookCssMapping, slideshowStore } from '$lib/stores/slideshow.store';
|
import { SlideshowLook, SlideshowState, slideshowLookCssMapping, slideshowStore } from '$lib/stores/slideshow.store';
|
||||||
import { photoZoomState } from '$lib/stores/zoom-image.store';
|
import { photoZoomState } from '$lib/stores/zoom-image.store';
|
||||||
import { getAssetOriginalUrl, getAssetThumbnailUrl, handlePromiseError } from '$lib/utils';
|
import { getAssetOriginalUrl, getAssetThumbnailUrl, handlePromiseError } from '$lib/utils';
|
||||||
import { canCopyImageToClipboard, copyImageToClipboard, isWebCompatibleImage } from '$lib/utils/asset-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 { getBoundingBox } from '$lib/utils/people-utils';
|
||||||
import { getAltText } from '$lib/utils/thumbnail-util';
|
import { getAltText } from '$lib/utils/thumbnail-util';
|
||||||
|
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||||
import { AssetMediaSize, type AssetResponseDto, type SharedLinkResponseDto } from '@immich/sdk';
|
import { AssetMediaSize, type AssetResponseDto, type SharedLinkResponseDto } from '@immich/sdk';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import { t } from 'svelte-i18n';
|
|
||||||
import { type SwipeCustomEvent, swipe } from 'svelte-gestures';
|
import { type SwipeCustomEvent, swipe } from 'svelte-gestures';
|
||||||
|
import { t } from 'svelte-i18n';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
||||||
import { NotificationType, notificationController } from '../shared-components/notification/notification';
|
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 {
|
interface Props {
|
||||||
asset: AssetResponseDto;
|
asset: AssetResponseDto;
|
||||||
@ -194,7 +195,7 @@
|
|||||||
bind:clientWidth={containerWidth}
|
bind:clientWidth={containerWidth}
|
||||||
bind:clientHeight={containerHeight}
|
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}
|
{#if !imageLoaded}
|
||||||
<div id="spinner" class="flex h-full items-center justify-center">
|
<div id="spinner" class="flex h-full items-center justify-center">
|
||||||
<LoadingSpinner />
|
<LoadingSpinner />
|
||||||
@ -210,7 +211,7 @@
|
|||||||
{#if $slideshowState !== SlideshowState.None && $slideshowLook === SlideshowLook.BlurredBackground}
|
{#if $slideshowState !== SlideshowState.None && $slideshowLook === SlideshowLook.BlurredBackground}
|
||||||
<img
|
<img
|
||||||
src={assetFileUrl}
|
src={assetFileUrl}
|
||||||
alt={$getAltText(asset)}
|
alt=""
|
||||||
class="absolute top-0 left-0 -z-10 object-cover h-full w-full blur-lg"
|
class="absolute top-0 left-0 -z-10 object-cover h-full w-full blur-lg"
|
||||||
draggable="false"
|
draggable="false"
|
||||||
/>
|
/>
|
||||||
@ -218,7 +219,7 @@
|
|||||||
<img
|
<img
|
||||||
bind:this={$photoViewerImgElement}
|
bind:this={$photoViewerImgElement}
|
||||||
src={assetFileUrl}
|
src={assetFileUrl}
|
||||||
alt={$getAltText(asset)}
|
alt={$getAltText(toTimelineAsset(asset))}
|
||||||
class="h-full w-full {$slideshowState === SlideshowState.None
|
class="h-full w-full {$slideshowState === SlideshowState.None
|
||||||
? 'object-contain'
|
? 'object-contain'
|
||||||
: slideshowLookCssMapping[$slideshowLook]}"
|
: slideshowLookCssMapping[$slideshowLook]}"
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
import { locale, playVideoThumbnailOnHover } from '$lib/stores/preferences.store';
|
import { locale, playVideoThumbnailOnHover } from '$lib/stores/preferences.store';
|
||||||
import { getAssetPlaybackUrl, getAssetThumbnailUrl, isSharedLink } from '$lib/utils';
|
import { getAssetPlaybackUrl, getAssetThumbnailUrl, isSharedLink } from '$lib/utils';
|
||||||
import { timeToSeconds } from '$lib/utils/date-time';
|
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 { AssetMediaSize } from '@immich/sdk';
|
||||||
import {
|
import {
|
||||||
mdiArchiveArrowDownOutline,
|
mdiArchiveArrowDownOutline,
|
||||||
@ -21,7 +21,6 @@
|
|||||||
import { mobileDevice } from '$lib/stores/mobile-device.svelte';
|
import { mobileDevice } from '$lib/stores/mobile-device.svelte';
|
||||||
import { getFocusable } from '$lib/utils/focus-util';
|
import { getFocusable } from '$lib/utils/focus-util';
|
||||||
import { currentUrlReplaceAssetId } from '$lib/utils/navigation';
|
import { currentUrlReplaceAssetId } from '$lib/utils/navigation';
|
||||||
import { getAltTextForTimelineAsset } from '$lib/utils/thumbnail-util';
|
|
||||||
import { TUNABLES } from '$lib/utils/tunables';
|
import { TUNABLES } from '$lib/utils/tunables';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import type { ClassValue } from 'svelte/elements';
|
import type { ClassValue } from 'svelte/elements';
|
||||||
@ -368,12 +367,11 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<!-- altText={$getAltText(asset)} -->
|
|
||||||
<ImageThumbnail
|
<ImageThumbnail
|
||||||
class={imageClass}
|
class={imageClass}
|
||||||
{brokenAssetClass}
|
{brokenAssetClass}
|
||||||
url={getAssetThumbnailUrl({ id: asset.id, size: AssetMediaSize.Thumbnail, cacheKey: asset.thumbhash })}
|
url={getAssetThumbnailUrl({ id: asset.id, size: AssetMediaSize.Thumbnail, cacheKey: asset.thumbhash })}
|
||||||
altText={getAltTextForTimelineAsset(asset)}
|
altText={$getAltText(asset)}
|
||||||
widthStyle="{width}px"
|
widthStyle="{width}px"
|
||||||
heightStyle="{height}px"
|
heightStyle="{height}px"
|
||||||
curve={selected}
|
curve={selected}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
import { memoryStore } from '$lib/stores/memory.store.svelte';
|
import { memoryStore } from '$lib/stores/memory.store.svelte';
|
||||||
import { getAssetThumbnailUrl, memoryLaneTitle } from '$lib/utils';
|
import { getAssetThumbnailUrl, memoryLaneTitle } from '$lib/utils';
|
||||||
import { getAltText } from '$lib/utils/thumbnail-util';
|
import { getAltText } from '$lib/utils/thumbnail-util';
|
||||||
|
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||||
import { mdiChevronLeft, mdiChevronRight } from '@mdi/js';
|
import { mdiChevronLeft, mdiChevronRight } from '@mdi/js';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
@ -82,7 +83,7 @@
|
|||||||
<img
|
<img
|
||||||
class="h-full w-full rounded-xl object-cover"
|
class="h-full w-full rounded-xl object-cover"
|
||||||
src={getAssetThumbnailUrl(memory.assets[0].id)}
|
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"
|
draggable="false"
|
||||||
/>
|
/>
|
||||||
<p class="absolute bottom-2 left-4 z-10 text-lg text-white max-md:text-sm">
|
<p class="absolute bottom-2 left-4 z-10 text-lg text-white max-md:text-sm">
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
import { getAssetThumbnailUrl } from '$lib/utils';
|
import { getAssetThumbnailUrl } from '$lib/utils';
|
||||||
import { getAssetResolution, getFileSize } from '$lib/utils/asset-utils';
|
import { getAssetResolution, getFileSize } from '$lib/utils/asset-utils';
|
||||||
import { getAltText } from '$lib/utils/thumbnail-util';
|
import { getAltText } from '$lib/utils/thumbnail-util';
|
||||||
|
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||||
import { type AssetResponseDto, getAllAlbums } from '@immich/sdk';
|
import { type AssetResponseDto, getAllAlbums } from '@immich/sdk';
|
||||||
import { mdiHeart, mdiImageMultipleOutline, mdiMagnifyPlus } from '@mdi/js';
|
import { mdiHeart, mdiImageMultipleOutline, mdiMagnifyPlus } from '@mdi/js';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
@ -36,7 +37,7 @@
|
|||||||
<!-- THUMBNAIL-->
|
<!-- THUMBNAIL-->
|
||||||
<img
|
<img
|
||||||
src={getAssetThumbnailUrl(asset.id)}
|
src={getAssetThumbnailUrl(asset.id)}
|
||||||
alt={$getAltText(asset)}
|
alt={$getAltText(toTimelineAsset(asset))}
|
||||||
title={assetData}
|
title={assetData}
|
||||||
class="h-60 object-cover rounded-t-xl w-full"
|
class="h-60 object-cover rounded-t-xl w-full"
|
||||||
draggable="false"
|
draggable="false"
|
||||||
|
@ -15,7 +15,6 @@ import {
|
|||||||
getTimeBucket,
|
getTimeBucket,
|
||||||
getTimeBuckets,
|
getTimeBuckets,
|
||||||
TimeBucketSize,
|
TimeBucketSize,
|
||||||
type AssetResponseDto,
|
|
||||||
type AssetStackResponseDto,
|
type AssetStackResponseDto,
|
||||||
} from '@immich/sdk';
|
} from '@immich/sdk';
|
||||||
import { clamp, debounce, isEqual, throttle } from 'lodash-es';
|
import { clamp, debounce, isEqual, throttle } from 'lodash-es';
|
||||||
@ -85,6 +84,11 @@ export type TimelineAsset = {
|
|||||||
duration: string | null;
|
duration: string | null;
|
||||||
projectionType: string | null;
|
projectionType: string | null;
|
||||||
livePhotoVideoId: string | null;
|
livePhotoVideoId: string | null;
|
||||||
|
text: {
|
||||||
|
city: string | null;
|
||||||
|
country: string | null;
|
||||||
|
people: string[];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
class IntersectingAsset {
|
class IntersectingAsset {
|
||||||
// --- public ---
|
// --- public ---
|
||||||
@ -1089,7 +1093,7 @@ export class AssetStore {
|
|||||||
);
|
);
|
||||||
if (bucketResponse) {
|
if (bucketResponse) {
|
||||||
if (this.#options.timelineAlbumId) {
|
if (this.#options.timelineAlbumId) {
|
||||||
const bucketAssets = await getTimeBucket(
|
const albumAssets = await getTimeBucket(
|
||||||
{
|
{
|
||||||
albumId: this.#options.timelineAlbumId,
|
albumId: this.#options.timelineAlbumId,
|
||||||
timeBucket: bucketDate,
|
timeBucket: bucketDate,
|
||||||
@ -1098,7 +1102,7 @@ export class AssetStore {
|
|||||||
},
|
},
|
||||||
{ signal },
|
{ signal },
|
||||||
);
|
);
|
||||||
for (const { id } of bucketAssets) {
|
for (const { id } of albumAssets) {
|
||||||
this.albumAssets.add(id);
|
this.albumAssets.add(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import type { TimelineAsset } from '$lib/stores/assets-store.svelte';
|
import type { TimelineAsset } from '$lib/stores/assets-store.svelte';
|
||||||
import { AssetTypeEnum, type AssetResponseDto } from '@immich/sdk';
|
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import { derived } from 'svelte/store';
|
import { derived } from 'svelte/store';
|
||||||
import { fromLocalDateTime } from './timeline-util';
|
import { fromLocalDateTime } from './timeline-util';
|
||||||
@ -44,24 +43,18 @@ export const getAltTextForTimelineAsset = (_: TimelineAsset) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getAltText = derived(t, ($t) => {
|
export const getAltText = derived(t, ($t) => {
|
||||||
return (asset: AssetResponseDto | null | undefined) => {
|
return (asset: TimelineAsset) => {
|
||||||
if (!asset) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
if (asset.exifInfo?.description) {
|
|
||||||
return asset.exifInfo.description;
|
|
||||||
}
|
|
||||||
|
|
||||||
const date = fromLocalDateTime(asset.localDateTime).toLocaleString({ dateStyle: 'long' });
|
const date = fromLocalDateTime(asset.localDateTime).toLocaleString({ dateStyle: 'long' });
|
||||||
const hasPlace = !!asset.exifInfo?.city && !!asset.exifInfo?.country;
|
const { city, country, people: names } = asset.text;
|
||||||
const names = asset.people?.filter((p) => p.name).map((p) => p.name) ?? [];
|
const hasPlace = city && country;
|
||||||
|
|
||||||
const peopleCount = names.length;
|
const peopleCount = names.length;
|
||||||
const isVideo = asset.type === AssetTypeEnum.Video;
|
const isVideo = asset.isVideo;
|
||||||
|
|
||||||
const values = {
|
const values = {
|
||||||
date,
|
date,
|
||||||
city: asset.exifInfo?.city,
|
city,
|
||||||
country: asset.exifInfo?.country,
|
country,
|
||||||
person1: names[0],
|
person1: names[0],
|
||||||
person2: names[1],
|
person2: names[1],
|
||||||
person3: names[2],
|
person3: names[2],
|
||||||
|
@ -114,6 +114,14 @@ export const toTimelineAsset = (unknownAsset: AssetResponseDto | TimelineAsset):
|
|||||||
const assetResponse = unknownAsset as AssetResponseDto;
|
const assetResponse = unknownAsset as AssetResponseDto;
|
||||||
const { width, height } = getAssetRatio(assetResponse);
|
const { width, height } = getAssetRatio(assetResponse);
|
||||||
const ratio = width / height;
|
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 {
|
return {
|
||||||
id: assetResponse.id,
|
id: assetResponse.id,
|
||||||
ownerId: assetResponse.ownerId,
|
ownerId: assetResponse.ownerId,
|
||||||
@ -129,6 +137,7 @@ export const toTimelineAsset = (unknownAsset: AssetResponseDto | TimelineAsset):
|
|||||||
duration: assetResponse.duration || null,
|
duration: assetResponse.duration || null,
|
||||||
projectionType: assetResponse.exifInfo?.projectionType || null,
|
projectionType: assetResponse.exifInfo?.projectionType || null,
|
||||||
livePhotoVideoId: assetResponse.livePhotoVideoId || null,
|
livePhotoVideoId: assetResponse.livePhotoVideoId || null,
|
||||||
|
text,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export const isTimelineAsset = (arg: AssetResponseDto | TimelineAsset): arg is TimelineAsset =>
|
export const isTimelineAsset = (arg: AssetResponseDto | TimelineAsset): arg is TimelineAsset =>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
|
|
||||||
function getBoolean(string: string | null, fallback: boolean) {
|
function getBoolean(string: string | null, fallback: boolean) {
|
||||||
if (string === null) {
|
if (string === null) {
|
||||||
return fallback;
|
return fallback;
|
||||||
@ -11,13 +12,11 @@ function getNumber(string: string | null, fallback: number) {
|
|||||||
}
|
}
|
||||||
return Number.parseInt(string);
|
return Number.parseInt(string);
|
||||||
}
|
}
|
||||||
|
|
||||||
const storage = browser
|
const storage = browser
|
||||||
? localStorage
|
? localStorage
|
||||||
: {
|
: {
|
||||||
getItem: () => null,
|
getItem: () => null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TUNABLES = {
|
export const TUNABLES = {
|
||||||
LAYOUT: {
|
LAYOUT: {
|
||||||
WASM: getBoolean(storage.getItem('LAYOUT.WASM'), false),
|
WASM: getBoolean(storage.getItem('LAYOUT.WASM'), false),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user