mirror of
https://github.com/immich-app/immich.git
synced 2024-11-24 08:52:28 +02:00
fix(web): new feature photo (#9443)
* fix: new feature photo * fix: use updatedAt
This commit is contained in:
parent
325aa1d392
commit
37b5d92110
BIN
mobile/openapi/lib/model/person_response_dto.dart
generated
BIN
mobile/openapi/lib/model/person_response_dto.dart
generated
Binary file not shown.
Binary file not shown.
@ -9345,6 +9345,11 @@
|
|||||||
},
|
},
|
||||||
"thumbnailPath": {
|
"thumbnailPath": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"description": "This property was added in v1.107.0",
|
||||||
|
"format": "date-time",
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
@ -9414,6 +9419,11 @@
|
|||||||
},
|
},
|
||||||
"thumbnailPath": {
|
"thumbnailPath": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"description": "This property was added in v1.107.0",
|
||||||
|
"format": "date-time",
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
@ -158,6 +158,8 @@ export type PersonWithFacesResponseDto = {
|
|||||||
isHidden: boolean;
|
isHidden: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
thumbnailPath: string;
|
thumbnailPath: string;
|
||||||
|
/** This property was added in v1.107.0 */
|
||||||
|
updatedAt?: string;
|
||||||
};
|
};
|
||||||
export type SmartInfoResponseDto = {
|
export type SmartInfoResponseDto = {
|
||||||
objects?: string[] | null;
|
objects?: string[] | null;
|
||||||
@ -432,6 +434,8 @@ export type PersonResponseDto = {
|
|||||||
isHidden: boolean;
|
isHidden: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
thumbnailPath: string;
|
thumbnailPath: string;
|
||||||
|
/** This property was added in v1.107.0 */
|
||||||
|
updatedAt?: string;
|
||||||
};
|
};
|
||||||
export type AssetFaceResponseDto = {
|
export type AssetFaceResponseDto = {
|
||||||
boundingBoxX1: number;
|
boundingBoxX1: number;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
import { IsArray, IsNotEmpty, IsString, MaxDate, ValidateNested } from 'class-validator';
|
import { IsArray, IsNotEmpty, IsString, MaxDate, ValidateNested } from 'class-validator';
|
||||||
|
import { PropertyLifecycle } from 'src/decorators';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
|
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
|
||||||
import { PersonEntity } from 'src/entities/person.entity';
|
import { PersonEntity } from 'src/entities/person.entity';
|
||||||
@ -71,6 +72,8 @@ export class PersonResponseDto {
|
|||||||
birthDate!: Date | null;
|
birthDate!: Date | null;
|
||||||
thumbnailPath!: string;
|
thumbnailPath!: string;
|
||||||
isHidden!: boolean;
|
isHidden!: boolean;
|
||||||
|
@PropertyLifecycle({ addedAt: 'v1.107.0' })
|
||||||
|
updatedAt?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PersonWithFacesResponseDto extends PersonResponseDto {
|
export class PersonWithFacesResponseDto extends PersonResponseDto {
|
||||||
@ -138,6 +141,7 @@ export function mapPerson(person: PersonEntity): PersonResponseDto {
|
|||||||
birthDate: person.birthDate,
|
birthDate: person.birthDate,
|
||||||
thumbnailPath: person.thumbnailPath,
|
thumbnailPath: person.thumbnailPath,
|
||||||
isHidden: person.isHidden,
|
isHidden: person.isHidden,
|
||||||
|
updatedAt: person.updatedAt,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@ const responseDto: PersonResponseDto = {
|
|||||||
birthDate: null,
|
birthDate: null,
|
||||||
thumbnailPath: '/path/to/thumbnail.jpg',
|
thumbnailPath: '/path/to/thumbnail.jpg',
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
|
updatedAt: expect.any(Date),
|
||||||
};
|
};
|
||||||
|
|
||||||
const statistics = { assets: 3 };
|
const statistics = { assets: 3 };
|
||||||
@ -126,6 +127,7 @@ describe(PersonService.name, () => {
|
|||||||
birthDate: null,
|
birthDate: null,
|
||||||
thumbnailPath: '/path/to/thumbnail.jpg',
|
thumbnailPath: '/path/to/thumbnail.jpg',
|
||||||
isHidden: true,
|
isHidden: true,
|
||||||
|
updatedAt: expect.any(Date),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -255,6 +257,7 @@ describe(PersonService.name, () => {
|
|||||||
birthDate: new Date('1976-06-30'),
|
birthDate: new Date('1976-06-30'),
|
||||||
thumbnailPath: '/path/to/thumbnail.jpg',
|
thumbnailPath: '/path/to/thumbnail.jpg',
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
|
updatedAt: expect.any(Date),
|
||||||
});
|
});
|
||||||
expect(personMock.update).toHaveBeenCalledWith({ id: 'person-1', birthDate: new Date('1976-06-30') });
|
expect(personMock.update).toHaveBeenCalledWith({ id: 'person-1', birthDate: new Date('1976-06-30') });
|
||||||
expect(jobMock.queue).not.toHaveBeenCalled();
|
expect(jobMock.queue).not.toHaveBeenCalled();
|
||||||
@ -407,6 +410,7 @@ describe(PersonService.name, () => {
|
|||||||
id: personStub.noName.id,
|
id: personStub.noName.id,
|
||||||
name: personStub.noName.name,
|
name: personStub.noName.name,
|
||||||
thumbnailPath: personStub.noName.thumbnailPath,
|
thumbnailPath: personStub.noName.thumbnailPath,
|
||||||
|
updatedAt: expect.any(Date),
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(jobMock.queue).not.toHaveBeenCalledWith();
|
expect(jobMock.queue).not.toHaveBeenCalledWith();
|
||||||
|
@ -213,7 +213,7 @@
|
|||||||
<ImageThumbnail
|
<ImageThumbnail
|
||||||
curve
|
curve
|
||||||
shadow
|
shadow
|
||||||
url={getPeopleThumbnailUrl(person.id)}
|
url={getPeopleThumbnailUrl(person)}
|
||||||
altText={person.name}
|
altText={person.name}
|
||||||
title={person.name}
|
title={person.name}
|
||||||
widthStyle="90px"
|
widthStyle="90px"
|
||||||
|
@ -108,7 +108,7 @@
|
|||||||
<ImageThumbnail
|
<ImageThumbnail
|
||||||
curve
|
curve
|
||||||
shadow
|
shadow
|
||||||
url={getPeopleThumbnailUrl(person.id)}
|
url={getPeopleThumbnailUrl(person)}
|
||||||
altText={getPersonNameWithHiddenValue(person.name, person.isHidden)}
|
altText={getPersonNameWithHiddenValue(person.name, person.isHidden)}
|
||||||
title={getPersonNameWithHiddenValue(person.name, person.isHidden)}
|
title={getPersonNameWithHiddenValue(person.name, person.isHidden)}
|
||||||
widthStyle="90px"
|
widthStyle="90px"
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
class:dark:border-immich-dark-primary={border}
|
class:dark:border-immich-dark-primary={border}
|
||||||
class:border-immich-primary={border}
|
class:border-immich-primary={border}
|
||||||
>
|
>
|
||||||
<ImageThumbnail {circle} url={getPeopleThumbnailUrl(person.id)} altText={person.name} widthStyle="100%" shadow />
|
<ImageThumbnail {circle} url={getPeopleThumbnailUrl(person)} altText={person.name} widthStyle="100%" shadow />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
<ImageThumbnail
|
<ImageThumbnail
|
||||||
circle
|
circle
|
||||||
shadow
|
shadow
|
||||||
url={getPeopleThumbnailUrl(personMerge1.id)}
|
url={getPeopleThumbnailUrl(personMerge1)}
|
||||||
altText={personMerge1.name}
|
altText={personMerge1.name}
|
||||||
widthStyle="100%"
|
widthStyle="100%"
|
||||||
/>
|
/>
|
||||||
@ -65,7 +65,7 @@
|
|||||||
border={potentialMergePeople.length > 0}
|
border={potentialMergePeople.length > 0}
|
||||||
circle
|
circle
|
||||||
shadow
|
shadow
|
||||||
url={getPeopleThumbnailUrl(personMerge2.id)}
|
url={getPeopleThumbnailUrl(personMerge2)}
|
||||||
altText={personMerge2.name}
|
altText={personMerge2.name}
|
||||||
widthStyle="100%"
|
widthStyle="100%"
|
||||||
/>
|
/>
|
||||||
@ -84,7 +84,7 @@
|
|||||||
border={true}
|
border={true}
|
||||||
circle
|
circle
|
||||||
shadow
|
shadow
|
||||||
url={getPeopleThumbnailUrl(person.id)}
|
url={getPeopleThumbnailUrl(person)}
|
||||||
altText={person.name}
|
altText={person.name}
|
||||||
widthStyle="100%"
|
widthStyle="100%"
|
||||||
on:click={() => changePersonToMerge(person)}
|
on:click={() => changePersonToMerge(person)}
|
||||||
|
@ -50,7 +50,7 @@
|
|||||||
<ImageThumbnail
|
<ImageThumbnail
|
||||||
shadow
|
shadow
|
||||||
{preload}
|
{preload}
|
||||||
url={getPeopleThumbnailUrl(person.id)}
|
url={getPeopleThumbnailUrl(person)}
|
||||||
altText={person.name}
|
altText={person.name}
|
||||||
title={person.name}
|
title={person.name}
|
||||||
widthStyle="100%"
|
widthStyle="100%"
|
||||||
|
@ -234,7 +234,7 @@
|
|||||||
<ImageThumbnail
|
<ImageThumbnail
|
||||||
curve
|
curve
|
||||||
shadow
|
shadow
|
||||||
url={getPeopleThumbnailUrl(selectedPersonToReassign[face.id].id)}
|
url={getPeopleThumbnailUrl(selectedPersonToReassign[face.id])}
|
||||||
altText={selectedPersonToReassign[face.id].name}
|
altText={selectedPersonToReassign[face.id].name}
|
||||||
title={getPersonNameWithHiddenValue(
|
title={getPersonNameWithHiddenValue(
|
||||||
selectedPersonToReassign[face.id].name,
|
selectedPersonToReassign[face.id].name,
|
||||||
@ -248,7 +248,7 @@
|
|||||||
<ImageThumbnail
|
<ImageThumbnail
|
||||||
curve
|
curve
|
||||||
shadow
|
shadow
|
||||||
url={getPeopleThumbnailUrl(face.person.id)}
|
url={getPeopleThumbnailUrl(face.person)}
|
||||||
altText={face.person.name}
|
altText={face.person.name}
|
||||||
title={getPersonNameWithHiddenValue(face.person.name, face.person.isHidden)}
|
title={getPersonNameWithHiddenValue(face.person.name, face.person.isHidden)}
|
||||||
widthStyle={thumbnailWidth}
|
widthStyle={thumbnailWidth}
|
||||||
|
@ -71,13 +71,7 @@
|
|||||||
: 'border-transparent'}"
|
: 'border-transparent'}"
|
||||||
on:click={() => togglePersonSelection(person.id)}
|
on:click={() => togglePersonSelection(person.id)}
|
||||||
>
|
>
|
||||||
<ImageThumbnail
|
<ImageThumbnail circle shadow url={getPeopleThumbnailUrl(person)} altText={person.name} widthStyle="100%" />
|
||||||
circle
|
|
||||||
shadow
|
|
||||||
url={getPeopleThumbnailUrl(person.id)}
|
|
||||||
altText={person.name}
|
|
||||||
widthStyle="100%"
|
|
||||||
/>
|
|
||||||
<p class="mt-2 line-clamp-2 text-sm font-medium dark:text-white">{person.name}</p>
|
<p class="mt-2 line-clamp-2 text-sm font-medium dark:text-white">{person.name}</p>
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -621,6 +621,7 @@
|
|||||||
"unable_to_save_settings": "Unable to save settings",
|
"unable_to_save_settings": "Unable to save settings",
|
||||||
"unable_to_scan_libraries": "Unable to scan libraries",
|
"unable_to_scan_libraries": "Unable to scan libraries",
|
||||||
"unable_to_scan_library": "Unable to scan library",
|
"unable_to_scan_library": "Unable to scan library",
|
||||||
|
"unable_to_set_feature_photo": "Unable to set feature photo",
|
||||||
"unable_to_set_profile_picture": "Unable to set profile picture",
|
"unable_to_set_profile_picture": "Unable to set profile picture",
|
||||||
"unable_to_submit_job": "Unable to submit job",
|
"unable_to_submit_job": "Unable to submit job",
|
||||||
"unable_to_trash_asset": "Unable to trash asset",
|
"unable_to_trash_asset": "Unable to trash asset",
|
||||||
|
@ -17,6 +17,7 @@ import {
|
|||||||
startOAuth,
|
startOAuth,
|
||||||
unlinkOAuthAccount,
|
unlinkOAuthAccount,
|
||||||
type AssetResponseDto,
|
type AssetResponseDto,
|
||||||
|
type PersonResponseDto,
|
||||||
type SharedLinkResponseDto,
|
type SharedLinkResponseDto,
|
||||||
} from '@immich/sdk';
|
} from '@immich/sdk';
|
||||||
import { mdiCogRefreshOutline, mdiDatabaseRefreshOutline, mdiImageRefreshOutline } from '@mdi/js';
|
import { mdiCogRefreshOutline, mdiDatabaseRefreshOutline, mdiImageRefreshOutline } from '@mdi/js';
|
||||||
@ -205,7 +206,8 @@ export const getAssetPlaybackUrl = (options: string | { id: string; checksum?: s
|
|||||||
|
|
||||||
export const getProfileImageUrl = (userId: string) => createUrl(getUserProfileImagePath(userId));
|
export const getProfileImageUrl = (userId: string) => createUrl(getUserProfileImagePath(userId));
|
||||||
|
|
||||||
export const getPeopleThumbnailUrl = (personId: string) => createUrl(getPeopleThumbnailPath(personId));
|
export const getPeopleThumbnailUrl = (person: PersonResponseDto, updatedAt?: string) =>
|
||||||
|
createUrl(getPeopleThumbnailPath(person.id), { updatedAt: updatedAt ?? person.updatedAt });
|
||||||
|
|
||||||
export const getAssetJobName = derived(t, ($t) => {
|
export const getAssetJobName = derived(t, ($t) => {
|
||||||
return (job: AssetJobName) => {
|
return (job: AssetJobName) => {
|
||||||
|
@ -61,7 +61,7 @@
|
|||||||
<ImageThumbnail
|
<ImageThumbnail
|
||||||
circle
|
circle
|
||||||
shadow
|
shadow
|
||||||
url={getPeopleThumbnailUrl(person.id)}
|
url={getPeopleThumbnailUrl(person)}
|
||||||
altText={person.name}
|
altText={person.name}
|
||||||
widthStyle="100%"
|
widthStyle="100%"
|
||||||
/>
|
/>
|
||||||
|
@ -508,7 +508,7 @@
|
|||||||
preload={searchName !== '' || index < 20}
|
preload={searchName !== '' || index < 20}
|
||||||
bind:hidden={person.isHidden}
|
bind:hidden={person.isHidden}
|
||||||
shadow
|
shadow
|
||||||
url={getPeopleThumbnailUrl(person.id)}
|
url={getPeopleThumbnailUrl(person)}
|
||||||
altText={person.name}
|
altText={person.name}
|
||||||
widthStyle="100%"
|
widthStyle="100%"
|
||||||
bind:eyeColor={eyeColorMap[person.id]}
|
bind:eyeColor={eyeColorMap[person.id]}
|
||||||
|
@ -91,7 +91,7 @@
|
|||||||
let refreshAssetGrid = false;
|
let refreshAssetGrid = false;
|
||||||
|
|
||||||
let personName = '';
|
let personName = '';
|
||||||
$: thumbnailData = getPeopleThumbnailUrl(data.person.id);
|
$: thumbnailData = getPeopleThumbnailUrl(data.person);
|
||||||
|
|
||||||
let name: string = data.person.name;
|
let name: string = data.person.name;
|
||||||
let suggestedPeople: PersonResponseDto[] = [];
|
let suggestedPeople: PersonResponseDto[] = [];
|
||||||
@ -121,7 +121,7 @@
|
|||||||
|
|
||||||
return websocketEvents.on('on_person_thumbnail', (personId: string) => {
|
return websocketEvents.on('on_person_thumbnail', (personId: string) => {
|
||||||
if (data.person.id === personId) {
|
if (data.person.id === personId) {
|
||||||
thumbnailData = getPeopleThumbnailUrl(data.person.id) + `?now=${Date.now()}`;
|
thumbnailData = getPeopleThumbnailUrl(data.person, Date.now().toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -206,10 +206,13 @@
|
|||||||
if (viewMode !== ViewMode.SELECT_PERSON) {
|
if (viewMode !== ViewMode.SELECT_PERSON) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
await updatePerson({ id: data.person.id, personUpdateDto: { featureFaceAssetId: asset.id } });
|
||||||
|
notificationController.show({ message: $t('feature_photo_updated'), type: NotificationType.Info });
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, $t('errors.unable_to_set_feature_photo'));
|
||||||
|
}
|
||||||
|
|
||||||
await updatePerson({ id: data.person.id, personUpdateDto: { featureFaceAssetId: asset.id } });
|
|
||||||
|
|
||||||
notificationController.show({ message: $t('feature_photo_updated'), type: NotificationType.Info });
|
|
||||||
assetInteractionStore.clearMultiselect();
|
assetInteractionStore.clearMultiselect();
|
||||||
|
|
||||||
viewMode = ViewMode.VIEW_ASSETS;
|
viewMode = ViewMode.VIEW_ASSETS;
|
||||||
@ -525,7 +528,7 @@
|
|||||||
<ImageThumbnail
|
<ImageThumbnail
|
||||||
circle
|
circle
|
||||||
shadow
|
shadow
|
||||||
url={getPeopleThumbnailUrl(person.id)}
|
url={getPeopleThumbnailUrl(person)}
|
||||||
altText={person.name}
|
altText={person.name}
|
||||||
widthStyle="2rem"
|
widthStyle="2rem"
|
||||||
heightStyle="2rem"
|
heightStyle="2rem"
|
||||||
|
Loading…
Reference in New Issue
Block a user