feat(web): revamp places (#12219)

* revamp places

* add english translations

* migrate places page and components to svelte 5

* fix lint

* chore: cleanup

---------

Co-authored-by: Jason Rasmussen <jason@rasm.me>
This commit is contained in:
Krassimir Valev
2025-02-06 21:54:01 +01:00
committed by GitHub
parent 45f7401513
commit 6aad9fae8e
8 changed files with 445 additions and 39 deletions
+26 -37
View File
@@ -1,13 +1,12 @@
<script lang="ts">
import Icon from '$lib/components/elements/icon.svelte';
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
import { AppRoute } from '$lib/constants';
import { mdiMapMarkerOff } from '@mdi/js';
import PlacesControls from '$lib/components/places-page/places-controls.svelte';
import type { PageData } from './$types';
import { getMetadataSearchQuery } from '$lib/utils/metadata-search';
import { AssetMediaSize, type AssetResponseDto } from '@immich/sdk';
import { type AssetResponseDto } from '@immich/sdk';
import { t } from 'svelte-i18n';
import { getAssetThumbnailUrl } from '$lib/utils';
import { locale } from '$lib/stores/preferences.store';
import Places from '$lib/components/places-page/places-list.svelte';
import { placesViewSettings } from '$lib/stores/preferences.store';
interface Props {
data: PageData;
@@ -21,43 +20,33 @@
};
};
let searchQuery = $state('');
let searchResultCount = $state(0);
let placesGroups: string[] = $state([]);
let places = $derived(data.items.filter((item): item is AssetWithCity => !!item.exifInfo?.city));
let hasPlaces = $derived(places.length > 0);
let countVisiblePlaces = $derived(searchQuery ? searchResultCount : places.length);
let innerHeight: number = $state(0);
</script>
<svelte:window bind:innerHeight />
<UserPageLayout title={$t('places')}>
{#if hasPlaces}
<div class="flex flex-row flex-wrap gap-4">
{#each places as item (item.id)}
{@const city = item.exifInfo.city}
<a class="relative" href="{AppRoute.SEARCH}?{getMetadataSearchQuery({ city })}" draggable="false">
<div
class="flex w-[calc((100vw-(72px+5rem))/2)] max-w-[156px] justify-center overflow-hidden rounded-xl brightness-75 filter"
>
<img
src={getAssetThumbnailUrl({ id: item.id, size: AssetMediaSize.Thumbnail })}
alt={city}
class="object-cover w-[156px] h-[156px]"
/>
</div>
<span
class="w-100 absolute bottom-2 w-full text-ellipsis px-1 text-center text-sm font-medium capitalize text-white backdrop-blur-[1px] hover:cursor-pointer"
>
{city}
</span>
</a>
{/each}
<UserPageLayout
title={$t('places')}
description={countVisiblePlaces === 0 && !searchQuery ? undefined : `(${countVisiblePlaces.toLocaleString($locale)})`}
>
{#snippet buttons()}
<div class="flex place-items-center gap-2">
<PlacesControls {placesGroups} bind:searchQuery />
</div>
{:else}
<div class="flex min-h-[calc(66vh_-_11rem)] w-full place-content-center items-center dark:text-white">
<div class="flex flex-col content-center items-center text-center">
<Icon path={mdiMapMarkerOff} size="3.5em" />
<p class="mt-5 text-3xl font-medium">{$t('no_places')}</p>
</div>
</div>
{/if}
{/snippet}
<Places
{places}
userSettings={$placesViewSettings}
{searchQuery}
bind:searchResultCount
bind:placesGroupIds={placesGroups}
/>
</UserPageLayout>