From f9baf3395e06efe960124937bcca74594d58b9e4 Mon Sep 17 00:00:00 2001 From: Maurice Date: Thu, 1 Oct 2026 14:19:51 +0200 Subject: [PATCH] feat(studio): add photos from Immich and Synology like the journey gallery (#2271) --- .../Journey/JourneyDetailPageGalleryView.tsx | 32 +++++---------- .../useConnectedPhotoProviders.test.tsx | 29 ++++++++++++++ .../Journey/useConnectedPhotoProviders.ts | 36 +++++++++++++++++ client/src/components/Studio/StudioShell.tsx | 17 ++++++++ .../src/components/Studio/StudioSidebar.tsx | 40 +++++++++++++++++-- .../journeyDetail/useProviderPhotoAdds.ts | 11 ++++- .../pages/journeyStudio/useJourneyStudio.ts | 34 ++++++++++++++++ .../tests/unit/studio/contentPhotos.test.tsx | 38 ++++++++++++++++++ shared/src/i18n/ar/journey.ts | 2 + shared/src/i18n/az/journey.ts | 4 ++ shared/src/i18n/br/journey.ts | 2 + shared/src/i18n/ca/journey.ts | 2 + shared/src/i18n/cs/journey.ts | 2 + shared/src/i18n/de/journey.ts | 2 + shared/src/i18n/en/journey.ts | 2 + shared/src/i18n/es/journey.ts | 2 + shared/src/i18n/et/journey.ts | 2 + shared/src/i18n/fr/journey.ts | 2 + shared/src/i18n/gr/journey.ts | 2 + shared/src/i18n/hu/journey.ts | 2 + shared/src/i18n/id/journey.ts | 2 + shared/src/i18n/it/journey.ts | 2 + shared/src/i18n/ja/journey.ts | 2 + shared/src/i18n/ko/journey.ts | 2 + shared/src/i18n/nl/journey.ts | 2 + shared/src/i18n/pl/journey.ts | 2 + shared/src/i18n/ru/journey.ts | 2 + shared/src/i18n/sk/journey.ts | 2 + shared/src/i18n/sv/journey.ts | 2 + shared/src/i18n/th/journey.ts | 2 + shared/src/i18n/tr/journey.ts | 2 + shared/src/i18n/uk/journey.ts | 2 + shared/src/i18n/vi/journey.ts | 2 + shared/src/i18n/zh-TW/journey.ts | 2 + shared/src/i18n/zh/journey.ts | 2 + 35 files changed, 265 insertions(+), 28 deletions(-) create mode 100644 client/src/components/Journey/useConnectedPhotoProviders.test.tsx create mode 100644 client/src/components/Journey/useConnectedPhotoProviders.ts diff --git a/client/src/components/Journey/JourneyDetailPageGalleryView.tsx b/client/src/components/Journey/JourneyDetailPageGalleryView.tsx index d66347f76..0c9b79d6f 100644 --- a/client/src/components/Journey/JourneyDetailPageGalleryView.tsx +++ b/client/src/components/Journey/JourneyDetailPageGalleryView.tsx @@ -4,12 +4,13 @@ import { normalizeImageFiles } from '../../utils/convertHeic' import { isVideoFile } from '../../utils/videoPoster' import { useJourneyStore } from '../../store/journeyStore' import { useTranslation } from '../../i18n' -import { journeyApi, addonsApi, memoriesApi } from '../../api/client' +import { journeyApi } from '../../api/client' import { useToast } from '../shared/Toast' import { getApiErrorMessage } from '../../types' import type { JourneyEntry, GalleryPhoto, JourneyTrip } from '../../store/journeyStore' import { photoUrl, posterlessVideo } from '../../pages/journeyDetail/JourneyDetailPage.helpers' import { ProviderPicker, type ProviderPhotoGroup } from './JourneyDetailPageProviderPicker' +import { useConnectedPhotoProviders } from './useConnectedPhotoProviders' import { ScrollTrigger } from './JourneyDetailPageScrollTrigger' import EmptyState from '../shared/EmptyState' @@ -22,7 +23,7 @@ export function GalleryView({ entries, gallery, journeyId, userId, trips, onPhot onPhotoClick: (photos: GalleryPhoto[], index: number) => void onRefresh: () => void /** What the picker's Add does: the host's useProviderPhotoAdds, shared with the phone screen. */ - onAddProviderPhotos: (journeyId: number, provider: string, groups: ProviderPhotoGroup[], entryId: number | null) => Promise + onAddProviderPhotos: (journeyId: number, provider: string, groups: ProviderPhotoGroup[], entryId: number | null) => Promise onRegisterUpload?: (fn: () => void) => void onRegisterProviders?: (providers: { id: string; name: string }[], browse: (provider: string) => void) => void }) { @@ -33,26 +34,13 @@ export function GalleryView({ entries, gallery, journeyId, userId, trips, onPhot const galleryUploading = galleryProgress !== null const toast = useToast() - // check which providers are enabled AND connected for the current user, then - // hand them up: the page header renders the provider buttons next to Upload + // The providers enabled AND connected for the current user, handed up: the page + // header renders the provider buttons next to Upload. + const connectedProviders = useConnectedPhotoProviders() useEffect(() => { - (async () => { - try { - const addonsData = await addonsApi.enabled() - const enabledProviders = (addonsData.addons || []).filter( - (a: any) => a.type === 'photo_provider' && a.enabled - ) - const connected: { id: string; name: string }[] = [] - for (const p of enabledProviders) { - try { - const status = await memoriesApi.status(p.id) - if (status.connected) connected.push({ id: p.id, name: p.name }) - } catch { } - } - onRegisterProviders?.(connected, browseProvider) - } catch { } - })() - }, []) + onRegisterProviders?.(connectedProviders, browseProvider) + // browseProvider only sets state; registering on the list alone is the old behaviour. + }, [connectedProviders]) const allPhotos = gallery // A long trip's gallery is hundreds of tiles, and rendering them all at once is @@ -174,7 +162,7 @@ export function GalleryView({ entries, gallery, journeyId, userId, trips, onPhot )} {/* Delete button */} )} + {canEdit && onBrowseProvider && providers.map(p => ( + + ))} {photos.map(p => (