1
0
forked from Cutlery/immich

feat(web): show assets without thumbs (#2561)

* feat(web): show assets without thumbnails

* chore: open api
This commit is contained in:
Jason Rasmussen 2023-05-24 22:13:02 -04:00 committed by GitHub
parent d827a6182b
commit 1613ae9185
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 86 additions and 19 deletions

View File

@ -10,6 +10,7 @@ Name | Type | Description | Notes
------------ | ------------- | ------------- | ------------- ------------ | ------------- | ------------- | -------------
**timeBucket** | **List<String>** | | [default to const []] **timeBucket** | **List<String>** | | [default to const []]
**userId** | **String** | | [optional] **userId** | **String** | | [optional]
**withoutThumbs** | **bool** | Include assets without thumbnails | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@ -15,6 +15,7 @@ class GetAssetByTimeBucketDto {
GetAssetByTimeBucketDto({ GetAssetByTimeBucketDto({
this.timeBucket = const [], this.timeBucket = const [],
this.userId, this.userId,
this.withoutThumbs,
}); });
List<String> timeBucket; List<String> timeBucket;
@ -27,19 +28,30 @@ class GetAssetByTimeBucketDto {
/// ///
String? userId; String? userId;
/// Include assets without thumbnails
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? withoutThumbs;
@override @override
bool operator ==(Object other) => identical(this, other) || other is GetAssetByTimeBucketDto && bool operator ==(Object other) => identical(this, other) || other is GetAssetByTimeBucketDto &&
other.timeBucket == timeBucket && other.timeBucket == timeBucket &&
other.userId == userId; other.userId == userId &&
other.withoutThumbs == withoutThumbs;
@override @override
int get hashCode => int get hashCode =>
// ignore: unnecessary_parenthesis // ignore: unnecessary_parenthesis
(timeBucket.hashCode) + (timeBucket.hashCode) +
(userId == null ? 0 : userId!.hashCode); (userId == null ? 0 : userId!.hashCode) +
(withoutThumbs == null ? 0 : withoutThumbs!.hashCode);
@override @override
String toString() => 'GetAssetByTimeBucketDto[timeBucket=$timeBucket, userId=$userId]'; String toString() => 'GetAssetByTimeBucketDto[timeBucket=$timeBucket, userId=$userId, withoutThumbs=$withoutThumbs]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@ -49,6 +61,11 @@ class GetAssetByTimeBucketDto {
} else { } else {
// json[r'userId'] = null; // json[r'userId'] = null;
} }
if (this.withoutThumbs != null) {
json[r'withoutThumbs'] = this.withoutThumbs;
} else {
// json[r'withoutThumbs'] = null;
}
return json; return json;
} }
@ -75,6 +92,7 @@ class GetAssetByTimeBucketDto {
? (json[r'timeBucket'] as Iterable).cast<String>().toList(growable: false) ? (json[r'timeBucket'] as Iterable).cast<String>().toList(growable: false)
: const [], : const [],
userId: mapValueOfType<String>(json, r'userId'), userId: mapValueOfType<String>(json, r'userId'),
withoutThumbs: mapValueOfType<bool>(json, r'withoutThumbs'),
); );
} }
return null; return null;

View File

@ -26,6 +26,12 @@ void main() {
// TODO // TODO
}); });
// Include assets without thumbnails
// bool withoutThumbs
test('to test the property `withoutThumbs`', () async {
// TODO
});
}); });

View File

@ -104,19 +104,23 @@ export class AssetRepository implements IAssetRepository {
return this.getAssetCount(items); return this.getAssetCount(items);
} }
async getAssetByTimeBucket(userId: string, getAssetByTimeBucketDto: GetAssetByTimeBucketDto): Promise<AssetEntity[]> { async getAssetByTimeBucket(userId: string, dto: 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 let builder = this.assetRepository
.createQueryBuilder('asset') .createQueryBuilder('asset')
.where('asset.ownerId = :userId', { userId: userId }) .where('asset.ownerId = :userId', { userId: userId })
.andWhere(`date_trunc('month', "fileCreatedAt") IN (:...buckets)`, { .andWhere(`date_trunc('month', "fileCreatedAt") IN (:...buckets)`, {
buckets: [...getAssetByTimeBucketDto.timeBucket], buckets: [...dto.timeBucket],
}) })
.andWhere('asset.resizePath is not NULL')
.andWhere('asset.isVisible = true') .andWhere('asset.isVisible = true')
.andWhere('asset.isArchived = false') .andWhere('asset.isArchived = false')
.orderBy('asset.fileCreatedAt', 'DESC') .orderBy('asset.fileCreatedAt', 'DESC');
.getMany();
if (!dto.withoutThumbs) {
builder = builder.andWhere('asset.resizePath is not NULL');
}
return builder.getMany();
} }
async getAssetCountByTimeBucket(userId: string, timeBucket: TimeGroupEnum) { async getAssetCountByTimeBucket(userId: string, timeBucket: TimeGroupEnum) {

View File

@ -1,5 +1,7 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsOptional, IsUUID } from 'class-validator'; import { Transform } from 'class-transformer';
import { IsBoolean, IsNotEmpty, IsOptional, IsUUID } from 'class-validator';
import { toBoolean } from '../../../utils/transform.util';
export class GetAssetByTimeBucketDto { export class GetAssetByTimeBucketDto {
@IsNotEmpty() @IsNotEmpty()
@ -15,4 +17,12 @@ export class GetAssetByTimeBucketDto {
@IsUUID('4') @IsUUID('4')
@ApiProperty({ format: 'uuid' }) @ApiProperty({ format: 'uuid' })
userId?: string; userId?: string;
/**
* Include assets without thumbnails
*/
@IsOptional()
@IsBoolean()
@Transform(toBoolean)
withoutThumbs?: boolean;
} }

View File

@ -5988,6 +5988,10 @@
"userId": { "userId": {
"type": "string", "type": "string",
"format": "uuid" "format": "uuid"
},
"withoutThumbs": {
"type": "boolean",
"description": "Include assets without thumbnails"
} }
}, },
"required": [ "required": [

View File

@ -1330,6 +1330,12 @@ export interface GetAssetByTimeBucketDto {
* @memberof GetAssetByTimeBucketDto * @memberof GetAssetByTimeBucketDto
*/ */
'userId'?: string; 'userId'?: string;
/**
* Include assets without thumbnails
* @type {boolean}
* @memberof GetAssetByTimeBucketDto
*/
'withoutThumbs'?: boolean;
} }
/** /**
* *

View File

@ -11,6 +11,7 @@
import { createEventDispatcher, onDestroy, onMount } from 'svelte'; import { createEventDispatcher, onDestroy, onMount } from 'svelte';
import ChevronLeft from 'svelte-material-icons/ChevronLeft.svelte'; import ChevronLeft from 'svelte-material-icons/ChevronLeft.svelte';
import ChevronRight from 'svelte-material-icons/ChevronRight.svelte'; import ChevronRight from 'svelte-material-icons/ChevronRight.svelte';
import ImageBrokenVariant from 'svelte-material-icons/ImageBrokenVariant.svelte';
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
import AlbumSelectionModal from '../shared-components/album-selection-modal.svelte'; import AlbumSelectionModal from '../shared-components/album-selection-modal.svelte';
import { import {
@ -350,7 +351,15 @@
<div class="row-start-1 row-span-full col-start-1 col-span-4"> <div class="row-start-1 row-span-full col-start-1 col-span-4">
{#key asset.id} {#key asset.id}
{#if asset.type === AssetTypeEnum.Image} {#if !asset.resizePath}
<div class="h-full w-full flex justify-center">
<div
class="h-full bg-gray-100 dark:bg-immich-dark-gray flex items-center justify-center aspect-square px-auto"
>
<ImageBrokenVariant size="25%" />
</div>
</div>
{:else if asset.type === AssetTypeEnum.Image}
{#if shouldPlayMotionPhoto && asset.livePhotoVideoId} {#if shouldPlayMotionPhoto && asset.livePhotoVideoId}
<VideoViewer <VideoViewer
{publicSharedKey} {publicSharedKey}

View File

@ -10,6 +10,7 @@
import ArchiveArrowDownOutline from 'svelte-material-icons/ArchiveArrowDownOutline.svelte'; import ArchiveArrowDownOutline from 'svelte-material-icons/ArchiveArrowDownOutline.svelte';
import ImageThumbnail from './image-thumbnail.svelte'; import ImageThumbnail from './image-thumbnail.svelte';
import VideoThumbnail from './video-thumbnail.svelte'; import VideoThumbnail from './video-thumbnail.svelte';
import ImageBrokenVariant from 'svelte-material-icons/ImageBrokenVariant.svelte';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
@ -101,7 +102,7 @@
</div> </div>
<div <div
class="bg-gray-100 dark:bg-immich-dark-gray absolute select-none transition-transform" class="h-full w-full bg-gray-100 dark:bg-immich-dark-gray absolute select-none transition-transform"
class:scale-[0.85]={selected} class:scale-[0.85]={selected}
> >
<!-- Gradient overlay on hover --> <!-- Gradient overlay on hover -->
@ -121,12 +122,19 @@
<ArchiveArrowDownOutline size="24" class="text-white" /> <ArchiveArrowDownOutline size="24" class="text-white" />
</div> </div>
{/if} {/if}
<ImageThumbnail
url={api.getAssetThumbnailUrl(asset.id, format, publicSharedKey)} {#if asset.resizePath}
altText={asset.originalFileName} <ImageThumbnail
widthStyle="{width}px" url={api.getAssetThumbnailUrl(asset.id, format, publicSharedKey)}
heightStyle="{height}px" altText={asset.originalFileName}
/> widthStyle="{width}px"
heightStyle="{height}px"
/>
{:else}
<div class="w-full h-full p-4 flex items-center justify-center">
<ImageBrokenVariant size="48" />
</div>
{/if}
{#if asset.type === AssetTypeEnum.Video} {#if asset.type === AssetTypeEnum.Video}
<div class="absolute w-full h-full top-0"> <div class="absolute w-full h-full top-0">

View File

@ -67,7 +67,8 @@ function createAssetStore() {
const { data: assets } = await api.assetApi.getAssetByTimeBucket( const { data: assets } = await api.assetApi.getAssetByTimeBucket(
{ {
timeBucket: [bucket], timeBucket: [bucket],
userId: _assetGridState.userId userId: _assetGridState.userId,
withoutThumbs: true
}, },
{ signal: currentBucketData?.cancelToken.signal } { signal: currentBucketData?.cancelToken.signal }
); );