diff --git a/apps/docs/docs/widgets/immich-album-carousel/index.mdx b/apps/docs/docs/widgets/immich-album-carousel/index.mdx
index 59b1beeb9..703bd5a57 100644
--- a/apps/docs/docs/widgets/immich-album-carousel/index.mdx
+++ b/apps/docs/docs/widgets/immich-album-carousel/index.mdx
@@ -1,6 +1,6 @@
---
title: 'Immich Album Carousel'
-description: 'Shows a slideshow of your album pictures from Immich'
+description: 'Shows a slideshow of pictures from an Immich album or your full library'
hide_title: true
---
@@ -37,4 +37,3 @@ import screen from './img/screen.png';
### Configuration
-
diff --git a/apps/docs/docs/widgets/immich-album-carousel/index.ts b/apps/docs/docs/widgets/immich-album-carousel/index.ts
index f6c4b224c..7e956bae6 100644
--- a/apps/docs/docs/widgets/immich-album-carousel/index.ts
+++ b/apps/docs/docs/widgets/immich-album-carousel/index.ts
@@ -4,13 +4,13 @@ import { IconPhoto } from "@tabler/icons-react";
export const immichAlbumCarouselWidget: WidgetDefinition = {
icon: IconPhoto,
name: "Immich Album",
- description: "Shows a slideshow of your album pictures from Immich",
+ description: "Shows a slideshow of pictures from an Immich album or your full library",
path: "../../widgets/immich-album-carousel",
configuration: {
items: [
{
name: "Album",
- description: "Select the Immich album to show in the slideshow.",
+ description: "Select an Immich album, or leave empty to show random photos from your full library.",
values: { type: "string" },
defaultValue: "",
},
diff --git a/packages/api/src/router/widgets/immich.ts b/packages/api/src/router/widgets/immich.ts
index 7e8c54d49..aa917271f 100644
--- a/packages/api/src/router/widgets/immich.ts
+++ b/packages/api/src/router/widgets/immich.ts
@@ -25,7 +25,7 @@ export const immichRouter = createTRPCRouter({
.concat(createImmichIntegrationMiddleware("query"))
.input(
z.object({
- albumId: z.string(),
+ albumId: z.string().optional(),
}),
)
.query(async ({ ctx, input }) => {
diff --git a/packages/integrations/src/immich/immich-integration.ts b/packages/integrations/src/immich/immich-integration.ts
index 6274802a5..9496482d6 100644
--- a/packages/integrations/src/immich/immich-integration.ts
+++ b/packages/integrations/src/immich/immich-integration.ts
@@ -1,11 +1,14 @@
import {
AssetMediaSize,
+ AssetTypeEnum,
+ AssetVisibility,
getAlbumInfo,
getAllAlbums,
getMyUser,
getServerStatistics,
init,
searchAssets,
+ searchRandom,
searchUsers,
} from "@immich/sdk";
import type { AssetResponseDto, MetadataSearchDto } from "@immich/sdk";
@@ -43,6 +46,7 @@ export interface ImmichAsset {
}
const logger = createLogger({ module: "immich-integration" });
+const randomPhotoCount = 50;
export class ImmichIntegration extends Integration {
constructor(integration: IntegrationInput) {
@@ -63,38 +67,36 @@ export class ImmichIntegration extends Integration {
};
}
- public async getAlbumAsync(albumId: string): Promise {
+ public async getAlbumAsync(albumId?: string): Promise {
this.initClient();
const requestOptions = this.getRequestOptions();
+
+ if (!albumId) {
+ const assets = await searchRandom(
+ {
+ randomSearchDto: {
+ size: randomPhotoCount,
+ type: AssetTypeEnum.Image,
+ visibility: AssetVisibility.Timeline,
+ },
+ },
+ requestOptions,
+ );
+
+ return {
+ id: "",
+ albumName: "",
+ assets: await this.createAssetsAsync(assets),
+ };
+ }
+
const [album, albumAssets] = await Promise.all([
getAlbumInfo({ id: albumId }, requestOptions),
this.searchAlbumAssetsAsync(albumId),
]);
- const imageProxy = new ImageProxy();
return {
albumName: album.albumName,
- assets: await Promise.all(
- albumAssets.map(async (asset) => {
- const publicLink = await imageProxy.createImageAsync(
- // This is the URL generated by the SDK's viewAsset call. Preview images are
- // browser-compatible while avoiding the large original asset download.
- this.url(`/api/assets/${asset.id}/thumbnail`, { size: AssetMediaSize.Preview }).toString(),
- {
- "x-api-key": this.getSecretValue("apiKey"),
- },
- );
- return {
- id: asset.id,
- type: asset.type,
- thumbhash: asset.thumbhash,
- fileCreatedAt: asset.fileCreatedAt,
- fileModifiedAt: asset.fileModifiedAt,
- originalPath: asset.originalPath,
- updatedAt: asset.updatedAt,
- publicLink,
- };
- }),
- ),
+ assets: await this.createAssetsAsync(albumAssets),
id: album.id,
};
}
@@ -136,6 +138,32 @@ export class ImmichIntegration extends Integration {
return assets;
}
+ private async createAssetsAsync(assets: AssetResponseDto[]): Promise {
+ const imageProxy = new ImageProxy();
+ return await Promise.all(
+ assets.map(async (asset) => {
+ const publicLink = await imageProxy.createImageAsync(
+ // This is the URL generated by the SDK's viewAsset call. Preview images are
+ // browser-compatible while avoiding the large original asset download.
+ this.url(`/api/assets/${asset.id}/thumbnail`, { size: AssetMediaSize.Preview }).toString(),
+ {
+ "x-api-key": this.getSecretValue("apiKey"),
+ },
+ );
+ return {
+ id: asset.id,
+ type: asset.type,
+ thumbhash: asset.thumbhash,
+ fileCreatedAt: asset.fileCreatedAt,
+ fileModifiedAt: asset.fileModifiedAt,
+ originalPath: asset.originalPath,
+ updatedAt: asset.updatedAt,
+ publicLink,
+ };
+ }),
+ );
+ }
+
private getRequestOptions(fetchAsync = fetchWithTrustedCertificatesAsync) {
return {
// Undici and node types are not the same
diff --git a/packages/request-handler/src/immich.ts b/packages/request-handler/src/immich.ts
index 56fc89bd0..a6e2a996c 100644
--- a/packages/request-handler/src/immich.ts
+++ b/packages/request-handler/src/immich.ts
@@ -33,7 +33,7 @@ export const immichAlbumsRequestHandler = createIntegrationRequestHandler<
export const immichAlbumRequestHandler = createIntegrationRequestHandler<
ImmichAlbum,
IntegrationKindByCategory<"photoService">,
- { albumId: string }
+ { albumId?: string }
>({
async requestAsync(integration, input) {
const integrationInstance = await createIntegrationAsync(integration);
diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json
index 1e48e4486..0ce813d33 100644
--- a/packages/translation/src/lang/en.json
+++ b/packages/translation/src/lang/en.json
@@ -3009,7 +3009,7 @@
"option": {
"albumId": {
"label": "Album",
- "description": "Select the Immich album to show in the slideshow."
+ "description": "Select an Immich album, or leave empty to show random photos from your full library."
},
"rotationIntervalSeconds": {
"label": "Interval in seconds",
@@ -3024,9 +3024,8 @@
"description": "Show photos in a random order instead of always starting with the same album order."
}
},
- "description": "Slideshow of pictures in your Immich album",
- "noAlbumSelected": "No album selected, edit widget and choose an album",
- "noPhotos": "No photos in this album"
+ "description": "Slideshow of pictures from an Immich album or your full library",
+ "noPhotos": "No photos found"
},
"downloads": {
"name": "Download Client",
diff --git a/packages/widgets/src/immich/album-carousel/component.tsx b/packages/widgets/src/immich/album-carousel/component.tsx
index ee7085757..85323b647 100644
--- a/packages/widgets/src/immich/album-carousel/component.tsx
+++ b/packages/widgets/src/immich/album-carousel/component.tsx
@@ -21,9 +21,9 @@ export default function ImmichAlbumCarouselWidget({
const { data: album } = clientApi.widget.immich.getAlbum.useQuery(
{
integrationId: integrationIds[0] ?? "",
- albumId: options.albumId ?? "",
+ albumId: options.albumId || undefined,
},
- { enabled: Boolean(options.albumId) },
+ { enabled: integrationIds.length > 0 },
);
const photoAssets = useMemo(() => {
@@ -35,10 +35,6 @@ export default function ImmichAlbumCarouselWidget({
setCurrentPhotoIndex(0);
}, [photoAssets]);
- if (!options.albumId) {
- return ;
- }
-
if (!album) return ;
if (album.assets.length === 0) {
@@ -117,20 +113,6 @@ function Carousel({ assets, currentIndex, setCurrentIndex, rotationInterval, sho
);
}
-function NoAlbumSelected() {
- const t = useI18n();
- return (
-
-
-
-
- {t("widget.immich-albumCarousel.noAlbumSelected")}
-
-
-
- );
-}
-
function NoPhotosInAlbum() {
const t = useI18n();
return (