1
0
mirror of https://github.com/immich-app/immich.git synced 2025-08-09 23:17:29 +02:00

Add ablum feature to web (#352)

* Added album page

* Refactor sidebar

* Added album assets count info

* Added album viewer page

* Refactor album sorting

* Fixed incorrectly showing selected asset in album selection

* Improve fetching speed with prefetch

* Refactor to use ImmichThubmnail component for all

* Update to the latest version of Svelte

* Implement fixed app bar in album viewer

* Added shared user avatar

* Correctly get all owned albums, including shared
This commit is contained in:
Alex
2022-07-15 23:18:17 -05:00
committed by GitHub
parent 1887b5a860
commit 7134f93eb8
62 changed files with 2572 additions and 991 deletions

View File

@@ -84,7 +84,7 @@ export class AlbumRepository implements IAlbumRepository {
});
}
getList(ownerId: string, getAlbumsDto: GetAlbumsDto): Promise<AlbumEntity[]> {
async getList(ownerId: string, getAlbumsDto: GetAlbumsDto): Promise<AlbumEntity[]> {
const filteringByShared = typeof getAlbumsDto.shared == 'boolean';
const userId = ownerId;
let query = this.albumRepository.createQueryBuilder('album');
@@ -132,35 +132,44 @@ export class AlbumRepository implements IAlbumRepository {
query = query
.leftJoinAndSelect('album.sharedUsers', 'sharedUser')
.leftJoinAndSelect('sharedUser.userInfo', 'userInfo')
.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}`;
});
.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}`;
// });
}
return query.orderBy('album.createdAt', 'DESC').getMany();
// Get information of assets in albums
query = query
.leftJoinAndSelect('album.assets', 'assets')
.leftJoinAndSelect('assets.assetInfo', 'assetInfo')
.orderBy('"assetInfo"."createdAt"::timestamptz', 'ASC');
const albums = await query.getMany();
albums.sort((a, b) => new Date(b.createdAt).valueOf() - new Date(a.createdAt).valueOf());
return albums;
}
async get(albumId: string): Promise<AlbumEntity | undefined> {
const album = await this.albumRepository.findOne({
where: { id: albumId },
relations: ['sharedUsers', 'sharedUsers.userInfo', 'assets', 'assets.assetInfo'],
});
let query = this.albumRepository.createQueryBuilder('album');
const album = await query
.where('album.id = :albumId', { albumId })
.leftJoinAndSelect('album.sharedUsers', 'sharedUser')
.leftJoinAndSelect('sharedUser.userInfo', 'userInfo')
.leftJoinAndSelect('album.assets', 'assets')
.leftJoinAndSelect('assets.assetInfo', 'assetInfo')
.orderBy('"assetInfo"."createdAt"::timestamptz', 'ASC')
.getOne();
if (!album) {
return;
}
// TODO: sort in query
const sortedSharedAsset = album.assets?.sort(
(a, b) => new Date(a.assetInfo.createdAt).valueOf() - new Date(b.assetInfo.createdAt).valueOf(),
);
album.assets = sortedSharedAsset;
return album;
}

View File

@@ -43,6 +43,7 @@ import { AssetFileUploadDto } from './dto/asset-file-upload.dto';
import { CreateAssetDto } from './dto/create-asset.dto';
import { AssetFileUploadResponseDto } from './response-dto/asset-file-upload-response.dto';
import { DeleteAssetResponseDto, DeleteAssetStatusEnum } from './response-dto/delete-asset-response.dto';
import { GetAssetThumbnailDto } from './dto/get-asset-thumbnail.dto';
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@@ -109,8 +110,11 @@ export class AssetController {
}
@Get('/thumbnail/:assetId')
async getAssetThumbnail(@Param('assetId') assetId: string): Promise<any> {
return this.assetService.getAssetThumbnail(assetId);
async getAssetThumbnail(
@Param('assetId') assetId: string,
@Query(new ValidationPipe({ transform: true })) query: GetAssetThumbnailDto,
): Promise<any> {
return this.assetService.getAssetThumbnail(assetId, query);
}
@Get('/allObjects')

View File

@@ -23,6 +23,7 @@ import { AssetResponseDto, mapAsset } from './response-dto/asset-response.dto';
import { AssetFileUploadDto } from './dto/asset-file-upload.dto';
import { CreateAssetDto } from './dto/create-asset.dto';
import { DeleteAssetResponseDto, DeleteAssetStatusEnum } from './response-dto/delete-asset-response.dto';
import { GetAssetThumbnailDto, GetAssetThumbnailFormatEnum } from './dto/get-asset-thumbnail.dto';
const fileInfo = promisify(stat);
@@ -187,7 +188,7 @@ export class AssetService {
}
}
public async getAssetThumbnail(assetId: string) {
public async getAssetThumbnail(assetId: string, query: GetAssetThumbnailDto) {
let fileReadStream: ReadStream;
const asset = await this.assetRepository.findOne({ where: { id: assetId } });
@@ -197,16 +198,25 @@ export class AssetService {
}
try {
if (asset.webpPath && asset.webpPath.length > 0) {
await fs.access(asset.webpPath, constants.R_OK | constants.W_OK);
fileReadStream = createReadStream(asset.webpPath);
} else {
if (query.format == GetAssetThumbnailFormatEnum.JPEG) {
if (!asset.resizePath) {
throw new NotFoundException('resizePath not set');
}
await fs.access(asset.resizePath, constants.R_OK | constants.W_OK);
fileReadStream = createReadStream(asset.resizePath);
} else {
if (asset.webpPath && asset.webpPath.length > 0) {
await fs.access(asset.webpPath, constants.R_OK | constants.W_OK);
fileReadStream = createReadStream(asset.webpPath);
} else {
if (!asset.resizePath) {
throw new NotFoundException('resizePath not set');
}
await fs.access(asset.resizePath, constants.R_OK | constants.W_OK);
fileReadStream = createReadStream(asset.resizePath);
}
}
return new StreamableFile(fileReadStream);

View File

@@ -0,0 +1,19 @@
import { ApiProperty } from '@nestjs/swagger';
import { Transform } from 'class-transformer';
import { IsBoolean, IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator';
export enum GetAssetThumbnailFormatEnum {
JPEG = 'JPEG',
WEBP = 'WEBP',
}
export class GetAssetThumbnailDto {
@IsOptional()
@ApiProperty({
enum: GetAssetThumbnailFormatEnum,
default: GetAssetThumbnailFormatEnum.WEBP,
required: false,
enumName: 'ThumbnailFormat',
})
format = GetAssetThumbnailFormatEnum.WEBP;
}