2025-01-02 17:56:47 -05:00
|
|
|
<script lang="ts">
|
2025-01-30 12:52:28 +01:00
|
|
|
import { createEventDispatcher, onMount } from 'svelte';
|
2025-01-30 13:02:49 +01:00
|
|
|
import { t } from 'svelte-i18n';
|
2025-01-30 12:30:11 +01:00
|
|
|
import ImmichLogo from '$lib/assets/immich.svg';
|
Rename Adventures to Locations (#696)
* Refactor user_id to user in adventures and related models, views, and components
- Updated all instances of user_id to user in the adventures app, including models, serializers, views, and frontend components.
- Adjusted queries and filters to reflect the new user field naming convention.
- Ensured consistency across the codebase for user identification in adventures, collections, notes, and transportation entities.
- Modified frontend components to align with the updated data structure, ensuring proper access control and rendering based on user ownership.
* Refactor adventure-related views and components to use "Location" terminology
- Updated GlobalSearchView to replace AdventureSerializer with LocationSerializer.
- Modified IcsCalendarGeneratorViewSet to use LocationSerializer instead of AdventureSerializer.
- Created new LocationImageViewSet for managing location images, including primary image toggling and image deletion.
- Introduced LocationViewSet for managing locations with enhanced filtering, sorting, and sharing capabilities.
- Updated ReverseGeocodeViewSet to utilize LocationSerializer.
- Added ActivityTypesView to retrieve distinct activity types from locations.
- Refactored user views to replace AdventureSerializer with LocationSerializer.
- Updated frontend components to reflect changes from "adventure" to "location", including AdventureCard, AdventureLink, AdventureModal, and others.
- Adjusted API endpoints in frontend routes to align with new location-based structure.
- Ensured all references to adventures are replaced with locations across the codebase.
* refactor: rename adventures to locations across the application
- Updated localization files to replace adventure-related terms with location-related terms.
- Refactored TypeScript types and variables from Adventure to Location in various routes and components.
- Adjusted UI elements and labels to reflect the change from adventures to locations.
- Ensured all references to adventures in the codebase are consistent with the new location terminology.
* Refactor code structure for improved readability and maintainability
* feat: Implement location details page with server-side loading and deletion functionality
- Added +page.server.ts to handle server-side loading of additional location info.
- Created +page.svelte for displaying location details, including images, visits, and maps.
- Integrated GPX file handling and rendering on the map.
- Updated map route to link to locations instead of adventures.
- Refactored profile and search routes to use LocationCard instead of AdventureCard.
* docs: Update terminology from "Adventure" to "Location" and enhance project overview
* docs: Clarify collection examples in usage documentation
* feat: Enable credentials for GPX file fetch and add CORS_ALLOW_CREDENTIALS setting
* Refactor adventure references to locations across the backend and frontend
- Updated CategoryViewSet to reflect location context instead of adventures.
- Modified ChecklistViewSet to include locations in retrieval logic.
- Changed GlobalSearchView to search for locations instead of adventures.
- Adjusted IcsCalendarGeneratorViewSet to handle locations instead of adventures.
- Refactored LocationImageViewSet to remove unused import.
- Updated LocationViewSet to clarify public access for locations.
- Changed LodgingViewSet to reference locations instead of adventures.
- Modified NoteViewSet to prevent listing all locations.
- Updated RecommendationsViewSet to handle locations in parsing and response.
- Adjusted ReverseGeocodeViewSet to search through user locations.
- Updated StatsViewSet to count locations instead of adventures.
- Changed TagsView to reflect activity types for locations.
- Updated TransportationViewSet to reference locations instead of adventures.
- Added new translations for search results related to locations in multiple languages.
- Updated dashboard and profile pages to reflect location counts instead of adventure counts.
- Adjusted search routes to handle locations instead of adventures.
* Update banner image
* style: Update stats component background and border for improved visibility
* refactor: Rename AdventureCard and AdventureModal to LocationCard and LocationModal for consistency
2025-06-25 11:49:34 -04:00
|
|
|
import type { Location, ImmichAlbum } from '$lib/types';
|
2025-01-30 12:30:11 +01:00
|
|
|
import { debounce } from '$lib';
|
|
|
|
|
2025-01-30 12:46:03 +01:00
|
|
|
let immichImages: any[] = [];
|
2025-01-02 17:56:47 -05:00
|
|
|
let immichSearchValue: string = '';
|
2025-01-30 12:52:28 +01:00
|
|
|
let searchCategory: 'search' | 'date' | 'album' = 'date';
|
2025-01-02 17:56:47 -05:00
|
|
|
let immichError: string = '';
|
2025-01-30 12:18:35 +01:00
|
|
|
let immichNextURL: string = '';
|
2025-01-30 13:02:49 +01:00
|
|
|
let loading = false;
|
2025-01-02 17:56:47 -05:00
|
|
|
|
Rename Adventures to Locations (#696)
* Refactor user_id to user in adventures and related models, views, and components
- Updated all instances of user_id to user in the adventures app, including models, serializers, views, and frontend components.
- Adjusted queries and filters to reflect the new user field naming convention.
- Ensured consistency across the codebase for user identification in adventures, collections, notes, and transportation entities.
- Modified frontend components to align with the updated data structure, ensuring proper access control and rendering based on user ownership.
* Refactor adventure-related views and components to use "Location" terminology
- Updated GlobalSearchView to replace AdventureSerializer with LocationSerializer.
- Modified IcsCalendarGeneratorViewSet to use LocationSerializer instead of AdventureSerializer.
- Created new LocationImageViewSet for managing location images, including primary image toggling and image deletion.
- Introduced LocationViewSet for managing locations with enhanced filtering, sorting, and sharing capabilities.
- Updated ReverseGeocodeViewSet to utilize LocationSerializer.
- Added ActivityTypesView to retrieve distinct activity types from locations.
- Refactored user views to replace AdventureSerializer with LocationSerializer.
- Updated frontend components to reflect changes from "adventure" to "location", including AdventureCard, AdventureLink, AdventureModal, and others.
- Adjusted API endpoints in frontend routes to align with new location-based structure.
- Ensured all references to adventures are replaced with locations across the codebase.
* refactor: rename adventures to locations across the application
- Updated localization files to replace adventure-related terms with location-related terms.
- Refactored TypeScript types and variables from Adventure to Location in various routes and components.
- Adjusted UI elements and labels to reflect the change from adventures to locations.
- Ensured all references to adventures in the codebase are consistent with the new location terminology.
* Refactor code structure for improved readability and maintainability
* feat: Implement location details page with server-side loading and deletion functionality
- Added +page.server.ts to handle server-side loading of additional location info.
- Created +page.svelte for displaying location details, including images, visits, and maps.
- Integrated GPX file handling and rendering on the map.
- Updated map route to link to locations instead of adventures.
- Refactored profile and search routes to use LocationCard instead of AdventureCard.
* docs: Update terminology from "Adventure" to "Location" and enhance project overview
* docs: Clarify collection examples in usage documentation
* feat: Enable credentials for GPX file fetch and add CORS_ALLOW_CREDENTIALS setting
* Refactor adventure references to locations across the backend and frontend
- Updated CategoryViewSet to reflect location context instead of adventures.
- Modified ChecklistViewSet to include locations in retrieval logic.
- Changed GlobalSearchView to search for locations instead of adventures.
- Adjusted IcsCalendarGeneratorViewSet to handle locations instead of adventures.
- Refactored LocationImageViewSet to remove unused import.
- Updated LocationViewSet to clarify public access for locations.
- Changed LodgingViewSet to reference locations instead of adventures.
- Modified NoteViewSet to prevent listing all locations.
- Updated RecommendationsViewSet to handle locations in parsing and response.
- Adjusted ReverseGeocodeViewSet to search through user locations.
- Updated StatsViewSet to count locations instead of adventures.
- Changed TagsView to reflect activity types for locations.
- Updated TransportationViewSet to reference locations instead of adventures.
- Added new translations for search results related to locations in multiple languages.
- Updated dashboard and profile pages to reflect location counts instead of adventure counts.
- Adjusted search routes to handle locations instead of adventures.
* Update banner image
* style: Update stats component background and border for improved visibility
* refactor: Rename AdventureCard and AdventureModal to LocationCard and LocationModal for consistency
2025-06-25 11:49:34 -04:00
|
|
|
export let adventure: Location | null = null;
|
2025-06-01 19:55:12 -04:00
|
|
|
export let copyImmichLocally: boolean = false;
|
2025-02-02 10:36:47 -05:00
|
|
|
|
2025-01-02 17:56:47 -05:00
|
|
|
const dispatch = createEventDispatcher();
|
|
|
|
|
|
|
|
let albums: ImmichAlbum[] = [];
|
|
|
|
let currentAlbum: string = '';
|
2025-01-30 12:30:11 +01:00
|
|
|
|
2025-02-02 10:36:47 -05:00
|
|
|
let selectedDate: string =
|
Rename Adventures to Locations (#696)
* Refactor user_id to user in adventures and related models, views, and components
- Updated all instances of user_id to user in the adventures app, including models, serializers, views, and frontend components.
- Adjusted queries and filters to reflect the new user field naming convention.
- Ensured consistency across the codebase for user identification in adventures, collections, notes, and transportation entities.
- Modified frontend components to align with the updated data structure, ensuring proper access control and rendering based on user ownership.
* Refactor adventure-related views and components to use "Location" terminology
- Updated GlobalSearchView to replace AdventureSerializer with LocationSerializer.
- Modified IcsCalendarGeneratorViewSet to use LocationSerializer instead of AdventureSerializer.
- Created new LocationImageViewSet for managing location images, including primary image toggling and image deletion.
- Introduced LocationViewSet for managing locations with enhanced filtering, sorting, and sharing capabilities.
- Updated ReverseGeocodeViewSet to utilize LocationSerializer.
- Added ActivityTypesView to retrieve distinct activity types from locations.
- Refactored user views to replace AdventureSerializer with LocationSerializer.
- Updated frontend components to reflect changes from "adventure" to "location", including AdventureCard, AdventureLink, AdventureModal, and others.
- Adjusted API endpoints in frontend routes to align with new location-based structure.
- Ensured all references to adventures are replaced with locations across the codebase.
* refactor: rename adventures to locations across the application
- Updated localization files to replace adventure-related terms with location-related terms.
- Refactored TypeScript types and variables from Adventure to Location in various routes and components.
- Adjusted UI elements and labels to reflect the change from adventures to locations.
- Ensured all references to adventures in the codebase are consistent with the new location terminology.
* Refactor code structure for improved readability and maintainability
* feat: Implement location details page with server-side loading and deletion functionality
- Added +page.server.ts to handle server-side loading of additional location info.
- Created +page.svelte for displaying location details, including images, visits, and maps.
- Integrated GPX file handling and rendering on the map.
- Updated map route to link to locations instead of adventures.
- Refactored profile and search routes to use LocationCard instead of AdventureCard.
* docs: Update terminology from "Adventure" to "Location" and enhance project overview
* docs: Clarify collection examples in usage documentation
* feat: Enable credentials for GPX file fetch and add CORS_ALLOW_CREDENTIALS setting
* Refactor adventure references to locations across the backend and frontend
- Updated CategoryViewSet to reflect location context instead of adventures.
- Modified ChecklistViewSet to include locations in retrieval logic.
- Changed GlobalSearchView to search for locations instead of adventures.
- Adjusted IcsCalendarGeneratorViewSet to handle locations instead of adventures.
- Refactored LocationImageViewSet to remove unused import.
- Updated LocationViewSet to clarify public access for locations.
- Changed LodgingViewSet to reference locations instead of adventures.
- Modified NoteViewSet to prevent listing all locations.
- Updated RecommendationsViewSet to handle locations in parsing and response.
- Adjusted ReverseGeocodeViewSet to search through user locations.
- Updated StatsViewSet to count locations instead of adventures.
- Changed TagsView to reflect activity types for locations.
- Updated TransportationViewSet to reference locations instead of adventures.
- Added new translations for search results related to locations in multiple languages.
- Updated dashboard and profile pages to reflect location counts instead of adventure counts.
- Adjusted search routes to handle locations instead of adventures.
* Update banner image
* style: Update stats component background and border for improved visibility
* refactor: Rename AdventureCard and AdventureModal to LocationCard and LocationModal for consistency
2025-06-25 11:49:34 -04:00
|
|
|
(adventure as Location | null)?.visits
|
2025-02-02 10:36:47 -05:00
|
|
|
.map((v) => new Date(v.end_date || v.start_date))
|
|
|
|
.sort((a, b) => +b - +a)[0]
|
|
|
|
?.toISOString()
|
|
|
|
?.split('T')[0] || '';
|
2025-01-30 12:30:11 +01:00
|
|
|
if (!selectedDate) {
|
|
|
|
selectedDate = new Date().toISOString().split('T')[0];
|
|
|
|
}
|
2025-01-02 17:56:47 -05:00
|
|
|
|
|
|
|
$: {
|
2025-06-03 19:16:44 -04:00
|
|
|
if (searchCategory === 'album' && currentAlbum) {
|
2025-01-30 13:02:49 +01:00
|
|
|
immichImages = [];
|
2025-01-02 17:56:47 -05:00
|
|
|
fetchAlbumAssets(currentAlbum);
|
2025-01-30 12:18:35 +01:00
|
|
|
} else if (searchCategory === 'date' && selectedDate) {
|
2025-06-03 19:16:44 -04:00
|
|
|
// Clear album selection when switching to date mode
|
|
|
|
if (currentAlbum) {
|
|
|
|
currentAlbum = '';
|
|
|
|
}
|
2025-01-30 13:02:49 +01:00
|
|
|
searchImmich();
|
2025-06-03 19:16:44 -04:00
|
|
|
} else if (searchCategory === 'search') {
|
|
|
|
// Clear album selection when switching to search mode
|
|
|
|
if (currentAlbum) {
|
|
|
|
currentAlbum = '';
|
|
|
|
}
|
|
|
|
// Search will be triggered by the form submission or debounced search
|
2025-01-30 13:02:49 +01:00
|
|
|
}
|
2025-01-30 12:18:35 +01:00
|
|
|
}
|
2025-02-02 10:36:47 -05:00
|
|
|
|
2025-01-30 12:18:35 +01:00
|
|
|
async function loadMoreImmich() {
|
2025-01-30 13:02:49 +01:00
|
|
|
// The next URL returned by our API is a absolute url to API, but we need to use the relative path, to use the frontend api proxy.
|
|
|
|
const url = new URL(immichNextURL);
|
|
|
|
immichNextURL = url.pathname + url.search;
|
|
|
|
return fetchAssets(immichNextURL, true);
|
2025-01-02 17:56:47 -05:00
|
|
|
}
|
|
|
|
|
2025-06-01 19:55:12 -04:00
|
|
|
async function saveImmichRemoteUrl(imageId: string) {
|
|
|
|
if (!adventure) {
|
|
|
|
console.error('No adventure provided to save the image URL');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let res = await fetch('/api/images', {
|
|
|
|
method: 'POST',
|
|
|
|
headers: {
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
},
|
|
|
|
body: JSON.stringify({
|
|
|
|
immich_id: imageId,
|
|
|
|
adventure: adventure.id
|
|
|
|
})
|
|
|
|
});
|
|
|
|
if (res.ok) {
|
|
|
|
let data = await res.json();
|
|
|
|
if (!data.image) {
|
|
|
|
console.error('No image data returned from the server');
|
|
|
|
immichError = $t('immich.error_saving_image');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
dispatch('remoteImmichSaved', data);
|
|
|
|
} else {
|
|
|
|
let errorData = await res.json();
|
|
|
|
console.error('Error saving image URL:', errorData);
|
|
|
|
immichError = $t(errorData.message || 'immich.error_saving_image');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-30 13:02:49 +01:00
|
|
|
async function fetchAssets(url: string, usingNext = false) {
|
|
|
|
loading = true;
|
|
|
|
try {
|
|
|
|
let res = await fetch(url);
|
|
|
|
immichError = '';
|
|
|
|
if (!res.ok) {
|
|
|
|
let data = await res.json();
|
|
|
|
let errorMessage = data.message;
|
|
|
|
console.error('Error in handling fetchAsstes', errorMessage);
|
|
|
|
immichError = $t(data.code);
|
|
|
|
} else {
|
|
|
|
let data = await res.json();
|
|
|
|
if (data.results && data.results.length > 0) {
|
|
|
|
if (usingNext) {
|
|
|
|
immichImages = [...immichImages, ...data.results];
|
|
|
|
} else {
|
|
|
|
immichImages = data.results;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
immichError = $t('immich.no_items_found');
|
|
|
|
}
|
|
|
|
|
|
|
|
immichNextURL = data.next || '';
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
loading = false;
|
|
|
|
}
|
|
|
|
}
|
2025-01-30 12:18:35 +01:00
|
|
|
|
2025-02-02 10:36:47 -05:00
|
|
|
async function fetchAlbumAssets(album_id: string) {
|
2025-01-30 13:02:49 +01:00
|
|
|
return fetchAssets(`/api/integrations/immich/albums/${album_id}`);
|
2025-01-02 17:56:47 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
onMount(async () => {
|
|
|
|
let res = await fetch('/api/integrations/immich/albums');
|
|
|
|
if (res.ok) {
|
|
|
|
let data = await res.json();
|
|
|
|
albums = data;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2025-01-30 13:02:49 +01:00
|
|
|
function buildQueryParams() {
|
|
|
|
let params = new URLSearchParams();
|
|
|
|
if (immichSearchValue && searchCategory === 'search') {
|
|
|
|
params.append('query', immichSearchValue);
|
|
|
|
} else if (selectedDate && searchCategory === 'date') {
|
|
|
|
params.append('date', selectedDate);
|
2025-02-02 10:36:47 -05:00
|
|
|
}
|
2025-01-30 13:02:49 +01:00
|
|
|
return params.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
const searchImmich = debounce(() => {
|
|
|
|
_searchImmich();
|
|
|
|
}, 500); // Debounce the search function to avoid multiple requests on every key press
|
2025-01-30 12:18:35 +01:00
|
|
|
|
|
|
|
async function _searchImmich() {
|
2025-02-02 10:36:47 -05:00
|
|
|
immichImages = [];
|
2025-01-30 13:02:49 +01:00
|
|
|
return fetchAssets(`/api/integrations/immich/search/?${buildQueryParams()}`);
|
2025-01-02 17:56:47 -05:00
|
|
|
}
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<div class="mb-4">
|
|
|
|
<label for="immich" class="block font-medium mb-2">
|
|
|
|
{$t('immich.immich')}
|
|
|
|
<img src={ImmichLogo} alt="Immich Logo" class="h-6 w-6 inline-block -mt-1" />
|
|
|
|
</label>
|
2025-01-02 18:34:13 -05:00
|
|
|
<div class="mt-4">
|
|
|
|
<div class="join">
|
2025-01-02 17:56:47 -05:00
|
|
|
<input
|
2025-01-02 18:34:13 -05:00
|
|
|
on:click={() => (currentAlbum = '')}
|
|
|
|
type="radio"
|
|
|
|
class="join-item btn"
|
2025-01-30 12:18:35 +01:00
|
|
|
bind:group={searchCategory}
|
2025-01-02 18:34:13 -05:00
|
|
|
value="search"
|
|
|
|
aria-label="Search"
|
2025-01-02 17:56:47 -05:00
|
|
|
/>
|
2025-01-30 13:02:49 +01:00
|
|
|
<input
|
2025-01-30 12:18:35 +01:00
|
|
|
type="radio"
|
|
|
|
class="join-item btn"
|
|
|
|
bind:group={searchCategory}
|
|
|
|
value="date"
|
|
|
|
aria-label="Show by date"
|
|
|
|
/>
|
2025-01-02 18:34:13 -05:00
|
|
|
<input
|
|
|
|
type="radio"
|
|
|
|
class="join-item btn"
|
2025-01-30 12:18:35 +01:00
|
|
|
bind:group={searchCategory}
|
|
|
|
value="album"
|
2025-01-02 18:34:13 -05:00
|
|
|
aria-label="Select Album"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div>
|
2025-01-30 12:18:35 +01:00
|
|
|
{#if searchCategory === 'search'}
|
2025-01-02 18:34:13 -05:00
|
|
|
<form on:submit|preventDefault={searchImmich}>
|
|
|
|
<input
|
|
|
|
type="text"
|
|
|
|
placeholder="Type here"
|
|
|
|
bind:value={immichSearchValue}
|
|
|
|
class="input input-bordered w-full max-w-xs"
|
|
|
|
/>
|
|
|
|
<button type="submit" class="btn btn-neutral mt-2">Search</button>
|
|
|
|
</form>
|
2025-01-30 13:02:49 +01:00
|
|
|
{:else if searchCategory === 'date'}
|
|
|
|
<input
|
|
|
|
type="date"
|
|
|
|
bind:value={selectedDate}
|
|
|
|
class="input input-bordered w-full max-w-xs mt-2"
|
|
|
|
/>
|
2025-01-30 12:18:35 +01:00
|
|
|
{:else if searchCategory === 'album'}
|
2025-01-02 18:34:13 -05:00
|
|
|
<select class="select select-bordered w-full max-w-xs mt-2" bind:value={currentAlbum}>
|
|
|
|
<option value="" disabled selected>Select an Album</option>
|
|
|
|
{#each albums as album}
|
|
|
|
<option value={album.id}>{album.albumName}</option>
|
|
|
|
{/each}
|
|
|
|
</select>
|
|
|
|
{/if}
|
|
|
|
</div>
|
2025-01-02 17:56:47 -05:00
|
|
|
</div>
|
|
|
|
|
|
|
|
<p class="text-red-500">{immichError}</p>
|
|
|
|
<div class="flex flex-wrap gap-4 mr-4 mt-2">
|
2025-01-30 13:02:49 +01:00
|
|
|
{#if loading}
|
2025-02-02 10:36:47 -05:00
|
|
|
<div
|
|
|
|
class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-[100] w-24 h-24"
|
|
|
|
>
|
|
|
|
<span class="loading loading-spinner w-24 h-24"></span>
|
|
|
|
</div>
|
2025-01-30 13:02:49 +01:00
|
|
|
{/if}
|
2025-01-30 12:46:03 +01:00
|
|
|
|
2025-01-02 17:56:47 -05:00
|
|
|
{#each immichImages as image}
|
2025-01-30 12:46:03 +01:00
|
|
|
<div class="flex flex-col items-center gap-2" class:blur-sm={loading}>
|
2025-01-02 17:56:47 -05:00
|
|
|
<!-- svelte-ignore a11y-img-redundant-alt -->
|
|
|
|
<img
|
2025-06-02 21:25:07 -04:00
|
|
|
src={`${image.image_url}`}
|
2025-01-02 17:56:47 -05:00
|
|
|
alt="Image from Immich"
|
|
|
|
class="h-24 w-24 object-cover rounded-md"
|
|
|
|
/>
|
2025-01-30 13:02:49 +01:00
|
|
|
<h4>
|
2025-02-02 10:36:47 -05:00
|
|
|
{image.fileCreatedAt?.split('T')[0] || 'Unknown'}
|
2025-01-30 13:02:49 +01:00
|
|
|
</h4>
|
2025-01-02 17:56:47 -05:00
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
class="btn btn-sm btn-primary"
|
|
|
|
on:click={() => {
|
|
|
|
let currentDomain = window.location.origin;
|
|
|
|
let fullUrl = `${currentDomain}/immich/${image.id}`;
|
2025-06-01 19:55:12 -04:00
|
|
|
if (copyImmichLocally) {
|
|
|
|
dispatch('fetchImage', fullUrl);
|
|
|
|
} else {
|
|
|
|
saveImmichRemoteUrl(image.id);
|
|
|
|
}
|
2025-01-02 17:56:47 -05:00
|
|
|
}}
|
|
|
|
>
|
|
|
|
{$t('adventures.upload_image')}
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
{/each}
|
2025-01-30 12:18:35 +01:00
|
|
|
{#if immichNextURL}
|
2025-01-02 17:56:47 -05:00
|
|
|
<button class="btn btn-neutral" on:click={loadMoreImmich}>{$t('immich.load_more')}</button>
|
|
|
|
{/if}
|
|
|
|
</div>
|
|
|
|
</div>
|