1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-07-25 15:59:38 +02:00

Merge pull request #478 from Thiesjoo/immich-improvements

Immich image selection improvements
This commit is contained in:
Sean Morley 2025-02-01 15:28:15 -05:00 committed by GitHub
commit bdb17a3177
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 222 additions and 133 deletions

View file

@ -1257,6 +1257,7 @@ it would also work to just use on:click on the MapLibre component itself. -->
{#if immichIntegration}
<ImmichSelect
adventure={adventure}
on:fetchImage={(e) => {
url = e.detail;
fetchImage();

View file

@ -1,32 +1,77 @@
<script lang="ts">
let immichSearchValue: string = '';
let searchOrSelect: string = 'search';
let immichError: string = '';
let immichNext: string = '';
let immichPage: number = 1;
import { createEventDispatcher, onMount } from 'svelte';
import { t } from 'svelte-i18n';
import ImmichLogo from '$lib/assets/immich.svg';
import type { Adventure, ImmichAlbum } from '$lib/types';
import { debounce } from '$lib';
let immichImages: any[] = [];
let immichSearchValue: string = '';
let searchCategory: 'search' | 'date' | 'album' = 'date';
let immichError: string = '';
let immichNextURL: string = '';
let loading = false;
export let adventure: Adventure | null = null;
const dispatch = createEventDispatcher();
let albums: ImmichAlbum[] = [];
let currentAlbum: string = '';
let selectedDate: string = (adventure as Adventure | null)?.visits.map(v => new Date(v.end_date || v.start_date)).sort((a,b) => +b - +a)[0]?.toISOString()?.split('T')[0] || '';
if (!selectedDate) {
selectedDate = new Date().toISOString().split('T')[0];
}
$: {
if (currentAlbum) {
immichImages = [];
fetchAlbumAssets(currentAlbum);
} else {
immichImages = [];
} else if (searchCategory === 'date' && selectedDate) {
searchImmich();
}
}
async function loadMoreImmich() {
// 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);
}
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;
}
}
async function fetchAlbumAssets(album_id: string) {
let res = await fetch(`/api/integrations/immich/albums/${album_id}`);
if (res.ok) {
let data = await res.json();
immichNext = '';
immichImages = data;
}
async function fetchAlbumAssets(album_id: string,) {
return fetchAssets(`/api/integrations/immich/albums/${album_id}`);
}
onMount(async () => {
@ -37,67 +82,25 @@
}
});
let immichImages: any[] = [];
import { t } from 'svelte-i18n';
import ImmichLogo from '$lib/assets/immich.svg';
import type { ImmichAlbum } from '$lib/types';
async function searchImmich() {
let res = await fetch(`/api/integrations/immich/search/?query=${immichSearchValue}`);
if (!res.ok) {
let data = await res.json();
let errorMessage = data.message;
console.log(errorMessage);
immichError = $t(data.code);
} else {
let data = await res.json();
console.log(data);
immichError = '';
if (data.results && data.results.length > 0) {
immichImages = data.results;
} else {
immichError = $t('immich.no_items_found');
}
if (data.next) {
immichNext =
'/api/integrations/immich/search?query=' +
immichSearchValue +
'&page=' +
(immichPage + 1);
} else {
immichNext = '';
}
}
function buildQueryParams() {
let params = new URLSearchParams();
if (immichSearchValue && searchCategory === 'search') {
params.append('query', immichSearchValue);
} else if (selectedDate && searchCategory === 'date') {
params.append('date', selectedDate);
}
return params.toString();
}
async function loadMoreImmich() {
let res = await fetch(immichNext);
if (!res.ok) {
let data = await res.json();
let errorMessage = data.message;
console.log(errorMessage);
immichError = $t(data.code);
} else {
let data = await res.json();
console.log(data);
immichError = '';
if (data.results && data.results.length > 0) {
immichImages = [...immichImages, ...data.results];
} else {
immichError = $t('immich.no_items_found');
}
if (data.next) {
immichNext =
'/api/integrations/immich/search?query=' +
immichSearchValue +
'&page=' +
(immichPage + 1);
immichPage++;
} else {
immichNext = '';
}
}
const searchImmich = debounce(() => {
_searchImmich();
}, 500); // Debounce the search function to avoid multiple requests on every key press
async function _searchImmich() {
return fetchAssets(`/api/integrations/immich/search/?${buildQueryParams()}`);
}
</script>
<div class="mb-4">
@ -111,20 +114,27 @@
on:click={() => (currentAlbum = '')}
type="radio"
class="join-item btn"
bind:group={searchOrSelect}
bind:group={searchCategory}
value="search"
aria-label="Search"
/>
<input
type="radio"
class="join-item btn"
bind:group={searchOrSelect}
value="select"
bind:group={searchCategory}
value="date"
aria-label="Show by date"
/>
<input
type="radio"
class="join-item btn"
bind:group={searchCategory}
value="album"
aria-label="Select Album"
/>
</div>
<div>
{#if searchOrSelect === 'search'}
{#if searchCategory === 'search'}
<form on:submit|preventDefault={searchImmich}>
<input
type="text"
@ -134,7 +144,13 @@
/>
<button type="submit" class="btn btn-neutral mt-2">Search</button>
</form>
{:else}
{:else if searchCategory === 'date'}
<input
type="date"
bind:value={selectedDate}
class="input input-bordered w-full max-w-xs mt-2"
/>
{:else if searchCategory === 'album'}
<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}
@ -147,14 +163,23 @@
<p class="text-red-500">{immichError}</p>
<div class="flex flex-wrap gap-4 mr-4 mt-2">
{#if loading}
<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>
{/if}
{#each immichImages as image}
<div class="flex flex-col items-center gap-2">
<div class="flex flex-col items-center gap-2" class:blur-sm={loading}>
<!-- svelte-ignore a11y-img-redundant-alt -->
<img
src={`/immich/${image.id}`}
alt="Image from Immich"
class="h-24 w-24 object-cover rounded-md"
/>
<h4>
{image.fileCreatedAt?.split('T')[0] || "Unknown"}
</h4>
<button
type="button"
class="btn btn-sm btn-primary"
@ -168,7 +193,7 @@
</button>
</div>
{/each}
{#if immichNext}
{#if immichNextURL}
<button class="btn btn-neutral" on:click={loadMoreImmich}>{$t('immich.load_more')}</button>
{/if}
</div>

View file

@ -17,7 +17,8 @@
// Event listener for focusing input
function handleKeydown(event: KeyboardEvent) {
if (event.key === '/' && document.activeElement !== inputElement) {
// Ignore any keypresses in an input/textarea field, so we don't interfere with typing.
if (event.key === '/' && !["INPUT", "TEXTAREA"].includes((event.target as HTMLElement)?.tagName)) {
event.preventDefault(); // Prevent browser's search shortcut
if (inputElement) {
inputElement.focus();