feat(web): add Exif-Rating (#11580)

* Add Exif-Rating

* Integrate star rating as own component

* Add e2e tests for rating and validation

* Rename component and async handleChangeRating

* Display rating can be enabled in app settings

* Correct i18n reference

Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>

* Star rating: change from slider to buttons

* Star rating for clarity

* Design updates.

* Renaming and code optimization

* chore: clean up

* chore: e2e formatting

* light mode border and default value

---------

Co-authored-by: Christoph Suter <christoph@suter-burri.ch>
Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
Co-authored-by: Mert <101130780+mertalev@users.noreply.github.com>
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
Christoph Suter
2024-08-09 19:45:52 +02:00
committed by GitHub
parent b1587a5dee
commit f33dbdfe9a
37 changed files with 599 additions and 18 deletions

View File

@@ -228,6 +228,13 @@ describe(AssetService.name, () => {
await sut.update(authStub.admin, 'asset-1', { description: 'Test description' });
expect(assetMock.upsertExif).toHaveBeenCalledWith({ assetId: 'asset-1', description: 'Test description' });
});
it('should update the exif rating', async () => {
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
assetMock.getById.mockResolvedValue(assetStub.image);
await sut.update(authStub.admin, 'asset-1', { rating: 3 });
expect(assetMock.upsertExif).toHaveBeenCalledWith({ assetId: 'asset-1', rating: 3 });
});
});
describe('updateAll', () => {

View File

@@ -158,8 +158,8 @@ export class AssetService {
async update(auth: AuthDto, id: string, dto: UpdateAssetDto): Promise<AssetResponseDto> {
await this.access.requirePermission(auth, Permission.ASSET_UPDATE, id);
const { description, dateTimeOriginal, latitude, longitude, ...rest } = dto;
await this.updateMetadata({ id, description, dateTimeOriginal, latitude, longitude });
const { description, dateTimeOriginal, latitude, longitude, rating, ...rest } = dto;
await this.updateMetadata({ id, description, dateTimeOriginal, latitude, longitude, rating });
await this.assetRepository.update({ id, ...rest });
const asset = await this.assetRepository.getById(id, {
@@ -405,8 +405,8 @@ export class AssetService {
}
private async updateMetadata(dto: ISidecarWriteJob) {
const { id, description, dateTimeOriginal, latitude, longitude } = dto;
const writes = _.omitBy({ description, dateTimeOriginal, latitude, longitude }, _.isUndefined);
const { id, description, dateTimeOriginal, latitude, longitude, rating } = dto;
const writes = _.omitBy({ description, dateTimeOriginal, latitude, longitude, rating }, _.isUndefined);
if (Object.keys(writes).length > 0) {
await this.assetRepository.upsertExif({ assetId: id, ...writes });
await this.jobRepository.queue({ name: JobName.SIDECAR_WRITE, data: { id, ...writes } });

View File

@@ -606,6 +606,7 @@ describe(MetadataService.name, () => {
ProfileDescription: 'extensive description',
ProjectionType: 'equirectangular',
tz: '+02:00',
Rating: 3,
};
assetMock.getByIds.mockResolvedValue([assetStub.image]);
metadataMock.readTags.mockResolvedValue(tags);
@@ -638,6 +639,7 @@ describe(MetadataService.name, () => {
profileDescription: tags.ProfileDescription,
projectionType: 'EQUIRECTANGULAR',
timeZone: tags.tz,
rating: tags.Rating,
});
expect(assetMock.update).toHaveBeenCalledWith({
id: assetStub.image.id,

View File

@@ -273,7 +273,7 @@ export class MetadataService implements OnEvents {
}
async handleSidecarWrite(job: ISidecarWriteJob): Promise<JobStatus> {
const { id, description, dateTimeOriginal, latitude, longitude } = job;
const { id, description, dateTimeOriginal, latitude, longitude, rating } = job;
const [asset] = await this.assetRepository.getByIds([id]);
if (!asset) {
return JobStatus.FAILED;
@@ -287,6 +287,7 @@ export class MetadataService implements OnEvents {
DateTimeOriginal: dateTimeOriginal,
GPSLatitude: latitude,
GPSLongitude: longitude,
Rating: rating,
},
_.isUndefined,
);
@@ -503,6 +504,7 @@ export class MetadataService implements OnEvents {
profileDescription: tags.ProfileDescription || null,
projectionType: tags.ProjectionType ? String(tags.ProjectionType).toUpperCase() : null,
timeZone: tags.tz ?? null,
rating: tags.Rating ?? null,
};
if (exifData.latitude === 0 && exifData.longitude === 0) {