浏览代码

Modify Album API endpoint to return a count attribute instead of a full assets array (#454)

* Change API to return assets count and change web behavior accordingly

* Refactor assets.length

* Explicitly declare type of assetCount so Dart SDK understand it

* Finished refactoring on mobile
Alex 2 年之前
父节点
当前提交
5c78f707fe

+ 2 - 2
mobile/lib/modules/album/ui/album_thumbnail_card.dart

@@ -61,13 +61,13 @@ class AlbumThumbnailCard extends StatelessWidget {
               mainAxisSize: MainAxisSize.min,
               mainAxisSize: MainAxisSize.min,
               children: [
               children: [
                 Text(
                 Text(
-                  album.assets.length == 1
+                  album.assetCount == 1
                       ? 'album_thumbnail_card_item'
                       ? 'album_thumbnail_card_item'
                       : 'album_thumbnail_card_items',
                       : 'album_thumbnail_card_items',
                   style: const TextStyle(
                   style: const TextStyle(
                     fontSize: 10,
                     fontSize: 10,
                   ),
                   ),
-                ).tr(args: ['${album.assets.length }']),
+                ).tr(args: ['${album.assetCount}']),
                 if (album.shared)
                 if (album.shared)
                   const Text(
                   const Text(
                     'album_thumbnail_card_shared',
                     'album_thumbnail_card_shared',

+ 1 - 1
mobile/lib/modules/album/views/album_viewer_page.dart

@@ -203,7 +203,7 @@ class AlbumViewerPage extends HookConsumerWidget {
                   assetList: albumInfo.assets,
                   assetList: albumInfo.assets,
                 );
                 );
               },
               },
-              childCount: albumInfo.assets.length,
+              childCount: albumInfo.assetCount,
             ),
             ),
           ),
           ),
         );
         );

+ 1 - 0
mobile/openapi/doc/AlbumResponseDto.md

@@ -8,6 +8,7 @@ import 'package:openapi/api.dart';
 ## Properties
 ## Properties
 Name | Type | Description | Notes
 Name | Type | Description | Notes
 ------------ | ------------- | ------------- | -------------
 ------------ | ------------- | ------------- | -------------
+**assetCount** | **int** |  | 
 **id** | **String** |  | 
 **id** | **String** |  | 
 **ownerId** | **String** |  | 
 **ownerId** | **String** |  | 
 **albumName** | **String** |  | 
 **albumName** | **String** |  | 

+ 9 - 1
mobile/openapi/lib/model/album_response_dto.dart

@@ -13,6 +13,7 @@ part of openapi.api;
 class AlbumResponseDto {
 class AlbumResponseDto {
   /// Returns a new [AlbumResponseDto] instance.
   /// Returns a new [AlbumResponseDto] instance.
   AlbumResponseDto({
   AlbumResponseDto({
+    required this.assetCount,
     required this.id,
     required this.id,
     required this.ownerId,
     required this.ownerId,
     required this.albumName,
     required this.albumName,
@@ -23,6 +24,8 @@ class AlbumResponseDto {
     this.assets = const [],
     this.assets = const [],
   });
   });
 
 
+  int assetCount;
+
   String id;
   String id;
 
 
   String ownerId;
   String ownerId;
@@ -41,6 +44,7 @@ class AlbumResponseDto {
 
 
   @override
   @override
   bool operator ==(Object other) => identical(this, other) || other is AlbumResponseDto &&
   bool operator ==(Object other) => identical(this, other) || other is AlbumResponseDto &&
+     other.assetCount == assetCount &&
      other.id == id &&
      other.id == id &&
      other.ownerId == ownerId &&
      other.ownerId == ownerId &&
      other.albumName == albumName &&
      other.albumName == albumName &&
@@ -53,6 +57,7 @@ class AlbumResponseDto {
   @override
   @override
   int get hashCode =>
   int get hashCode =>
     // ignore: unnecessary_parenthesis
     // ignore: unnecessary_parenthesis
+    (assetCount.hashCode) +
     (id.hashCode) +
     (id.hashCode) +
     (ownerId.hashCode) +
     (ownerId.hashCode) +
     (albumName.hashCode) +
     (albumName.hashCode) +
@@ -63,10 +68,11 @@ class AlbumResponseDto {
     (assets.hashCode);
     (assets.hashCode);
 
 
   @override
   @override
-  String toString() => 'AlbumResponseDto[id=$id, ownerId=$ownerId, albumName=$albumName, createdAt=$createdAt, albumThumbnailAssetId=$albumThumbnailAssetId, shared=$shared, sharedUsers=$sharedUsers, assets=$assets]';
+  String toString() => 'AlbumResponseDto[assetCount=$assetCount, id=$id, ownerId=$ownerId, albumName=$albumName, createdAt=$createdAt, albumThumbnailAssetId=$albumThumbnailAssetId, shared=$shared, sharedUsers=$sharedUsers, assets=$assets]';
 
 
   Map<String, dynamic> toJson() {
   Map<String, dynamic> toJson() {
     final _json = <String, dynamic>{};
     final _json = <String, dynamic>{};
+      _json[r'assetCount'] = assetCount;
       _json[r'id'] = id;
       _json[r'id'] = id;
       _json[r'ownerId'] = ownerId;
       _json[r'ownerId'] = ownerId;
       _json[r'albumName'] = albumName;
       _json[r'albumName'] = albumName;
@@ -101,6 +107,7 @@ class AlbumResponseDto {
       }());
       }());
 
 
       return AlbumResponseDto(
       return AlbumResponseDto(
+        assetCount: mapValueOfType<int>(json, r'assetCount')!,
         id: mapValueOfType<String>(json, r'id')!,
         id: mapValueOfType<String>(json, r'id')!,
         ownerId: mapValueOfType<String>(json, r'ownerId')!,
         ownerId: mapValueOfType<String>(json, r'ownerId')!,
         albumName: mapValueOfType<String>(json, r'albumName')!,
         albumName: mapValueOfType<String>(json, r'albumName')!,
@@ -158,6 +165,7 @@ class AlbumResponseDto {
 
 
   /// The list of required keys that must be present in a JSON.
   /// The list of required keys that must be present in a JSON.
   static const requiredKeys = <String>{
   static const requiredKeys = <String>{
+    'assetCount',
     'id',
     'id',
     'ownerId',
     'ownerId',
     'albumName',
     'albumName',

+ 51 - 64
mobile/openapi/lib/model/asset_response_dto.dart

@@ -76,72 +76,69 @@ class AssetResponseDto {
   SmartInfoResponseDto? smartInfo;
   SmartInfoResponseDto? smartInfo;
 
 
   @override
   @override
-  bool operator ==(Object other) =>
-      identical(this, other) ||
-      other is AssetResponseDto &&
-          other.type == type &&
-          other.id == id &&
-          other.deviceAssetId == deviceAssetId &&
-          other.ownerId == ownerId &&
-          other.deviceId == deviceId &&
-          other.originalPath == originalPath &&
-          other.resizePath == resizePath &&
-          other.createdAt == createdAt &&
-          other.modifiedAt == modifiedAt &&
-          other.isFavorite == isFavorite &&
-          other.mimeType == mimeType &&
-          other.duration == duration &&
-          other.webpPath == webpPath &&
-          other.encodedVideoPath == encodedVideoPath &&
-          other.exifInfo == exifInfo &&
-          other.smartInfo == smartInfo;
+  bool operator ==(Object other) => identical(this, other) || other is AssetResponseDto &&
+     other.type == type &&
+     other.id == id &&
+     other.deviceAssetId == deviceAssetId &&
+     other.ownerId == ownerId &&
+     other.deviceId == deviceId &&
+     other.originalPath == originalPath &&
+     other.resizePath == resizePath &&
+     other.createdAt == createdAt &&
+     other.modifiedAt == modifiedAt &&
+     other.isFavorite == isFavorite &&
+     other.mimeType == mimeType &&
+     other.duration == duration &&
+     other.webpPath == webpPath &&
+     other.encodedVideoPath == encodedVideoPath &&
+     other.exifInfo == exifInfo &&
+     other.smartInfo == smartInfo;
 
 
   @override
   @override
   int get hashCode =>
   int get hashCode =>
-      // ignore: unnecessary_parenthesis
-      (type.hashCode) +
-      (id.hashCode) +
-      (deviceAssetId.hashCode) +
-      (ownerId.hashCode) +
-      (deviceId.hashCode) +
-      (originalPath.hashCode) +
-      (resizePath == null ? 0 : resizePath!.hashCode) +
-      (createdAt.hashCode) +
-      (modifiedAt.hashCode) +
-      (isFavorite.hashCode) +
-      (mimeType == null ? 0 : mimeType!.hashCode) +
-      (duration.hashCode) +
-      (webpPath == null ? 0 : webpPath!.hashCode) +
-      (encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) +
-      (exifInfo == null ? 0 : exifInfo!.hashCode) +
-      (smartInfo == null ? 0 : smartInfo!.hashCode);
+    // ignore: unnecessary_parenthesis
+    (type.hashCode) +
+    (id.hashCode) +
+    (deviceAssetId.hashCode) +
+    (ownerId.hashCode) +
+    (deviceId.hashCode) +
+    (originalPath.hashCode) +
+    (resizePath == null ? 0 : resizePath!.hashCode) +
+    (createdAt.hashCode) +
+    (modifiedAt.hashCode) +
+    (isFavorite.hashCode) +
+    (mimeType == null ? 0 : mimeType!.hashCode) +
+    (duration.hashCode) +
+    (webpPath == null ? 0 : webpPath!.hashCode) +
+    (encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) +
+    (exifInfo == null ? 0 : exifInfo!.hashCode) +
+    (smartInfo == null ? 0 : smartInfo!.hashCode);
 
 
   @override
   @override
-  String toString() =>
-      'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, resizePath=$resizePath, createdAt=$createdAt, modifiedAt=$modifiedAt, isFavorite=$isFavorite, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo]';
+  String toString() => 'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, resizePath=$resizePath, createdAt=$createdAt, modifiedAt=$modifiedAt, isFavorite=$isFavorite, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo]';
 
 
   Map<String, dynamic> toJson() {
   Map<String, dynamic> toJson() {
     final _json = <String, dynamic>{};
     final _json = <String, dynamic>{};
-    _json[r'type'] = type;
-    _json[r'id'] = id;
-    _json[r'deviceAssetId'] = deviceAssetId;
-    _json[r'ownerId'] = ownerId;
-    _json[r'deviceId'] = deviceId;
-    _json[r'originalPath'] = originalPath;
+      _json[r'type'] = type;
+      _json[r'id'] = id;
+      _json[r'deviceAssetId'] = deviceAssetId;
+      _json[r'ownerId'] = ownerId;
+      _json[r'deviceId'] = deviceId;
+      _json[r'originalPath'] = originalPath;
     if (resizePath != null) {
     if (resizePath != null) {
       _json[r'resizePath'] = resizePath;
       _json[r'resizePath'] = resizePath;
     } else {
     } else {
       _json[r'resizePath'] = null;
       _json[r'resizePath'] = null;
     }
     }
-    _json[r'createdAt'] = createdAt;
-    _json[r'modifiedAt'] = modifiedAt;
-    _json[r'isFavorite'] = isFavorite;
+      _json[r'createdAt'] = createdAt;
+      _json[r'modifiedAt'] = modifiedAt;
+      _json[r'isFavorite'] = isFavorite;
     if (mimeType != null) {
     if (mimeType != null) {
       _json[r'mimeType'] = mimeType;
       _json[r'mimeType'] = mimeType;
     } else {
     } else {
       _json[r'mimeType'] = null;
       _json[r'mimeType'] = null;
     }
     }
-    _json[r'duration'] = duration;
+      _json[r'duration'] = duration;
     if (webpPath != null) {
     if (webpPath != null) {
       _json[r'webpPath'] = webpPath;
       _json[r'webpPath'] = webpPath;
     } else {
     } else {
@@ -177,10 +174,8 @@ class AssetResponseDto {
       // Note 2: this code is stripped in release mode!
       // Note 2: this code is stripped in release mode!
       assert(() {
       assert(() {
         requiredKeys.forEach((key) {
         requiredKeys.forEach((key) {
-          assert(json.containsKey(key),
-              'Required key "AssetResponseDto[$key]" is missing from JSON.');
-          assert(json[key] != null,
-              'Required key "AssetResponseDto[$key]" has a null value in JSON.');
+          assert(json.containsKey(key), 'Required key "AssetResponseDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "AssetResponseDto[$key]" has a null value in JSON.');
         });
         });
         return true;
         return true;
       }());
       }());
@@ -207,10 +202,7 @@ class AssetResponseDto {
     return null;
     return null;
   }
   }
 
 
-  static List<AssetResponseDto>? listFromJson(
-    dynamic json, {
-    bool growable = false,
-  }) {
+  static List<AssetResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
     final result = <AssetResponseDto>[];
     final result = <AssetResponseDto>[];
     if (json is List && json.isNotEmpty) {
     if (json is List && json.isNotEmpty) {
       for (final row in json) {
       for (final row in json) {
@@ -238,18 +230,12 @@ class AssetResponseDto {
   }
   }
 
 
   // maps a json object with a list of AssetResponseDto-objects as value to a dart map
   // maps a json object with a list of AssetResponseDto-objects as value to a dart map
-  static Map<String, List<AssetResponseDto>> mapListFromJson(
-    dynamic json, {
-    bool growable = false,
-  }) {
+  static Map<String, List<AssetResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
     final map = <String, List<AssetResponseDto>>{};
     final map = <String, List<AssetResponseDto>>{};
     if (json is Map && json.isNotEmpty) {
     if (json is Map && json.isNotEmpty) {
       json = json.cast<String, dynamic>(); // ignore: parameter_assignments
       json = json.cast<String, dynamic>(); // ignore: parameter_assignments
       for (final entry in json.entries) {
       for (final entry in json.entries) {
-        final value = AssetResponseDto.listFromJson(
-          entry.value,
-          growable: growable,
-        );
+        final value = AssetResponseDto.listFromJson(entry.value, growable: growable,);
         if (value != null) {
         if (value != null) {
           map[entry.key] = value;
           map[entry.key] = value;
         }
         }
@@ -276,3 +262,4 @@ class AssetResponseDto {
     'encodedVideoPath',
     'encodedVideoPath',
   };
   };
 }
 }
+

+ 2 - 9
server/apps/immich/src/api-v1/album/album-repository.ts

@@ -134,21 +134,14 @@ export class AlbumRepository implements IAlbumRepository {
         .leftJoinAndSelect('album.sharedUsers', 'sharedUser')
         .leftJoinAndSelect('album.sharedUsers', 'sharedUser')
         .leftJoinAndSelect('sharedUser.userInfo', 'userInfo')
         .leftJoinAndSelect('sharedUser.userInfo', 'userInfo')
         .where('album.ownerId = :ownerId', { ownerId: userId });
         .where('album.ownerId = :ownerId', { ownerId: userId });
-      // .orWhere((qb) => {
-      //   const subQuery = qb
-      //     .subQuery()
-      //     .select('userAlbum.albumId')
-      //     .from(UserAlbumEntity, 'userAlbum')
-      //     .where('userAlbum.sharedUserId = :sharedUserId', { sharedUserId: userId })
-      //     .getQuery();
-      //   return `album.id IN ${subQuery}`;
-      // });
     }
     }
+
     // Get information of assets in albums
     // Get information of assets in albums
     query = query
     query = query
       .leftJoinAndSelect('album.assets', 'assets')
       .leftJoinAndSelect('album.assets', 'assets')
       .leftJoinAndSelect('assets.assetInfo', 'assetInfo')
       .leftJoinAndSelect('assets.assetInfo', 'assetInfo')
       .orderBy('"assetInfo"."createdAt"::timestamptz', 'ASC');
       .orderBy('"assetInfo"."createdAt"::timestamptz', 'ASC');
+
     const albums = await query.getMany();
     const albums = await query.getMany();
 
 
     albums.sort((a, b) => new Date(b.createdAt).valueOf() - new Date(a.createdAt).valueOf());
     albums.sort((a, b) => new Date(b.createdAt).valueOf() - new Date(a.createdAt).valueOf());

+ 3 - 2
server/apps/immich/src/api-v1/album/album.service.ts

@@ -7,7 +7,7 @@ import { AddUsersDto } from './dto/add-users.dto';
 import { RemoveAssetsDto } from './dto/remove-assets.dto';
 import { RemoveAssetsDto } from './dto/remove-assets.dto';
 import { UpdateAlbumDto } from './dto/update-album.dto';
 import { UpdateAlbumDto } from './dto/update-album.dto';
 import { GetAlbumsDto } from './dto/get-albums.dto';
 import { GetAlbumsDto } from './dto/get-albums.dto';
-import { AlbumResponseDto, mapAlbum } from './response-dto/album-response.dto';
+import { AlbumResponseDto, mapAlbum, mapAlbumExcludeAssetInfo } from './response-dto/album-response.dto';
 import { ALBUM_REPOSITORY, IAlbumRepository } from './album-repository';
 import { ALBUM_REPOSITORY, IAlbumRepository } from './album-repository';
 
 
 @Injectable()
 @Injectable()
@@ -49,7 +49,8 @@ export class AlbumService {
    */
    */
   async getAllAlbums(authUser: AuthUserDto, getAlbumsDto: GetAlbumsDto): Promise<AlbumResponseDto[]> {
   async getAllAlbums(authUser: AuthUserDto, getAlbumsDto: GetAlbumsDto): Promise<AlbumResponseDto[]> {
     const albums = await this._albumRepository.getList(authUser.id, getAlbumsDto);
     const albums = await this._albumRepository.getList(authUser.id, getAlbumsDto);
-    return albums.map((album) => mapAlbum(album));
+
+    return albums.map((album) => mapAlbumExcludeAssetInfo(album));
   }
   }
 
 
   async getAlbumInfo(authUser: AuthUserDto, albumId: string): Promise<AlbumResponseDto> {
   async getAlbumInfo(authUser: AuthUserDto, albumId: string): Promise<AlbumResponseDto> {

+ 20 - 0
server/apps/immich/src/api-v1/album/response-dto/album-response.dto.ts

@@ -1,6 +1,7 @@
 import { AlbumEntity } from '../../../../../../libs/database/src/entities/album.entity';
 import { AlbumEntity } from '../../../../../../libs/database/src/entities/album.entity';
 import { UserResponseDto, mapUser } from '../../user/response-dto/user-response.dto';
 import { UserResponseDto, mapUser } from '../../user/response-dto/user-response.dto';
 import { AssetResponseDto, mapAsset } from '../../asset/response-dto/asset-response.dto';
 import { AssetResponseDto, mapAsset } from '../../asset/response-dto/asset-response.dto';
+import { ApiProperty } from '@nestjs/swagger';
 
 
 export class AlbumResponseDto {
 export class AlbumResponseDto {
   id!: string;
   id!: string;
@@ -11,6 +12,9 @@ export class AlbumResponseDto {
   shared!: boolean;
   shared!: boolean;
   sharedUsers!: UserResponseDto[];
   sharedUsers!: UserResponseDto[];
   assets!: AssetResponseDto[];
   assets!: AssetResponseDto[];
+
+  @ApiProperty({ type: 'integer' })
+  assetCount!: number;
 }
 }
 
 
 export function mapAlbum(entity: AlbumEntity): AlbumResponseDto {
 export function mapAlbum(entity: AlbumEntity): AlbumResponseDto {
@@ -24,5 +28,21 @@ export function mapAlbum(entity: AlbumEntity): AlbumResponseDto {
     sharedUsers,
     sharedUsers,
     shared: sharedUsers.length > 0,
     shared: sharedUsers.length > 0,
     assets: entity.assets?.map((assetAlbum) => mapAsset(assetAlbum.assetInfo)) || [],
     assets: entity.assets?.map((assetAlbum) => mapAsset(assetAlbum.assetInfo)) || [],
+    assetCount: entity.assets?.length || 0,
+  };
+}
+
+export function mapAlbumExcludeAssetInfo(entity: AlbumEntity): AlbumResponseDto {
+  const sharedUsers = entity.sharedUsers?.map((userAlbum) => mapUser(userAlbum.userInfo)) || [];
+  return {
+    albumName: entity.albumName,
+    albumThumbnailAssetId: entity.albumThumbnailAssetId,
+    createdAt: entity.createdAt,
+    id: entity.id,
+    ownerId: entity.ownerId,
+    sharedUsers,
+    shared: sharedUsers.length > 0,
+    assets: [],
+    assetCount: entity.assets?.length || 0,
   };
   };
 }
 }

文件差异内容过多而无法显示
+ 0 - 0
server/immich-openapi-specs.json


+ 6 - 0
web/src/api/open-api/api.ts

@@ -90,6 +90,12 @@ export interface AdminSignupResponseDto {
  * @interface AlbumResponseDto
  * @interface AlbumResponseDto
  */
  */
 export interface AlbumResponseDto {
 export interface AlbumResponseDto {
+    /**
+     * 
+     * @type {number}
+     * @memberof AlbumResponseDto
+     */
+    'assetCount': number;
     /**
     /**
      * 
      * 
      * @type {string}
      * @type {string}

+ 2 - 2
web/src/lib/components/album-page/album-card.svelte

@@ -32,7 +32,7 @@
 	};
 	};
 
 
 	onMount(async () => {
 	onMount(async () => {
-		imageData = await loadHighQualityThumbnail(album.albumThumbnailAssetId) || 'no-thumbnail.png';
+		imageData = (await loadHighQualityThumbnail(album.albumThumbnailAssetId)) || 'no-thumbnail.png';
 	});
 	});
 </script>
 </script>
 
 
@@ -67,7 +67,7 @@
 		</p>
 		</p>
 
 
 		<span class="text-xs flex gap-2">
 		<span class="text-xs flex gap-2">
-			<p>{album.assets.length} items</p>
+			<p>{album.assetCount} items</p>
 
 
 			{#if album.shared}
 			{#if album.shared}
 				<p>·</p>
 				<p>·</p>

+ 8 - 8
web/src/lib/components/album-page/album-viewer.svelte

@@ -61,7 +61,7 @@
 
 
 	$: {
 	$: {
 		if (album.assets?.length < 6) {
 		if (album.assets?.length < 6) {
-			thumbnailSize = Math.floor(viewWidth / album.assets.length - album.assets.length);
+			thumbnailSize = Math.floor(viewWidth / album.assetCount - album.assetCount);
 		} else {
 		} else {
 			thumbnailSize = Math.floor(viewWidth / 6 - 6);
 			thumbnailSize = Math.floor(viewWidth / 6 - 6);
 		}
 		}
@@ -69,7 +69,7 @@
 
 
 	const getDateRange = () => {
 	const getDateRange = () => {
 		const startDate = new Date(album.assets[0].createdAt);
 		const startDate = new Date(album.assets[0].createdAt);
-		const endDate = new Date(album.assets[album.assets.length - 1].createdAt);
+		const endDate = new Date(album.assets[album.assetCount - 1].createdAt);
 
 
 		const timeFormatOption: Intl.DateTimeFormatOptions = {
 		const timeFormatOption: Intl.DateTimeFormatOptions = {
 			month: 'short',
 			month: 'short',
@@ -135,7 +135,7 @@
 	};
 	};
 	const navigateAssetForward = () => {
 	const navigateAssetForward = () => {
 		try {
 		try {
-			if (currentViewAssetIndex < album.assets.length - 1) {
+			if (currentViewAssetIndex < album.assetCount - 1) {
 				currentViewAssetIndex++;
 				currentViewAssetIndex++;
 				selectedAsset = album.assets[currentViewAssetIndex];
 				selectedAsset = album.assets[currentViewAssetIndex];
 				pushState(selectedAsset.id);
 				pushState(selectedAsset.id);
@@ -296,7 +296,7 @@
 	{#if !isMultiSelectionMode}
 	{#if !isMultiSelectionMode}
 		<ControlAppBar on:close-button-click={() => goto(backUrl)} backIcon={ArrowLeft}>
 		<ControlAppBar on:close-button-click={() => goto(backUrl)} backIcon={ArrowLeft}>
 			<svelte:fragment slot="trailing">
 			<svelte:fragment slot="trailing">
-				{#if album.assets.length > 0}
+				{#if album.assetCount > 0}
 					<CircleIconButton
 					<CircleIconButton
 						title="Add Photos"
 						title="Add Photos"
 						on:click={() => (isShowAssetSelection = true)}
 						on:click={() => (isShowAssetSelection = true)}
@@ -322,7 +322,7 @@
 
 
 				{#if isCreatingSharedAlbum && album.sharedUsers.length == 0}
 				{#if isCreatingSharedAlbum && album.sharedUsers.length == 0}
 					<button
 					<button
-						disabled={album.assets.length == 0}
+						disabled={album.assetCount == 0}
 						on:click={() => (isShowShareUserSelection = true)}
 						on:click={() => (isShowShareUserSelection = true)}
 						class="immich-text-button border bg-immich-primary text-gray-50 hover:bg-immich-primary/75 px-6 text-sm disabled:opacity-25 disabled:bg-gray-500 disabled:cursor-not-allowed"
 						class="immich-text-button border bg-immich-primary text-gray-50 hover:bg-immich-primary/75 px-6 text-sm disabled:opacity-25 disabled:bg-gray-500 disabled:cursor-not-allowed"
 						><span class="px-2">Share</span></button
 						><span class="px-2">Share</span></button
@@ -351,7 +351,7 @@
 			bind:this={titleInput}
 			bind:this={titleInput}
 		/>
 		/>
 
 
-		{#if album.assets.length > 0}
+		{#if album.assetCount > 0}
 			<p class="my-4 text-sm text-gray-500 font-medium">{getDateRange()}</p>
 			<p class="my-4 text-sm text-gray-500 font-medium">{getDateRange()}</p>
 		{/if}
 		{/if}
 
 
@@ -375,11 +375,11 @@
 			</div>
 			</div>
 		{/if}
 		{/if}
 
 
-		{#if album.assets.length > 0}
+		{#if album.assetCount > 0}
 			<div class="flex flex-wrap gap-1 w-full pb-20" bind:clientWidth={viewWidth}>
 			<div class="flex flex-wrap gap-1 w-full pb-20" bind:clientWidth={viewWidth}>
 				{#each album.assets as asset}
 				{#each album.assets as asset}
 					{#key asset.id}
 					{#key asset.id}
-						{#if album.assets.length < 7}
+						{#if album.assetCount < 7}
 							<ImmichThumbnail
 							<ImmichThumbnail
 								{asset}
 								{asset}
 								{thumbnailSize}
 								{thumbnailSize}

+ 1 - 1
web/src/routes/albums/index.svelte

@@ -61,7 +61,7 @@
 
 
 		// Delete album that has no photos and is named 'Untitled'
 		// Delete album that has no photos and is named 'Untitled'
 		for (const album of albums) {
 		for (const album of albums) {
-			if (album.albumName === 'Untitled' && album.assets.length === 0) {
+			if (album.albumName === 'Untitled' && album.assetCount === 0) {
 				const isDeleted = await autoDeleteAlbum(album);
 				const isDeleted = await autoDeleteAlbum(album);
 
 
 				if (isDeleted) {
 				if (isDeleted) {

部分文件因为文件数量过多而无法显示