mirror of
https://github.com/immich-app/immich.git
synced 2024-12-25 10:43:13 +02:00
feat(web): add asset and album count info (#623)
* Get asset and album count * Generate APIs * Added asset count for each type * Added api on the web * Added info button for asset and album count to trigger getting info on hover * Remove websocket event from photo page
This commit is contained in:
parent
18a7ff8726
commit
566039b93f
@ -6,10 +6,12 @@ doc/AddAssetsDto.md
|
|||||||
doc/AddUsersDto.md
|
doc/AddUsersDto.md
|
||||||
doc/AdminSignupResponseDto.md
|
doc/AdminSignupResponseDto.md
|
||||||
doc/AlbumApi.md
|
doc/AlbumApi.md
|
||||||
|
doc/AlbumCountResponseDto.md
|
||||||
doc/AlbumResponseDto.md
|
doc/AlbumResponseDto.md
|
||||||
doc/AssetApi.md
|
doc/AssetApi.md
|
||||||
doc/AssetCountByTimeBucket.md
|
doc/AssetCountByTimeBucket.md
|
||||||
doc/AssetCountByTimeBucketResponseDto.md
|
doc/AssetCountByTimeBucketResponseDto.md
|
||||||
|
doc/AssetCountByUserIdResponseDto.md
|
||||||
doc/AssetFileUploadResponseDto.md
|
doc/AssetFileUploadResponseDto.md
|
||||||
doc/AssetResponseDto.md
|
doc/AssetResponseDto.md
|
||||||
doc/AssetTypeEnum.md
|
doc/AssetTypeEnum.md
|
||||||
@ -70,9 +72,11 @@ lib/auth/oauth.dart
|
|||||||
lib/model/add_assets_dto.dart
|
lib/model/add_assets_dto.dart
|
||||||
lib/model/add_users_dto.dart
|
lib/model/add_users_dto.dart
|
||||||
lib/model/admin_signup_response_dto.dart
|
lib/model/admin_signup_response_dto.dart
|
||||||
|
lib/model/album_count_response_dto.dart
|
||||||
lib/model/album_response_dto.dart
|
lib/model/album_response_dto.dart
|
||||||
lib/model/asset_count_by_time_bucket.dart
|
lib/model/asset_count_by_time_bucket.dart
|
||||||
lib/model/asset_count_by_time_bucket_response_dto.dart
|
lib/model/asset_count_by_time_bucket_response_dto.dart
|
||||||
|
lib/model/asset_count_by_user_id_response_dto.dart
|
||||||
lib/model/asset_file_upload_response_dto.dart
|
lib/model/asset_file_upload_response_dto.dart
|
||||||
lib/model/asset_response_dto.dart
|
lib/model/asset_response_dto.dart
|
||||||
lib/model/asset_type_enum.dart
|
lib/model/asset_type_enum.dart
|
||||||
@ -111,3 +115,4 @@ lib/model/user_count_response_dto.dart
|
|||||||
lib/model/user_response_dto.dart
|
lib/model/user_response_dto.dart
|
||||||
lib/model/validate_access_token_response_dto.dart
|
lib/model/validate_access_token_response_dto.dart
|
||||||
pubspec.yaml
|
pubspec.yaml
|
||||||
|
test/asset_count_by_user_id_response_dto_test.dart
|
||||||
|
Binary file not shown.
Binary file not shown.
BIN
mobile/openapi/doc/AlbumCountResponseDto.md
Normal file
BIN
mobile/openapi/doc/AlbumCountResponseDto.md
Normal file
Binary file not shown.
Binary file not shown.
BIN
mobile/openapi/doc/AssetCountByUserIdResponseDto.md
Normal file
BIN
mobile/openapi/doc/AssetCountByUserIdResponseDto.md
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
mobile/openapi/lib/model/album_count_response_dto.dart
Normal file
BIN
mobile/openapi/lib/model/album_count_response_dto.dart
Normal file
Binary file not shown.
Binary file not shown.
BIN
mobile/openapi/test/album_count_response_dto_test.dart
Normal file
BIN
mobile/openapi/test/album_count_response_dto_test.dart
Normal file
Binary file not shown.
Binary file not shown.
@ -10,6 +10,7 @@ import { CreateAlbumDto } from './dto/create-album.dto';
|
|||||||
import { GetAlbumsDto } from './dto/get-albums.dto';
|
import { GetAlbumsDto } from './dto/get-albums.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 { AlbumCountResponseDto } from './response-dto/album-count-response.dto';
|
||||||
import { AlbumResponseDto } from './response-dto/album-response.dto';
|
import { AlbumResponseDto } from './response-dto/album-response.dto';
|
||||||
|
|
||||||
export interface IAlbumRepository {
|
export interface IAlbumRepository {
|
||||||
@ -23,6 +24,7 @@ export interface IAlbumRepository {
|
|||||||
addAssets(album: AlbumEntity, addAssetsDto: AddAssetsDto): Promise<AlbumEntity>;
|
addAssets(album: AlbumEntity, addAssetsDto: AddAssetsDto): Promise<AlbumEntity>;
|
||||||
updateAlbum(album: AlbumEntity, updateAlbumDto: UpdateAlbumDto): Promise<AlbumEntity>;
|
updateAlbum(album: AlbumEntity, updateAlbumDto: UpdateAlbumDto): Promise<AlbumEntity>;
|
||||||
getListByAssetId(userId: string, assetId: string): Promise<AlbumEntity[]>;
|
getListByAssetId(userId: string, assetId: string): Promise<AlbumEntity[]>;
|
||||||
|
getCountByUserId(userId: string): Promise<AlbumCountResponseDto>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ALBUM_REPOSITORY = 'ALBUM_REPOSITORY';
|
export const ALBUM_REPOSITORY = 'ALBUM_REPOSITORY';
|
||||||
@ -42,6 +44,18 @@ export class AlbumRepository implements IAlbumRepository {
|
|||||||
private dataSource: DataSource,
|
private dataSource: DataSource,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
async getCountByUserId(userId: string): Promise<AlbumCountResponseDto> {
|
||||||
|
const ownedAlbums = await this.albumRepository.find({ where: { ownerId: userId }, relations: ['sharedUsers'] });
|
||||||
|
|
||||||
|
const sharedAlbums = await this.userAlbumRepository.count({
|
||||||
|
where: { sharedUserId: userId },
|
||||||
|
});
|
||||||
|
|
||||||
|
const sharingAlbums = ownedAlbums.map((album) => album.sharedUsers?.length || 0).reduce((a, b) => a + b, 0);
|
||||||
|
|
||||||
|
return new AlbumCountResponseDto(ownedAlbums.length, sharedAlbums, sharingAlbums);
|
||||||
|
}
|
||||||
|
|
||||||
async create(ownerId: string, createAlbumDto: CreateAlbumDto): Promise<AlbumEntity> {
|
async create(ownerId: string, createAlbumDto: CreateAlbumDto): Promise<AlbumEntity> {
|
||||||
return this.dataSource.transaction(async (transactionalEntityManager) => {
|
return this.dataSource.transaction(async (transactionalEntityManager) => {
|
||||||
// Create album entity
|
// Create album entity
|
||||||
@ -154,23 +168,23 @@ export class AlbumRepository implements IAlbumRepository {
|
|||||||
let query = this.albumRepository.createQueryBuilder('album');
|
let query = this.albumRepository.createQueryBuilder('album');
|
||||||
|
|
||||||
const albums = await query
|
const albums = await query
|
||||||
.where('album.ownerId = :ownerId', { ownerId: userId })
|
.where('album.ownerId = :ownerId', { ownerId: userId })
|
||||||
.andWhere((qb) => {
|
.andWhere((qb) => {
|
||||||
// shared with userId
|
// shared with userId
|
||||||
const subQuery = qb
|
const subQuery = qb
|
||||||
.subQuery()
|
.subQuery()
|
||||||
.select('assetAlbum.albumId')
|
.select('assetAlbum.albumId')
|
||||||
.from(AssetAlbumEntity, 'assetAlbum')
|
.from(AssetAlbumEntity, 'assetAlbum')
|
||||||
.where('assetAlbum.assetId = :assetId', {assetId: assetId})
|
.where('assetAlbum.assetId = :assetId', { assetId: assetId })
|
||||||
.getQuery();
|
.getQuery();
|
||||||
return `album.id IN ${subQuery}`;
|
return `album.id IN ${subQuery}`;
|
||||||
})
|
})
|
||||||
.leftJoinAndSelect('album.assets', 'assets')
|
.leftJoinAndSelect('album.assets', 'assets')
|
||||||
.leftJoinAndSelect('assets.assetInfo', 'assetInfo')
|
.leftJoinAndSelect('assets.assetInfo', 'assetInfo')
|
||||||
.leftJoinAndSelect('album.sharedUsers', 'sharedUser')
|
.leftJoinAndSelect('album.sharedUsers', 'sharedUser')
|
||||||
.leftJoinAndSelect('sharedUser.userInfo', 'userInfo')
|
.leftJoinAndSelect('sharedUser.userInfo', 'userInfo')
|
||||||
.orderBy('"assetInfo"."createdAt"::timestamptz', 'ASC')
|
.orderBy('"assetInfo"."createdAt"::timestamptz', 'ASC')
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
||||||
return albums;
|
return albums;
|
||||||
}
|
}
|
||||||
@ -228,9 +242,10 @@ export class AlbumRepository implements IAlbumRepository {
|
|||||||
|
|
||||||
// TODO: No need to return boolean if using a singe delete query
|
// TODO: No need to return boolean if using a singe delete query
|
||||||
if (deleteAssetCount == removeAssetsDto.assetIds.length) {
|
if (deleteAssetCount == removeAssetsDto.assetIds.length) {
|
||||||
const retAlbum = await this.get(album.id) as AlbumEntity;
|
const retAlbum = (await this.get(album.id)) as AlbumEntity;
|
||||||
|
|
||||||
if (retAlbum?.assets?.length === 0) { // is empty album
|
if (retAlbum?.assets?.length === 0) {
|
||||||
|
// is empty album
|
||||||
await this.albumRepository.update(album.id, { albumThumbnailAssetId: null });
|
await this.albumRepository.update(album.id, { albumThumbnailAssetId: null });
|
||||||
retAlbum.albumThumbnailAssetId = null;
|
retAlbum.albumThumbnailAssetId = null;
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
ParseUUIDPipe,
|
ParseUUIDPipe,
|
||||||
Put,
|
Put,
|
||||||
Query,
|
Query,
|
||||||
|
Header,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ParseMeUUIDPipe } from '../validation/parse-me-uuid-pipe';
|
import { ParseMeUUIDPipe } from '../validation/parse-me-uuid-pipe';
|
||||||
import { AlbumService } from './album.service';
|
import { AlbumService } from './album.service';
|
||||||
@ -24,6 +25,7 @@ import { UpdateAlbumDto } from './dto/update-album.dto';
|
|||||||
import { GetAlbumsDto } from './dto/get-albums.dto';
|
import { GetAlbumsDto } from './dto/get-albums.dto';
|
||||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||||
import { AlbumResponseDto } from './response-dto/album-response.dto';
|
import { AlbumResponseDto } from './response-dto/album-response.dto';
|
||||||
|
import { AlbumCountResponseDto } from './response-dto/album-count-response.dto';
|
||||||
|
|
||||||
// TODO might be worth creating a AlbumParamsDto that validates `albumId` instead of using the pipe.
|
// TODO might be worth creating a AlbumParamsDto that validates `albumId` instead of using the pipe.
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@ -33,6 +35,11 @@ import { AlbumResponseDto } from './response-dto/album-response.dto';
|
|||||||
export class AlbumController {
|
export class AlbumController {
|
||||||
constructor(private readonly albumService: AlbumService) {}
|
constructor(private readonly albumService: AlbumService) {}
|
||||||
|
|
||||||
|
@Get('count-by-user-id')
|
||||||
|
async getAlbumCountByUserId(@GetAuthUser() authUser: AuthUserDto): Promise<AlbumCountResponseDto> {
|
||||||
|
return this.albumService.getAlbumCountByUserId(authUser);
|
||||||
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
async createAlbum(@GetAuthUser() authUser: AuthUserDto, @Body(ValidationPipe) createAlbumDto: CreateAlbumDto) {
|
async createAlbum(@GetAuthUser() authUser: AuthUserDto, @Body(ValidationPipe) createAlbumDto: CreateAlbumDto) {
|
||||||
return this.albumService.create(authUser, createAlbumDto);
|
return this.albumService.create(authUser, createAlbumDto);
|
||||||
|
@ -116,7 +116,8 @@ describe('Album service', () => {
|
|||||||
removeAssets: jest.fn(),
|
removeAssets: jest.fn(),
|
||||||
removeUser: jest.fn(),
|
removeUser: jest.fn(),
|
||||||
updateAlbum: jest.fn(),
|
updateAlbum: jest.fn(),
|
||||||
getListByAssetId: jest.fn()
|
getListByAssetId: jest.fn(),
|
||||||
|
getCountByUserId: jest.fn(),
|
||||||
};
|
};
|
||||||
sut = new AlbumService(albumRepositoryMock);
|
sut = new AlbumService(albumRepositoryMock);
|
||||||
});
|
});
|
||||||
|
@ -9,6 +9,7 @@ import { UpdateAlbumDto } from './dto/update-album.dto';
|
|||||||
import { GetAlbumsDto } from './dto/get-albums.dto';
|
import { GetAlbumsDto } from './dto/get-albums.dto';
|
||||||
import { AlbumResponseDto, mapAlbum, mapAlbumExcludeAssetInfo } 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';
|
||||||
|
import { AlbumCountResponseDto } from './response-dto/album-count-response.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AlbumService {
|
export class AlbumService {
|
||||||
@ -118,4 +119,8 @@ export class AlbumService {
|
|||||||
const updatedAlbum = await this._albumRepository.updateAlbum(album, updateAlbumDto);
|
const updatedAlbum = await this._albumRepository.updateAlbum(album, updateAlbumDto);
|
||||||
return mapAlbum(updatedAlbum);
|
return mapAlbum(updatedAlbum);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getAlbumCountByUserId(authUser: AuthUserDto): Promise<AlbumCountResponseDto> {
|
||||||
|
return this._albumRepository.getCountByUserId(authUser.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class AlbumCountResponseDto {
|
||||||
|
@ApiProperty({ type: 'integer' })
|
||||||
|
owned!: number;
|
||||||
|
|
||||||
|
@ApiProperty({ type: 'integer' })
|
||||||
|
shared!: number;
|
||||||
|
|
||||||
|
@ApiProperty({ type: 'integer' })
|
||||||
|
sharing!: number;
|
||||||
|
|
||||||
|
constructor(owned: number, shared: number, sharing: number) {
|
||||||
|
this.owned = owned;
|
||||||
|
this.shared = shared;
|
||||||
|
this.sharing = sharing;
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@ import { CuratedObjectsResponseDto } from './response-dto/curated-objects-respon
|
|||||||
import { AssetCountByTimeBucket } from './response-dto/asset-count-by-time-group-response.dto';
|
import { AssetCountByTimeBucket } from './response-dto/asset-count-by-time-group-response.dto';
|
||||||
import { TimeGroupEnum } from './dto/get-asset-count-by-time-bucket.dto';
|
import { TimeGroupEnum } from './dto/get-asset-count-by-time-bucket.dto';
|
||||||
import { GetAssetByTimeBucketDto } from './dto/get-asset-by-time-bucket.dto';
|
import { GetAssetByTimeBucketDto } from './dto/get-asset-by-time-bucket.dto';
|
||||||
|
import { AssetCountByUserIdResponseDto } from './response-dto/asset-count-by-user-id-response.dto';
|
||||||
|
|
||||||
export interface IAssetRepository {
|
export interface IAssetRepository {
|
||||||
create(
|
create(
|
||||||
@ -25,6 +26,7 @@ export interface IAssetRepository {
|
|||||||
getDetectedObjectsByUserId(userId: string): Promise<CuratedObjectsResponseDto[]>;
|
getDetectedObjectsByUserId(userId: string): Promise<CuratedObjectsResponseDto[]>;
|
||||||
getSearchPropertiesByUserId(userId: string): Promise<SearchPropertiesDto[]>;
|
getSearchPropertiesByUserId(userId: string): Promise<SearchPropertiesDto[]>;
|
||||||
getAssetCountByTimeBucket(userId: string, timeBucket: TimeGroupEnum): Promise<AssetCountByTimeBucket[]>;
|
getAssetCountByTimeBucket(userId: string, timeBucket: TimeGroupEnum): Promise<AssetCountByTimeBucket[]>;
|
||||||
|
getAssetCountByUserId(userId: string): Promise<AssetCountByUserIdResponseDto>;
|
||||||
getAssetByTimeBucket(userId: string, getAssetByTimeBucketDto: GetAssetByTimeBucketDto): Promise<AssetEntity[]>;
|
getAssetByTimeBucket(userId: string, getAssetByTimeBucketDto: GetAssetByTimeBucketDto): Promise<AssetEntity[]>;
|
||||||
getAssetByChecksum(userId: string, checksum: Buffer): Promise<AssetEntity>;
|
getAssetByChecksum(userId: string, checksum: Buffer): Promise<AssetEntity>;
|
||||||
}
|
}
|
||||||
@ -38,6 +40,28 @@ export class AssetRepository implements IAssetRepository {
|
|||||||
private assetRepository: Repository<AssetEntity>,
|
private assetRepository: Repository<AssetEntity>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
async getAssetCountByUserId(userId: string): Promise<AssetCountByUserIdResponseDto> {
|
||||||
|
// Get asset count by AssetType
|
||||||
|
const res = await this.assetRepository
|
||||||
|
.createQueryBuilder('asset')
|
||||||
|
.select(`COUNT(asset.id)`, 'count')
|
||||||
|
.addSelect(`asset.type`, 'type')
|
||||||
|
.where('"userId" = :userId', { userId: userId })
|
||||||
|
.groupBy('asset.type')
|
||||||
|
.getRawMany();
|
||||||
|
|
||||||
|
const assetCountByUserId = new AssetCountByUserIdResponseDto(0, 0);
|
||||||
|
res.map((item) => {
|
||||||
|
if (item.type === 'IMAGE') {
|
||||||
|
assetCountByUserId.photos = item.count;
|
||||||
|
} else if (item.type === 'VIDEO') {
|
||||||
|
assetCountByUserId.videos = item.count;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return assetCountByUserId;
|
||||||
|
}
|
||||||
|
|
||||||
async getAssetByTimeBucket(userId: string, getAssetByTimeBucketDto: GetAssetByTimeBucketDto): Promise<AssetEntity[]> {
|
async getAssetByTimeBucket(userId: string, getAssetByTimeBucketDto: GetAssetByTimeBucketDto): Promise<AssetEntity[]> {
|
||||||
// Get asset entity from a list of time buckets
|
// Get asset entity from a list of time buckets
|
||||||
return await this.assetRepository
|
return await this.assetRepository
|
||||||
|
@ -48,6 +48,7 @@ import { AssetCountByTimeBucketResponseDto } from './response-dto/asset-count-by
|
|||||||
import { GetAssetCountByTimeBucketDto } from './dto/get-asset-count-by-time-bucket.dto';
|
import { GetAssetCountByTimeBucketDto } from './dto/get-asset-count-by-time-bucket.dto';
|
||||||
import { GetAssetByTimeBucketDto } from './dto/get-asset-by-time-bucket.dto';
|
import { GetAssetByTimeBucketDto } from './dto/get-asset-by-time-bucket.dto';
|
||||||
import { QueryFailedError } from 'typeorm';
|
import { QueryFailedError } from 'typeorm';
|
||||||
|
import { AssetCountByUserIdResponseDto } from './response-dto/asset-count-by-user-id-response.dto';
|
||||||
|
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@ -78,7 +79,13 @@ export class AssetController {
|
|||||||
const checksum = await this.assetService.calculateChecksum(file.path);
|
const checksum = await this.assetService.calculateChecksum(file.path);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const savedAsset = await this.assetService.createUserAsset(authUser, assetInfo, file.path, file.mimetype, checksum);
|
const savedAsset = await this.assetService.createUserAsset(
|
||||||
|
authUser,
|
||||||
|
assetInfo,
|
||||||
|
file.path,
|
||||||
|
file.mimetype,
|
||||||
|
checksum,
|
||||||
|
);
|
||||||
|
|
||||||
if (!savedAsset) {
|
if (!savedAsset) {
|
||||||
await this.backgroundTaskService.deleteFileOnDisk([
|
await this.backgroundTaskService.deleteFileOnDisk([
|
||||||
@ -104,7 +111,7 @@ export class AssetController {
|
|||||||
]); // simulate asset to make use of delete queue (or use fs.unlink instead)
|
]); // simulate asset to make use of delete queue (or use fs.unlink instead)
|
||||||
|
|
||||||
if (err instanceof QueryFailedError && (err as any).constraint === 'UQ_userid_checksum') {
|
if (err instanceof QueryFailedError && (err as any).constraint === 'UQ_userid_checksum') {
|
||||||
const existedAsset = await this.assetService.getAssetByChecksum(authUser.id, checksum)
|
const existedAsset = await this.assetService.getAssetByChecksum(authUser.id, checksum);
|
||||||
return new AssetFileUploadResponseDto(existedAsset.id);
|
return new AssetFileUploadResponseDto(existedAsset.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,6 +179,10 @@ export class AssetController {
|
|||||||
return this.assetService.getAssetCountByTimeBucket(authUser, getAssetCountByTimeGroupDto);
|
return this.assetService.getAssetCountByTimeBucket(authUser, getAssetCountByTimeGroupDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get('/count-by-user-id')
|
||||||
|
async getAssetCountByUserId(@GetAuthUser() authUser: AuthUserDto): Promise<AssetCountByUserIdResponseDto> {
|
||||||
|
return this.assetService.getAssetCountByUserId(authUser);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Get all AssetEntity belong to the user
|
* Get all AssetEntity belong to the user
|
||||||
*/
|
*/
|
||||||
|
@ -61,6 +61,7 @@ describe('AssetService', () => {
|
|||||||
getSearchPropertiesByUserId: jest.fn(),
|
getSearchPropertiesByUserId: jest.fn(),
|
||||||
getAssetByTimeBucket: jest.fn(),
|
getAssetByTimeBucket: jest.fn(),
|
||||||
getAssetByChecksum: jest.fn(),
|
getAssetByChecksum: jest.fn(),
|
||||||
|
getAssetCountByUserId: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
sui = new AssetService(assetRepositoryMock, a);
|
sui = new AssetService(assetRepositoryMock, a);
|
||||||
|
@ -35,6 +35,7 @@ import {
|
|||||||
} from './response-dto/asset-count-by-time-group-response.dto';
|
} from './response-dto/asset-count-by-time-group-response.dto';
|
||||||
import { GetAssetCountByTimeBucketDto } from './dto/get-asset-count-by-time-bucket.dto';
|
import { GetAssetCountByTimeBucketDto } from './dto/get-asset-count-by-time-bucket.dto';
|
||||||
import { GetAssetByTimeBucketDto } from './dto/get-asset-by-time-bucket.dto';
|
import { GetAssetByTimeBucketDto } from './dto/get-asset-by-time-bucket.dto';
|
||||||
|
import { AssetCountByUserIdResponseDto } from './response-dto/asset-count-by-user-id-response.dto';
|
||||||
|
|
||||||
const fileInfo = promisify(stat);
|
const fileInfo = promisify(stat);
|
||||||
|
|
||||||
@ -479,4 +480,8 @@ export class AssetService {
|
|||||||
fileReadStream.pipe(sha1Hash);
|
fileReadStream.pipe(sha1Hash);
|
||||||
return deferred;
|
return deferred;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAssetCountByUserId(authUser: AuthUserDto): Promise<AssetCountByUserIdResponseDto> {
|
||||||
|
return this._assetRepository.getAssetCountByUserId(authUser.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class AssetCountByUserIdResponseDto {
|
||||||
|
@ApiProperty({ type: 'integer' })
|
||||||
|
photos!: number;
|
||||||
|
|
||||||
|
@ApiProperty({ type: 'integer' })
|
||||||
|
videos!: number;
|
||||||
|
|
||||||
|
constructor(photos: number, videos: number) {
|
||||||
|
this.photos = photos;
|
||||||
|
this.videos = videos;
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
@ -84,6 +84,31 @@ export interface AdminSignupResponseDto {
|
|||||||
*/
|
*/
|
||||||
'createdAt': string;
|
'createdAt': string;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface AlbumCountResponseDto
|
||||||
|
*/
|
||||||
|
export interface AlbumCountResponseDto {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof AlbumCountResponseDto
|
||||||
|
*/
|
||||||
|
'owned': number;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof AlbumCountResponseDto
|
||||||
|
*/
|
||||||
|
'shared': number;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof AlbumCountResponseDto
|
||||||
|
*/
|
||||||
|
'sharing': number;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
@ -183,6 +208,25 @@ export interface AssetCountByTimeBucketResponseDto {
|
|||||||
*/
|
*/
|
||||||
'buckets': Array<AssetCountByTimeBucket>;
|
'buckets': Array<AssetCountByTimeBucket>;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface AssetCountByUserIdResponseDto
|
||||||
|
*/
|
||||||
|
export interface AssetCountByUserIdResponseDto {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof AssetCountByUserIdResponseDto
|
||||||
|
*/
|
||||||
|
'photos': number;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof AssetCountByUserIdResponseDto
|
||||||
|
*/
|
||||||
|
'videos': number;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
@ -1408,6 +1452,39 @@ export const AlbumApiAxiosParamCreator = function (configuration?: Configuration
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
|
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
getAlbumCountByUserId: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
|
const localVarPath = `/album/count-by-user-id`;
|
||||||
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||||
|
let baseOptions;
|
||||||
|
if (configuration) {
|
||||||
|
baseOptions = configuration.baseOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||||
|
const localVarHeaderParameter = {} as any;
|
||||||
|
const localVarQueryParameter = {} as any;
|
||||||
|
|
||||||
|
// authentication bearer required
|
||||||
|
// http bearer authentication required
|
||||||
|
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||||
@ -1676,6 +1753,15 @@ export const AlbumApiFp = function(configuration?: Configuration) {
|
|||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.deleteAlbum(albumId, options);
|
const localVarAxiosArgs = await localVarAxiosParamCreator.deleteAlbum(albumId, options);
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async getAlbumCountByUserId(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AlbumCountResponseDto>> {
|
||||||
|
const localVarAxiosArgs = await localVarAxiosParamCreator.getAlbumCountByUserId(options);
|
||||||
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {string} albumId
|
* @param {string} albumId
|
||||||
@ -1778,6 +1864,14 @@ export const AlbumApiFactory = function (configuration?: Configuration, basePath
|
|||||||
deleteAlbum(albumId: string, options?: any): AxiosPromise<void> {
|
deleteAlbum(albumId: string, options?: any): AxiosPromise<void> {
|
||||||
return localVarFp.deleteAlbum(albumId, options).then((request) => request(axios, basePath));
|
return localVarFp.deleteAlbum(albumId, options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
getAlbumCountByUserId(options?: any): AxiosPromise<AlbumCountResponseDto> {
|
||||||
|
return localVarFp.getAlbumCountByUserId(options).then((request) => request(axios, basePath));
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {string} albumId
|
* @param {string} albumId
|
||||||
@ -1883,6 +1977,16 @@ export class AlbumApi extends BaseAPI {
|
|||||||
return AlbumApiFp(this.configuration).deleteAlbum(albumId, options).then((request) => request(this.axios, this.basePath));
|
return AlbumApiFp(this.configuration).deleteAlbum(albumId, options).then((request) => request(this.axios, this.basePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
* @memberof AlbumApi
|
||||||
|
*/
|
||||||
|
public getAlbumCountByUserId(options?: AxiosRequestConfig) {
|
||||||
|
return AlbumApiFp(this.configuration).getAlbumCountByUserId(options).then((request) => request(this.axios, this.basePath));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {string} albumId
|
* @param {string} albumId
|
||||||
@ -2236,6 +2340,39 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
|||||||
options: localVarRequestOptions,
|
options: localVarRequestOptions,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
getAssetCountByUserId: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
|
const localVarPath = `/asset/count-by-user-id`;
|
||||||
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||||
|
let baseOptions;
|
||||||
|
if (configuration) {
|
||||||
|
baseOptions = configuration.baseOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||||
|
const localVarHeaderParameter = {} as any;
|
||||||
|
const localVarQueryParameter = {} as any;
|
||||||
|
|
||||||
|
// authentication bearer required
|
||||||
|
// http bearer authentication required
|
||||||
|
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
|
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions,
|
||||||
|
};
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
@ -2640,6 +2777,15 @@ export const AssetApiFp = function(configuration?: Configuration) {
|
|||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetCountByTimeBucket(getAssetCountByTimeBucketDto, options);
|
const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetCountByTimeBucket(getAssetCountByTimeBucketDto, options);
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async getAssetCountByUserId(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AssetCountByUserIdResponseDto>> {
|
||||||
|
const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetCountByUserId(options);
|
||||||
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
@ -2800,6 +2946,14 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
|
|||||||
getAssetCountByTimeBucket(getAssetCountByTimeBucketDto: GetAssetCountByTimeBucketDto, options?: any): AxiosPromise<AssetCountByTimeBucketResponseDto> {
|
getAssetCountByTimeBucket(getAssetCountByTimeBucketDto: GetAssetCountByTimeBucketDto, options?: any): AxiosPromise<AssetCountByTimeBucketResponseDto> {
|
||||||
return localVarFp.getAssetCountByTimeBucket(getAssetCountByTimeBucketDto, options).then((request) => request(axios, basePath));
|
return localVarFp.getAssetCountByTimeBucket(getAssetCountByTimeBucketDto, options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
getAssetCountByUserId(options?: any): AxiosPromise<AssetCountByUserIdResponseDto> {
|
||||||
|
return localVarFp.getAssetCountByUserId(options).then((request) => request(axios, basePath));
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
@ -2966,6 +3120,16 @@ export class AssetApi extends BaseAPI {
|
|||||||
return AssetApiFp(this.configuration).getAssetCountByTimeBucket(getAssetCountByTimeBucketDto, options).then((request) => request(this.axios, this.basePath));
|
return AssetApiFp(this.configuration).getAssetCountByTimeBucket(getAssetCountByTimeBucketDto, options).then((request) => request(this.axios, this.basePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
* @memberof AssetApi
|
||||||
|
*/
|
||||||
|
public getAssetCountByUserId(options?: AxiosRequestConfig) {
|
||||||
|
return AssetApiFp(this.configuration).getAssetCountByUserId(options).then((request) => request(this.axios, this.basePath));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
|
@ -5,11 +5,19 @@
|
|||||||
import ImageAlbum from 'svelte-material-icons/ImageAlbum.svelte';
|
import ImageAlbum from 'svelte-material-icons/ImageAlbum.svelte';
|
||||||
import ImageOutline from 'svelte-material-icons/ImageOutline.svelte';
|
import ImageOutline from 'svelte-material-icons/ImageOutline.svelte';
|
||||||
import AccountMultipleOutline from 'svelte-material-icons/AccountMultipleOutline.svelte';
|
import AccountMultipleOutline from 'svelte-material-icons/AccountMultipleOutline.svelte';
|
||||||
|
import InformationOutline from 'svelte-material-icons/InformationOutline.svelte';
|
||||||
import SideBarButton from './side-bar-button.svelte';
|
import SideBarButton from './side-bar-button.svelte';
|
||||||
import StatusBox from '../status-box.svelte';
|
import StatusBox from '../status-box.svelte';
|
||||||
|
import { AlbumCountResponseDto, api, AssetCountByUserIdResponseDto } from '@api';
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
import LoadingSpinner from '../loading-spinner.svelte';
|
||||||
|
|
||||||
let selectedAction: AppSideBarSelection;
|
let selectedAction: AppSideBarSelection;
|
||||||
|
|
||||||
|
let showAssetCount: boolean = false;
|
||||||
|
let showSharingCount = false;
|
||||||
|
let showAlbumsCount = false;
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if ($page.routeId == 'albums') {
|
if ($page.routeId == 'albums') {
|
||||||
selectedAction = AppSideBarSelection.ALBUMS;
|
selectedAction = AppSideBarSelection.ALBUMS;
|
||||||
@ -19,35 +27,130 @@
|
|||||||
selectedAction = AppSideBarSelection.SHARING;
|
selectedAction = AppSideBarSelection.SHARING;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getAssetCount = async () => {
|
||||||
|
const { data: assetCount } = await api.assetApi.getAssetCountByUserId();
|
||||||
|
|
||||||
|
return {
|
||||||
|
videos: assetCount.videos,
|
||||||
|
photos: assetCount.photos
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAlbumCount = async () => {
|
||||||
|
const { data: albumCount } = await api.albumApi.getAlbumCountByUserId();
|
||||||
|
return {
|
||||||
|
shared: albumCount.shared,
|
||||||
|
sharing: albumCount.sharing,
|
||||||
|
owned: albumCount.owned
|
||||||
|
};
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section id="sidebar" class="flex flex-col gap-1 pt-8 pr-6">
|
<section id="sidebar" class="flex flex-col gap-1 pt-8 pr-6">
|
||||||
<a sveltekit:prefetch sveltekit:noscroll href={$page.routeId !== 'photos' ? `/photos` : null}>
|
<a
|
||||||
|
sveltekit:prefetch
|
||||||
|
sveltekit:noscroll
|
||||||
|
href={$page.routeId !== 'photos' ? `/photos` : null}
|
||||||
|
class="relative"
|
||||||
|
>
|
||||||
<SideBarButton
|
<SideBarButton
|
||||||
title="Photos"
|
title={`Photos`}
|
||||||
logo={ImageOutline}
|
logo={ImageOutline}
|
||||||
actionType={AppSideBarSelection.PHOTOS}
|
actionType={AppSideBarSelection.PHOTOS}
|
||||||
isSelected={selectedAction === AppSideBarSelection.PHOTOS}
|
isSelected={selectedAction === AppSideBarSelection.PHOTOS}
|
||||||
/></a
|
/>
|
||||||
>
|
<div
|
||||||
<a sveltekit:prefetch href={$page.routeId !== 'sharing' ? `/sharing` : null}>
|
id="asset-count-info"
|
||||||
|
class="absolute right-4 top-[15px] z-40 text-xs hover:cursor-help"
|
||||||
|
on:mouseenter={() => (showAssetCount = true)}
|
||||||
|
on:mouseleave={() => (showAssetCount = false)}
|
||||||
|
>
|
||||||
|
<InformationOutline size={18} color="#4250af" />
|
||||||
|
{#if showAssetCount}
|
||||||
|
<div
|
||||||
|
transition:fade={{ duration: 200 }}
|
||||||
|
id="asset-count-info-detail"
|
||||||
|
class="w-32 rounded-lg px-4 py-2 shadow-lg bg-white absolute -right-[135px] top-0 z-[9999] flex place-items-center place-content-center"
|
||||||
|
>
|
||||||
|
{#await getAssetCount()}
|
||||||
|
<LoadingSpinner />
|
||||||
|
{:then data}
|
||||||
|
<div>
|
||||||
|
<p>{data.videos} Videos</p>
|
||||||
|
<p>{data.photos} Photos</p>
|
||||||
|
</div>
|
||||||
|
{/await}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a sveltekit:prefetch href={$page.routeId !== 'sharing' ? `/sharing` : null} class="relative">
|
||||||
<SideBarButton
|
<SideBarButton
|
||||||
title="Sharing"
|
title="Sharing"
|
||||||
logo={AccountMultipleOutline}
|
logo={AccountMultipleOutline}
|
||||||
actionType={AppSideBarSelection.SHARING}
|
actionType={AppSideBarSelection.SHARING}
|
||||||
isSelected={selectedAction === AppSideBarSelection.SHARING}
|
isSelected={selectedAction === AppSideBarSelection.SHARING}
|
||||||
/></a
|
/>
|
||||||
>
|
<div
|
||||||
|
id="sharing-count-info"
|
||||||
|
class="absolute right-4 top-[15px] z-40 text-xs hover:cursor-help"
|
||||||
|
on:mouseenter={() => (showSharingCount = true)}
|
||||||
|
on:mouseleave={() => (showSharingCount = false)}
|
||||||
|
>
|
||||||
|
<InformationOutline size={18} color="#4250af" />
|
||||||
|
{#if showSharingCount}
|
||||||
|
<div
|
||||||
|
transition:fade={{ duration: 200 }}
|
||||||
|
id="asset-count-info-detail"
|
||||||
|
class="w-32 rounded-lg px-4 py-2 shadow-lg bg-white absolute -right-[135px] top-0 z-[9999] flex place-items-center place-content-center"
|
||||||
|
>
|
||||||
|
{#await getAlbumCount()}
|
||||||
|
<LoadingSpinner />
|
||||||
|
{:then data}
|
||||||
|
<div>
|
||||||
|
<p>{data.shared + data.sharing} albums</p>
|
||||||
|
</div>
|
||||||
|
{/await}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
<div class="text-xs ml-5 my-4">
|
<div class="text-xs ml-5 my-4">
|
||||||
<p>LIBRARY</p>
|
<p>LIBRARY</p>
|
||||||
</div>
|
</div>
|
||||||
<a sveltekit:prefetch href={$page.routeId !== 'albums' ? `/albums` : null}>
|
<a sveltekit:prefetch href={$page.routeId !== 'albums' ? `/albums` : null} class="relative">
|
||||||
<SideBarButton
|
<SideBarButton
|
||||||
title="Albums"
|
title="Albums"
|
||||||
logo={ImageAlbum}
|
logo={ImageAlbum}
|
||||||
actionType={AppSideBarSelection.ALBUMS}
|
actionType={AppSideBarSelection.ALBUMS}
|
||||||
isSelected={selectedAction === AppSideBarSelection.ALBUMS}
|
isSelected={selectedAction === AppSideBarSelection.ALBUMS}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="album-count-info"
|
||||||
|
class="absolute right-4 top-[15px] z-40 text-xs hover:cursor-help"
|
||||||
|
on:mouseenter={() => (showAlbumsCount = true)}
|
||||||
|
on:mouseleave={() => (showAlbumsCount = false)}
|
||||||
|
>
|
||||||
|
<InformationOutline size={18} color="#4250af" />
|
||||||
|
{#if showAlbumsCount}
|
||||||
|
<div
|
||||||
|
transition:fade={{ duration: 200 }}
|
||||||
|
id="asset-count-info-detail"
|
||||||
|
class="w-32 rounded-lg px-4 py-2 shadow-lg bg-white absolute -right-[135px] top-0 z-[9999] flex place-items-center place-content-center"
|
||||||
|
>
|
||||||
|
{#await getAlbumCount()}
|
||||||
|
<LoadingSpinner />
|
||||||
|
{:then data}
|
||||||
|
<div>
|
||||||
|
<p>{data.owned} albums</p>
|
||||||
|
</div>
|
||||||
|
{/await}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!-- Status Box -->
|
<!-- Status Box -->
|
||||||
|
@ -26,14 +26,6 @@
|
|||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
openWebsocketConnection();
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
closeWebsocketConnection();
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const deleteSelectedAssetHandler = async () => {
|
const deleteSelectedAssetHandler = async () => {
|
||||||
try {
|
try {
|
||||||
if (
|
if (
|
||||||
|
Loading…
Reference in New Issue
Block a user