Skip to content

Commit

Permalink
Storing face-tags to events.db and database
Browse files Browse the repository at this point in the history
  • Loading branch information
Dukobpa3 committed Apr 12, 2024
1 parent 205ba35 commit 41d1851
Show file tree
Hide file tree
Showing 21 changed files with 400 additions and 218 deletions.
64 changes: 49 additions & 15 deletions packages/events/src/apply-events.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,33 @@
import { Event, EventAction } from './models';
import { random } from 'lodash';
import { Event, EventAction, FaceTag, Rect } from './models';

import { Taggable } from './taggable';

const defaultRect: Rect = {
x: -1,
y: -1,
width: -1,
height: -1
}

const findFace = (faces: any[], rect: Rect) => {
return faces.findIndex((face) =>
face.x == rect.x
&& face.y == rect.y
&& face.width == rect.width
&& face.height == rect.height
)
}

const applyEventAction = <T extends Taggable>(data: T, action: EventAction): boolean => {
let changed = false;
switch (action.action) {
case 'addTag': {
if (!data.tags) {
data.tags = [];
}
if (data.tags.indexOf(action.value) < 0) {
data.tags.push(action.value);
if (data.tags.indexOf(action.value as string) < 0) {
data.tags.push(action.value as string);
changed = true;
}
break;
Expand All @@ -19,28 +36,45 @@ const applyEventAction = <T extends Taggable>(data: T, action: EventAction): boo
if (!data.tags || !data.tags.length) {
return false;
}
const index = data.tags.indexOf(action.value);
const index = data.tags.indexOf(action.value as string);
if (index >= 0) {
data.tags.splice(index, 1);
changed = true;
}
break;
}

case 'addFaceTag': {
if (!data.faces || !data.faces.length) {
return false;
}

const faceIdx = findFace(data.faces, (action.value as FaceTag).rect)
if (faceIdx >= 0) {
data.faces[faceIdx].faceTag = (action.value as FaceTag).name;
changed = true;
}
break;
}
case 'removeFaceTag': {
if (!data.faces || !data.faces.length) {
return false;
}
const faceIdx = findFace(data.faces, (action.value as FaceTag).rect)
if (faceIdx >= 0) {
data.faces[faceIdx].faceTag = `unknown (${random(0, 1000)})`;
changed = true;
}
break;
}
}
return changed;
}

const isSubIdsValid = (event: Event) => {
if(!event.subtargetCoords) return true;
if(event.subtargetCoords.length == event.targetIds.length) return true;
return false;
}

const isValidEvent = (event: Event) => {
return event.type == 'userAction'
&& event.targetIds?.length
&& event.actions?.length
&& isSubIdsValid(event)
return event.type == 'userAction'
&& event.targetIds?.length
&& event.actions?.length
}

const applyEventDate = (entry: Taggable, event: Event) => {
Expand All @@ -51,7 +85,7 @@ const applyEventDate = (entry: Taggable, event: Event) => {
}
}

type EntryIdMap = {[key: string]: Taggable[]}
type EntryIdMap = { [key: string]: Taggable[] }

const idMapReducer = (result: EntryIdMap, entry: Taggable) => {
const id = entry.id
Expand Down
23 changes: 6 additions & 17 deletions packages/events/src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,17 @@ export interface Event {
id: string;
date?: string;
targetIds: string[];
// means that can't be more than one same object in one file.
// made for faces, so seems useful
// for example we have file 02f3p with faces 0, 1, 2
// in one event we can put only one faceTag. So in case we will make bulk renaming we will get three events:
// targetIds[02f3p], tagretSubIds[0]
// targetIds[02f3p], tagretSubIds[1]
// targetIds[02f3p], tagretSubIds[2]
//
// espesially important for multiedit of many files, for example files 02f3p, jhr65, 3lh7a
// they have same person, but it have different places in each file, then we will get ONE event:
// targetIds[02f3p, jhr65, 3lh7a], tagretSubIds[5, 0, 3]
//
// this approach is kind of trade off and we loose some benefits in single file/many faces edit
// but makes multifiles edit better

subtargetCoords?: Rect[];
actions: EventAction[];
}

export interface EventAction {
action: string;
value: string;
value: string|FaceTag;
}

export interface FaceTag {
name: string;
rect: Rect;
}

export interface Rect {
Expand Down
1 change: 1 addition & 0 deletions packages/events/src/taggable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ export interface Taggable {
id: string;
updated?: string;
tags?: string[];
faces?: any[];
appliedEventIds?: string[];
}
21 changes: 20 additions & 1 deletion packages/webapp/src/api/ApiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { v4 as uuidv4 } from 'uuid';
import { Event, EventAction } from '@home-gallery/events'
import { pushEvent as pushEventApi, eventStream as eventStreamApi, ServerEventListener, getTree, mapEntriesForBrowser } from './api';
import { UnsavedEventHandler } from './UnsavedEventHandler';
import { Tag } from './models';
import { FaceTag, Tag } from './models';
import { Entry } from "../store/entry";
import { OfflineDatabase } from '../offline'
import { EventBus } from './EventBus';
Expand All @@ -18,12 +18,31 @@ const tagToAction = (tag: Tag): EventAction => {
}
}

const faceTagToAction = (tag: FaceTag): EventAction => {
if (tag.remove) {
return {action: 'removeFaceTag', value: {name:tag.name, rect:tag.rect}}
} else {
return {action: 'addFaceTag', value: {name:tag.name, rect:tag.rect}}
}
}

export const addTags = async (entryIds: string[], tags: Tag[]) => {
const actions = tags.map(tagToAction);
const event: Event = {type: 'userAction', id: uuidv4(), targetIds: entryIds, actions };
return pushEvent(event);
}

export const addFaceTags = async (entryIds: string[], faceTags: FaceTag[]) => {
const actions = faceTags.map(faceTagToAction);
const event: Event = {
type: 'userAction',
id: uuidv4(),
targetIds: entryIds,
actions
};
return pushEvent(event);
}

let eventStreamSubscribed = false;

const unsavedEventHandler = new UnsavedEventHandler();
Expand Down
7 changes: 4 additions & 3 deletions packages/webapp/src/api/models.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Rect } from '@home-gallery/events'

export interface Tag {
name: string;
remove: boolean;
}

export interface FaceTag extends Tag {
descriptorIndex: number;
}

rect: Rect;
}
137 changes: 137 additions & 0 deletions packages/webapp/src/dialog/dialog-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import * as React from "react";

import { Tag, FaceTag } from "../api/models";

import { MultiTagDialog, SingleTagDialog } from './tag-dialog'
import { MultiFaceTagDialog, SingleFaceTagDialog } from './face-tag-dialog'

export type TagsConfig = {
initialTags?: Tag[];
onTagsSubmit: ({ tags }: { tags: Tag[] }) => void;
}

export type FaceTagsConfig = {
initialFaceTags?: FaceTag[];
onFaceTagsSubmit: ({ faceTags }: { faceTags: FaceTag[] }) => void;
}

export type DialogConfig = {
tagsConfig?: TagsConfig;
faceTagsConfig?: FaceTagsConfig;
}

const initialTagsConfig: TagsConfig = {
initialTags: [],
onTagsSubmit: () => false,
}

const initialFaceTagsConfig: FaceTagsConfig = {
initialFaceTags: [],
onFaceTagsSubmit: () => false
}

export type DialogContextType = {
setTagsDialogVisible: (visible: boolean) => void;
openTagsDialog: ({ initialTags, onTagsSubmit }: TagsConfig) => void

setFaceTagsDialogVisible: (visible: boolean) => void;
openFaceTagsDialog: ({ initialFaceTags, onFaceTagsSubmit }: FaceTagsConfig) => void
}

const initialDialogContextValue: DialogContextType = {
setTagsDialogVisible: () => false,
openTagsDialog: () => false,

setFaceTagsDialogVisible: () => false,
openFaceTagsDialog: () => false
}

export const DialogContext = React.createContext<DialogContextType>(initialDialogContextValue)

export const MultiTagDialogProvider = ({ children }) => {
const [tagsDialogVisible, setTagsDialogVisible] = React.useState(false);
const [facesDialogVisible, setFaceTagsDialogVisible] = React.useState(false);

const [tagsConfig, setTagsConfig] = React.useState<TagsConfig>(initialTagsConfig);
const [faceTagsConfig, setFaceTagsConfig] = React.useState<FaceTagsConfig>(initialFaceTagsConfig);

const openTagsDialog = (tagsConfig: TagsConfig) => {
setTagsDialogVisible(true);
setFaceTagsDialogVisible(false);

setTagsConfig((prev) => ({ ...prev, ...tagsConfig }));
};

const onTagsSubmit = ({ tags }) => {
tagsConfig.onTagsSubmit({ tags })
}

const openFaceTagsDialog = (faceTagsConfig: FaceTagsConfig) => {
setTagsDialogVisible(true);
setFaceTagsDialogVisible(false);

setFaceTagsConfig((prev) => ({ ...prev, ...faceTagsConfig }));
};

const onFaceTagsSubmit = ({ faceTags }) => {
faceTagsConfig.onFaceTagsSubmit({ faceTags })
}

return (
<DialogContext.Provider value={{ setTagsDialogVisible, openTagsDialog, setFaceTagsDialogVisible, openFaceTagsDialog }}>
{children}
{tagsDialogVisible && (
<MultiTagDialog onSubmit={onTagsSubmit} onCancel={() => setTagsDialogVisible(false)}></MultiTagDialog>
)}
{ facesDialogVisible && (
<MultiFaceTagDialog onSubmit={onFaceTagsSubmit} onCancel={() => setFaceTagsDialogVisible(false)}></MultiFaceTagDialog>
)}
</DialogContext.Provider>
)
}

export const SingleTagDialogProvider = ({ children }) => {
const [tagsDialogVisible, setTagsDialogVisible] = React.useState(false);
const [facesDialogVisible, setFaceTagsDialogVisible] = React.useState(false);

const [tagsConfig, setTagsConfig] = React.useState<TagsConfig>(initialTagsConfig);
const [faceTagsConfig, setFaceTagsConfig] = React.useState<FaceTagsConfig>(initialFaceTagsConfig);


const openTagsDialog = ({ initialTags, onTagsSubmit }: TagsConfig) => {
setTagsDialogVisible(true);
setFaceTagsDialogVisible(false);

setTagsConfig({ initialTags, onTagsSubmit });
};

const openFaceTagsDialog = ({ initialFaceTags, onFaceTagsSubmit }: FaceTagsConfig) => {
setTagsDialogVisible(false);
setFaceTagsDialogVisible(true);

setFaceTagsConfig({ initialFaceTags, onFaceTagsSubmit });
};

const onTagsSubmit = ({ tags }) => {
tagsConfig.onTagsSubmit({ tags })
}

const onFaceTagsSubmit = ({ faceTags }) => {
faceTagsConfig.onFaceTagsSubmit({ faceTags })
}


return (
<DialogContext.Provider value={{ setTagsDialogVisible, openTagsDialog, setFaceTagsDialogVisible, openFaceTagsDialog }}>
{children}
{tagsDialogVisible && (
<SingleTagDialog tags={tagsConfig.initialTags || []} onSubmit={onTagsSubmit} onCancel={() => setTagsDialogVisible(false)}></SingleTagDialog>
)}
{facesDialogVisible && (
<SingleFaceTagDialog faceTags={faceTagsConfig.initialFaceTags || []} onSubmit={onFaceTagsSubmit} onCancel={() => setFaceTagsDialogVisible(false)}></SingleFaceTagDialog>
)}
</DialogContext.Provider>
)
}


8 changes: 4 additions & 4 deletions packages/webapp/src/dialog/face-tag-dialog-store.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { useReducer } from "react"

import { FaceTag } from "../api/models"
import { TagSuggestion, getSuggestions } from "./suggestion"
import { FaceTagSuggestion, getSuggestions } from "./face-tag-suggestion"

export interface FaceTagDialogState {
current: number
faceTags: FaceTag[]
allTags: string[]
suggestions: TagSuggestion[]
suggestions: FaceTagSuggestion[]
showSuggestions: boolean
}

Expand Down Expand Up @@ -45,10 +45,10 @@ export const reducer = (state: FaceTagDialogState, action: FaceTagAction): FaceT
case 'addFaceTag': {
let name = action.value.replace(/(^\s+|\s+$)/g, '')
let remove = false
let descriptorIndex = state.faceTags[action.selectedId].descriptorIndex;
let rect = state.faceTags[action.selectedId].rect;
const tailSuggestions = {inputValue: action.tail || '', suggestions: getSuggestions(state.allTags, action.tail), showSuggestions: !!action.tail}

const faceTags: FaceTag[] = [...state.faceTags, {descriptorIndex, name, remove}];
const faceTags: FaceTag[] = [...state.faceTags, {rect, name, remove}];
return {...state, faceTags, ...tailSuggestions}
}
/*
Expand Down
Loading

0 comments on commit 41d1851

Please sign in to comment.