mirror of
https://github.com/bpatrik/pigallery2.git
synced 2024-11-28 08:58:49 +02:00
adding favourite option to faces
This commit is contained in:
parent
8106646739
commit
5695f74e29
@ -10,6 +10,22 @@ const LOG_TAG = '[PersonMWs]';
|
||||
export class PersonMWs {
|
||||
|
||||
|
||||
public static async updatePerson(req: Request, res: Response, next: NextFunction) {
|
||||
if (!req.params.name) {
|
||||
return next();
|
||||
}
|
||||
|
||||
try {
|
||||
req.resultPipe = await ObjectManagers.getInstance()
|
||||
.PersonManager.updatePerson(req.params.name as string,
|
||||
req.body as PersonDTO);
|
||||
return next();
|
||||
|
||||
} catch (err) {
|
||||
return next(new ErrorDTO(ErrorCodes.PERSON_ERROR, 'Error during updating a person', err));
|
||||
}
|
||||
}
|
||||
|
||||
public static async listPersons(req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
req.resultPipe = await ObjectManagers.getInstance()
|
||||
@ -18,7 +34,7 @@ export class PersonMWs {
|
||||
return next();
|
||||
|
||||
} catch (err) {
|
||||
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error during listing the directory', err));
|
||||
return next(new ErrorDTO(ErrorCodes.PERSON_ERROR, 'Error during listing persons', err));
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,7 +53,7 @@ export class PersonMWs {
|
||||
return next();
|
||||
|
||||
} catch (err) {
|
||||
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error during listing the directory', err));
|
||||
return next(new ErrorDTO(ErrorCodes.PERSON_ERROR, 'Error during adding sample photo for all persons', err));
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,7 +71,7 @@ export class PersonMWs {
|
||||
return next();
|
||||
|
||||
} catch (err) {
|
||||
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error during listing the directory', err));
|
||||
return next(new ErrorDTO(ErrorCodes.PERSON_ERROR, 'Error during removing sample photo from all persons', err));
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,7 +92,7 @@ export class PersonMWs {
|
||||
return next();
|
||||
|
||||
} catch (err) {
|
||||
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error during listing the directory', err));
|
||||
return next(new ErrorDTO(ErrorCodes.PERSON_ERROR, 'Error during getting sample photo for a person', err));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import {PersonEntry} from '../sql/enitites/PersonEntry';
|
||||
import {MediaDTO} from '../../../common/entities/MediaDTO';
|
||||
import {PhotoDTO} from '../../../common/entities/PhotoDTO';
|
||||
import {PersonDTO} from '../../../common/entities/PersonDTO';
|
||||
|
||||
export interface IPersonManager {
|
||||
getAll(): Promise<PersonEntry[]>;
|
||||
@ -14,4 +15,6 @@ export interface IPersonManager {
|
||||
keywordsToPerson(media: MediaDTO[]): Promise<void>;
|
||||
|
||||
updateCounts(): Promise<void>;
|
||||
|
||||
updatePerson(name: string, partialPerson: PersonDTO): Promise<PersonEntry>;
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
import {IPersonManager} from '../interfaces/IPersonManager';
|
||||
import {MediaDTO} from '../../../common/entities/MediaDTO';
|
||||
import {PersonEntry} from '../sql/enitites/PersonEntry';
|
||||
import {PhotoDTO} from '../../../common/entities/PhotoDTO';
|
||||
import {PersonDTO} from '../../../common/entities/PersonDTO';
|
||||
|
||||
export class PersonManager implements IPersonManager {
|
||||
getAll(): Promise<PersonEntry[]> {
|
||||
|
||||
getAll(): Promise<any[]> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
@ -27,4 +28,8 @@ export class PersonManager implements IPersonManager {
|
||||
updateCounts(): Promise<void> {
|
||||
throw new Error('not supported by memory DB');
|
||||
}
|
||||
|
||||
updatePerson(name: string, partialPerson: PersonDTO): Promise<any> {
|
||||
throw new Error('not supported by memory DB');
|
||||
}
|
||||
}
|
||||
|
@ -5,12 +5,34 @@ import {MediaDTO} from '../../../common/entities/MediaDTO';
|
||||
import {PhotoDTO} from '../../../common/entities/PhotoDTO';
|
||||
import {MediaEntity} from './enitites/MediaEntity';
|
||||
import {FaceRegionEntry} from './enitites/FaceRegionEntry';
|
||||
import {PersonDTO} from '../../../common/entities/PersonDTO';
|
||||
|
||||
const LOG_TAG = '[PersonManager]';
|
||||
|
||||
export class PersonManager implements IPersonManager {
|
||||
persons: PersonEntry[] = [];
|
||||
|
||||
async updatePerson(name: string, partialPerson: PersonDTO): Promise<PersonEntry> {
|
||||
const connection = await SQLConnection.getConnection();
|
||||
const repository = connection.getRepository(PersonEntry);
|
||||
const person = await repository.createQueryBuilder('person')
|
||||
.limit(1)
|
||||
.where('person.name LIKE :name COLLATE utf8_general_ci', {name: name}).getOne();
|
||||
|
||||
|
||||
if (typeof partialPerson.name !== 'undefined') {
|
||||
person.name = partialPerson.name;
|
||||
}
|
||||
if (typeof partialPerson.isFavourite !== 'undefined') {
|
||||
person.isFavourite = partialPerson.isFavourite;
|
||||
}
|
||||
await repository.save(person);
|
||||
|
||||
await this.loadAll();
|
||||
|
||||
return person;
|
||||
}
|
||||
|
||||
async getSamplePhoto(name: string): Promise<PhotoDTO> {
|
||||
const connection = await SQLConnection.getConnection();
|
||||
const rawAndEntities = await connection.getRepository(MediaEntity).createQueryBuilder('media')
|
||||
@ -18,7 +40,7 @@ export class PersonManager implements IPersonManager {
|
||||
.leftJoinAndSelect('media.directory', 'directory')
|
||||
.leftJoinAndSelect('media.metadata.faces', 'faces')
|
||||
.leftJoinAndSelect('faces.person', 'person')
|
||||
.where('person.name LIKE :name COLLATE utf8_general_ci', {name: '%' + name + '%'}).getRawAndEntities();
|
||||
.where('person.name LIKE :name COLLATE utf8_general_ci', {name: name}).getRawAndEntities();
|
||||
|
||||
if (rawAndEntities.entities.length === 0) {
|
||||
return null;
|
||||
|
@ -16,6 +16,9 @@ export class PersonEntry implements PersonDTO {
|
||||
@Column('int', {unsigned: true, default: 0})
|
||||
count: number;
|
||||
|
||||
@Column({default: false})
|
||||
isFavourite: boolean;
|
||||
|
||||
@OneToMany(type => FaceRegionEntry, faceRegion => faceRegion.person)
|
||||
public faces: FaceRegionEntry[];
|
||||
|
||||
|
@ -5,14 +5,30 @@ import {UserRoles} from '../../common/entities/UserDTO';
|
||||
import {PersonMWs} from '../middlewares/PersonMWs';
|
||||
import {ThumbnailGeneratorMWs} from '../middlewares/thumbnail/ThumbnailGeneratorMWs';
|
||||
import {VersionMWs} from '../middlewares/VersionMWs';
|
||||
import {Config} from '../../common/config/private/Config';
|
||||
|
||||
export class PersonRouter {
|
||||
public static route(app: Express) {
|
||||
|
||||
this.updatePerson(app);
|
||||
this.addPersons(app);
|
||||
this.getPersonThumbnail(app);
|
||||
}
|
||||
|
||||
|
||||
private static updatePerson(app: Express) {
|
||||
app.post(['/api/person/:name'],
|
||||
// common part
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(Config.Client.Faces.writeAccessMinRole),
|
||||
VersionMWs.injectGalleryVersion,
|
||||
|
||||
// specific part
|
||||
PersonMWs.updatePerson,
|
||||
RenderingMWs.renderResult
|
||||
);
|
||||
}
|
||||
|
||||
private static addPersons(app: Express) {
|
||||
app.get(['/api/person'],
|
||||
// common part
|
||||
|
@ -1 +1 @@
|
||||
export const DataStructureVersion = 11;
|
||||
export const DataStructureVersion = 12;
|
||||
|
@ -69,6 +69,7 @@ export module ClientConfig {
|
||||
export interface FacesConfig {
|
||||
enabled: boolean;
|
||||
keywordsToPersons: boolean;
|
||||
writeAccessMinRole: UserRoles;
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
@ -147,7 +148,8 @@ export class PublicConfigClass {
|
||||
},
|
||||
Faces: {
|
||||
enabled: true,
|
||||
keywordsToPersons: true
|
||||
keywordsToPersons: true,
|
||||
writeAccessMinRole: UserRoles.Admin
|
||||
},
|
||||
authenticationRequired: true,
|
||||
unAuthenticatedUserRole: UserRoles.Admin,
|
||||
|
@ -11,13 +11,14 @@ export enum ErrorCodes {
|
||||
|
||||
GENERAL_ERROR = 7,
|
||||
THUMBNAIL_GENERATION_ERROR = 8,
|
||||
SERVER_ERROR = 9,
|
||||
PERSON_ERROR = 9,
|
||||
SERVER_ERROR = 10,
|
||||
|
||||
USER_MANAGEMENT_DISABLED = 10,
|
||||
USER_MANAGEMENT_DISABLED = 11,
|
||||
|
||||
INPUT_ERROR = 11,
|
||||
INPUT_ERROR = 12,
|
||||
|
||||
SETTINGS_ERROR = 12
|
||||
SETTINGS_ERROR = 13
|
||||
}
|
||||
|
||||
export class ErrorDTO {
|
||||
|
@ -3,10 +3,12 @@ export interface PersonDTO {
|
||||
name: string;
|
||||
count: number;
|
||||
readyThumbnail: boolean;
|
||||
isFavourite: boolean;
|
||||
}
|
||||
|
||||
|
||||
export class Person implements PersonDTO {
|
||||
isFavourite: boolean;
|
||||
count: number;
|
||||
id: number;
|
||||
name: string;
|
||||
|
@ -1,3 +1,25 @@
|
||||
.star {
|
||||
margin: 2px;
|
||||
color: #888;
|
||||
cursor: default;
|
||||
|
||||
}
|
||||
|
||||
.star.favourite {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.star.clickable {
|
||||
cursor: pointer;
|
||||
transition: all .05s ease-in-out;
|
||||
transform: scale(1.0, 1.0);
|
||||
}
|
||||
|
||||
.star.clickable:hover {
|
||||
transform: scale(1.4, 1.4);
|
||||
}
|
||||
|
||||
|
||||
a {
|
||||
position: relative;
|
||||
}
|
||||
@ -51,6 +73,7 @@ a:hover .photo-container {
|
||||
}
|
||||
|
||||
.person-name {
|
||||
display: inline-block;
|
||||
width: 180px;
|
||||
white-space: normal;
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
<a class="button btn btn-secondary"
|
||||
[routerLink]="['/search', person.name, {type: SearchTypes[SearchTypes.person]}]"
|
||||
<a [routerLink]="['/search', person.name, {type: SearchTypes[SearchTypes.person]}]"
|
||||
style="display: inline-block;">
|
||||
|
||||
|
||||
<div class="photo-container"
|
||||
[style.width.px]="size"
|
||||
[style.height.px]="size">
|
||||
|
||||
<div class="photo"
|
||||
*ngIf="thumbnail && thumbnail.Available"
|
||||
[style.background-image]="getSanitizedThUrl()"></div>
|
||||
@ -18,7 +18,10 @@
|
||||
|
||||
<!--Info box -->
|
||||
<div class="info">
|
||||
<div class="person-name">{{person.name}} ({{person.count}})</div>
|
||||
<span (click)="CanUpdate && toggleFavourite($event)"
|
||||
class="star oi oi-star"
|
||||
[ngClass]="{'favourite':person.isFavourite, 'clickable':CanUpdate}"></span>
|
||||
{{person.name}} ({{person.count}})
|
||||
|
||||
</div>
|
||||
</a>
|
||||
|
@ -4,6 +4,9 @@ import {PersonDTO} from '../../../../../common/entities/PersonDTO';
|
||||
import {SearchTypes} from '../../../../../common/entities/AutoCompleteItem';
|
||||
import {DomSanitizer} from '@angular/platform-browser';
|
||||
import {PersonThumbnail, ThumbnailManagerService} from '../../gallery/thumbnailManager.service';
|
||||
import {FacesService} from '../faces.service';
|
||||
import {AuthenticationService} from '../../../model/network/authentication.service';
|
||||
import {Config} from '../../../../../common/config/public/Config';
|
||||
|
||||
@Component({
|
||||
selector: 'app-face',
|
||||
@ -19,10 +22,16 @@ export class FaceComponent implements OnInit, OnDestroy {
|
||||
SearchTypes = SearchTypes;
|
||||
|
||||
constructor(private thumbnailService: ThumbnailManagerService,
|
||||
private _sanitizer: DomSanitizer) {
|
||||
private _sanitizer: DomSanitizer,
|
||||
private faceService: FacesService,
|
||||
public authenticationService: AuthenticationService) {
|
||||
|
||||
}
|
||||
|
||||
get CanUpdate(): boolean {
|
||||
return this.authenticationService.user.getValue().role >= Config.Client.Faces.writeAccessMinRole;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.thumbnail = this.thumbnailService.getPersonThumbnail(this.person);
|
||||
|
||||
@ -42,5 +51,11 @@ export class FaceComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
async toggleFavourite($event: MouseEvent) {
|
||||
$event.preventDefault();
|
||||
$event.stopPropagation();
|
||||
await this.faceService.setFavourite(this.person, !this.person.isFavourite).catch(console.error);
|
||||
this.faceService.getPersons();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,12 @@
|
||||
<app-frame>
|
||||
|
||||
<div body #container class="container-fluid">
|
||||
<app-face *ngFor="let person of facesService.persons.value"
|
||||
[person]="person"
|
||||
[size]="size"></app-face>
|
||||
<app-face *ngFor="let person of favourites | async"
|
||||
[person]="person"
|
||||
[size]="size"></app-face>
|
||||
<hr/>
|
||||
<app-face *ngFor="let person of nonFavourites | async"
|
||||
[person]="person"
|
||||
[size]="size"></app-face>
|
||||
</div>
|
||||
</app-frame>
|
||||
|
@ -1,7 +1,9 @@
|
||||
import {Component, ElementRef, OnInit, ViewChild} from '@angular/core';
|
||||
import {FacesService} from './faces.service';
|
||||
import {QueryService} from '../../model/query.service';
|
||||
|
||||
import {map} from 'rxjs/operators';
|
||||
import {PersonDTO} from '../../../../common/entities/PersonDTO';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
|
||||
@Component({
|
||||
selector: 'app-faces',
|
||||
@ -11,10 +13,24 @@ import {QueryService} from '../../model/query.service';
|
||||
export class FacesComponent implements OnInit {
|
||||
@ViewChild('container') container: ElementRef;
|
||||
public size: number;
|
||||
favourites: Observable<PersonDTO[]>;
|
||||
nonFavourites: Observable<PersonDTO[]>;
|
||||
|
||||
constructor(public facesService: FacesService,
|
||||
public queryService: QueryService) {
|
||||
this.facesService.getPersons().catch(console.error);
|
||||
const personCmp = (p1: PersonDTO, p2: PersonDTO) => {
|
||||
return p1.name.localeCompare(p2.name);
|
||||
};
|
||||
this.favourites = this.facesService.persons.pipe(
|
||||
map(value => value.filter(p => p.isFavourite)
|
||||
.sort(personCmp))
|
||||
);
|
||||
this.nonFavourites = this.facesService.persons.pipe(
|
||||
map(value =>
|
||||
value.filter(p => !p.isFavourite)
|
||||
.sort(personCmp))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
@ -6,11 +6,22 @@ import {PersonDTO} from '../../../../common/entities/PersonDTO';
|
||||
|
||||
@Injectable()
|
||||
export class FacesService {
|
||||
|
||||
public persons: BehaviorSubject<PersonDTO[]>;
|
||||
|
||||
constructor(private networkService: NetworkService) {
|
||||
this.persons = new BehaviorSubject<PersonDTO[]>(null);
|
||||
this.persons = new BehaviorSubject<PersonDTO[]>([]);
|
||||
}
|
||||
|
||||
public async setFavourite(person: PersonDTO, isFavourite: boolean): Promise<void> {
|
||||
const updated = await this.networkService.postJson<PersonDTO>('/person/' + person.name, {isFavourite: isFavourite});
|
||||
const updatesList = this.persons.getValue();
|
||||
for (let i = 0; i < updatesList.length; i++) {
|
||||
if (updatesList[i].id === updated.id) {
|
||||
updatesList[i] = updated;
|
||||
this.persons.next(updatesList);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async getPersons() {
|
||||
|
@ -69,7 +69,8 @@ export class SettingsService {
|
||||
},
|
||||
Faces: {
|
||||
enabled: true,
|
||||
keywordsToPersons: true
|
||||
keywordsToPersons: true,
|
||||
writeAccessMinRole: UserRoles.Admin
|
||||
},
|
||||
urlBase: '',
|
||||
publicUrl: '',
|
||||
|
Loading…
Reference in New Issue
Block a user