feat: tags (#11980)

* feat: tags

* fix: folder tree icons

* navigate to tag from detail panel

* delete tag

* Tag position and add tag button

* Tag asset in detail panel

* refactor form

* feat: navigate to tag page from clicking on a tag

* feat: delete tags from the tag page

* refactor: moving tag section in detail panel and add + tag button

* feat: tag asset action in detail panel

* refactor add tag form

* fdisable add tag button when there is no selection

* feat: tag bulk endpoint

* feat: tag colors

* chore: clean up

* chore: unit tests

* feat: write tags to sidecar

* Remove tag and auto focus on tag creation form opened

* chore: regenerate migration

* chore: linting

* add color picker to tag edit form

* fix: force render tags timeline on navigating back from asset viewer

* feat: read tags from keywords

* chore: clean up

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
Jason Rasmussen
2024-08-29 12:14:03 -04:00
committed by GitHub
parent 682adaa334
commit d08a20bd57
68 changed files with 3032 additions and 814 deletions
@@ -12,6 +12,7 @@ import { PersonEntity } from 'src/entities/person.entity';
import { SessionEntity } from 'src/entities/session.entity';
import { SharedLinkEntity } from 'src/entities/shared-link.entity';
import { StackEntity } from 'src/entities/stack.entity';
import { TagEntity } from 'src/entities/tag.entity';
import { AlbumUserRole } from 'src/enum';
import { IAccessRepository } from 'src/interfaces/access.interface';
import { Instrumentation } from 'src/utils/instrumentation';
@@ -25,6 +26,7 @@ type IMemoryAccess = IAccessRepository['memory'];
type IPersonAccess = IAccessRepository['person'];
type IPartnerAccess = IAccessRepository['partner'];
type IStackAccess = IAccessRepository['stack'];
type ITagAccess = IAccessRepository['tag'];
type ITimelineAccess = IAccessRepository['timeline'];
@Instrumentation()
@@ -444,6 +446,28 @@ class PartnerAccess implements IPartnerAccess {
}
}
class TagAccess implements ITagAccess {
constructor(private tagRepository: Repository<TagEntity>) {}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
@ChunkedSet({ paramIndex: 1 })
async checkOwnerAccess(userId: string, tagIds: Set<string>): Promise<Set<string>> {
if (tagIds.size === 0) {
return new Set();
}
return this.tagRepository
.find({
select: { id: true },
where: {
id: In([...tagIds]),
userId,
},
})
.then((tags) => new Set(tags.map((tag) => tag.id)));
}
}
export class AccessRepository implements IAccessRepository {
activity: IActivityAccess;
album: IAlbumAccess;
@@ -453,6 +477,7 @@ export class AccessRepository implements IAccessRepository {
person: IPersonAccess;
partner: IPartnerAccess;
stack: IStackAccess;
tag: ITagAccess;
timeline: ITimelineAccess;
constructor(
@@ -467,6 +492,7 @@ export class AccessRepository implements IAccessRepository {
@InjectRepository(SharedLinkEntity) sharedLinkRepository: Repository<SharedLinkEntity>,
@InjectRepository(SessionEntity) sessionRepository: Repository<SessionEntity>,
@InjectRepository(StackEntity) stackRepository: Repository<StackEntity>,
@InjectRepository(TagEntity) tagRepository: Repository<TagEntity>,
) {
this.activity = new ActivityAccess(activityRepository, albumRepository);
this.album = new AlbumAccess(albumRepository, sharedLinkRepository);
@@ -476,6 +502,7 @@ export class AccessRepository implements IAccessRepository {
this.person = new PersonAccess(assetFaceRepository, personRepository);
this.partner = new PartnerAccess(partnerRepository);
this.stack = new StackAccess(stackRepository);
this.tag = new TagAccess(tagRepository);
this.timeline = new TimelineAccess(partnerRepository);
}
}