mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-02 18:47:07 -05:00 
			
		
		
		
	feat(web): places page (#6669)
Add a place overview page, like the "People" page. This adds the same functionality as available on mobile.
This commit is contained in:
		
							parent
							
								
									8aef92affc
								
							
						
					
					
						commit
						77f11e3ae5
					
				@ -21,6 +21,7 @@ export enum AppRoute {
 | 
				
			|||||||
  ARCHIVE = '/archive',
 | 
					  ARCHIVE = '/archive',
 | 
				
			||||||
  FAVORITES = '/favorites',
 | 
					  FAVORITES = '/favorites',
 | 
				
			||||||
  PEOPLE = '/people',
 | 
					  PEOPLE = '/people',
 | 
				
			||||||
 | 
					  PLACES = '/places',
 | 
				
			||||||
  PHOTOS = '/photos',
 | 
					  PHOTOS = '/photos',
 | 
				
			||||||
  EXPLORE = '/explore',
 | 
					  EXPLORE = '/explore',
 | 
				
			||||||
  SHARING = '/sharing',
 | 
					  SHARING = '/sharing',
 | 
				
			||||||
 | 
				
			|||||||
@ -72,8 +72,13 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  {#if places.length > 0}
 | 
					  {#if places.length > 0}
 | 
				
			||||||
    <div class="mb-6 mt-2">
 | 
					    <div class="mb-6 mt-2">
 | 
				
			||||||
      <div>
 | 
					      <div class="flex justify-between">
 | 
				
			||||||
        <p class="mb-4 font-medium dark:text-immich-dark-fg">Places</p>
 | 
					        <p class="mb-4 font-medium dark:text-immich-dark-fg">Places</p>
 | 
				
			||||||
 | 
					        <a
 | 
				
			||||||
 | 
					          href={AppRoute.PLACES}
 | 
				
			||||||
 | 
					          class="pr-4 text-sm font-medium hover:text-immich-primary dark:text-immich-dark-fg dark:hover:text-immich-dark-primary"
 | 
				
			||||||
 | 
					          draggable="false">View All</a
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div class="flex flex-row flex-wrap gap-4">
 | 
					      <div class="flex flex-row flex-wrap gap-4">
 | 
				
			||||||
        {#each places as item (item.data.id)}
 | 
					        {#each places as item (item.data.id)}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										52
									
								
								web/src/routes/(user)/places/+page.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								web/src/routes/(user)/places/+page.svelte
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					  import type { SearchExploreResponseDto } from '@api';
 | 
				
			||||||
 | 
					  import type { PageData } from './$types';
 | 
				
			||||||
 | 
					  import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
 | 
				
			||||||
 | 
					  import { mdiMapMarkerOff } from '@mdi/js';
 | 
				
			||||||
 | 
					  import Icon from '$lib/components/elements/icon.svelte';
 | 
				
			||||||
 | 
					  import { AppRoute } from '$lib/constants';
 | 
				
			||||||
 | 
					  import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export let data: PageData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const CITY_FIELD = 'exifInfo.city';
 | 
				
			||||||
 | 
					  const getFieldItems = (items: SearchExploreResponseDto[]) => {
 | 
				
			||||||
 | 
					    const targetField = items.find((item) => item.fieldName === CITY_FIELD);
 | 
				
			||||||
 | 
					    return targetField?.items || [];
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  $: places = getFieldItems(data.items);
 | 
				
			||||||
 | 
					  $: hasPlaces = places.length > 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let innerHeight: number;
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<svelte:window bind:innerHeight />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<UserPageLayout title="Places">
 | 
				
			||||||
 | 
					  {#if hasPlaces}
 | 
				
			||||||
 | 
					    <div class="flex flex-row flex-wrap gap-4">
 | 
				
			||||||
 | 
					      {#each places as item (item.data.id)}
 | 
				
			||||||
 | 
					        <a class="relative" href="{AppRoute.SEARCH}?q={item.value}" draggable="false">
 | 
				
			||||||
 | 
					          <div
 | 
				
			||||||
 | 
					            class="flex w-[calc((100vw-(72px+5rem))/2)] max-w-[156px] justify-center overflow-hidden rounded-xl brightness-75 filter"
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
 | 
					            <Thumbnail thumbnailSize={156} asset={item.data} readonly />
 | 
				
			||||||
 | 
					          </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"
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
 | 
					            {item.value}
 | 
				
			||||||
 | 
					          </span>
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
 | 
					      {/each}
 | 
				
			||||||
 | 
					    </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">No places</p>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  {/if}
 | 
				
			||||||
 | 
					</UserPageLayout>
 | 
				
			||||||
							
								
								
									
										15
									
								
								web/src/routes/(user)/places/+page.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								web/src/routes/(user)/places/+page.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					import { authenticate } from '$lib/utils/auth';
 | 
				
			||||||
 | 
					import { api } from '@api';
 | 
				
			||||||
 | 
					import type { PageLoad } from './$types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const load = (async () => {
 | 
				
			||||||
 | 
					  await authenticate();
 | 
				
			||||||
 | 
					  const { data: items } = await api.searchApi.getExploreData();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    items,
 | 
				
			||||||
 | 
					    meta: {
 | 
				
			||||||
 | 
					      title: 'Places',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}) satisfies PageLoad;
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user