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:
parent
d827a6182b
commit
1613ae9185
1
mobile/openapi/doc/GetAssetByTimeBucketDto.md
generated
1
mobile/openapi/doc/GetAssetByTimeBucketDto.md
generated
@ -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)
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -26,6 +26,12 @@ void main() {
|
|||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Include assets without thumbnails
|
||||||
|
// bool withoutThumbs
|
||||||
|
test('to test the property `withoutThumbs`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -5988,6 +5988,10 @@
|
|||||||
"userId": {
|
"userId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "uuid"
|
"format": "uuid"
|
||||||
|
},
|
||||||
|
"withoutThumbs": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Include assets without thumbnails"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
6
web/src/api/open-api/api.ts
generated
6
web/src/api/open-api/api.ts
generated
@ -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;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -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}
|
||||||
|
@ -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">
|
||||||
|
@ -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 }
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user