import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { IconButton, Size } from 'browndash-components'; import * as faceapi from 'face-api.js'; import { FaceMatcher } from 'face-api.js'; import 'ldrs/ring'; import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import React from 'react'; import { Utils } from '../../../../Utils'; import { Doc, DocListCast } from '../../../../fields/Doc'; import { DocData } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; import { ImageCast, StrCast } from '../../../../fields/Types'; import { DocumentType } from '../../../documents/DocumentTypes'; import { Docs } from '../../../documents/Documents'; import { DragManager } from '../../../util/DragManager'; import { SnappingManager } from '../../../util/SnappingManager'; import { ViewBoxBaseComponent } from '../../DocComponent'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import { DocumentView } from '../../nodes/DocumentView'; import { FieldView, FieldViewProps } from '../../nodes/FieldView'; import './FaceCollectionBox.scss'; import { MarqueeOptionsMenu } from './MarqueeOptionsMenu'; interface FaceDocumentProps { faceDoc: Doc; } /** * A componenent to visually represent a Face Document. */ @observer export class FaceDocumentItem extends ObservableReactComponent { private ref: React.RefObject; @observable _displayImages: boolean = true; private _dropDisposer?: DragManager.DragDropDisposer; private _inputRef = React.createRef(); constructor(props: FaceDocumentProps) { super(props); makeObservable(this); this.ref = React.createRef(); } protected createDropTarget = (ele: HTMLDivElement) => { this._dropDisposer?.(); ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this._props.faceDoc)); }; protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean { const { docDragData } = de.complete; if (docDragData) { const filteredDocs = docDragData.droppedDocuments.filter(doc => doc.type === DocumentType.IMG); filteredDocs.forEach(doc => { // If the current Face Document has no items, and the doc has more than one face descriptor, don't let the user add the document first. if ((this._props.faceDoc[DocData].faceDescriptors as List>).length === 0 && (doc[DocData].faces as List>).length > 1) { alert('Cannot add a document with multiple faces as the first item!'); } else { // Loop through the documents' face descriptors. // Choose the face with the smallest distance to add. const float32Array = (this._props.faceDoc[DocData].faceDescriptors as List>).map(faceDescriptor => new Float32Array(Array.from(faceDescriptor))); const labeledFaceDescriptor = new faceapi.LabeledFaceDescriptors(StrCast(this._props.faceDoc[DocData].label), float32Array); const faceDescriptors: faceapi.LabeledFaceDescriptors[] = [labeledFaceDescriptor]; const faceMatcher = new FaceMatcher(faceDescriptors, 1); let cur_lowest_distance = 1; let cur_matching_face = new List(); (doc[DocData].faces as List>).forEach(face => { // If the face has the current lowest distance, mark it as such // Once that lowest distance is found, add the face descriptor to the faceDoc, and add the associated doc const convered_32_array: Float32Array = new Float32Array(Array.from(face)); const match = faceMatcher.matchDescriptor(convered_32_array); if (match.distance < cur_lowest_distance) { cur_lowest_distance = match.distance; cur_matching_face = face; } }); if (doc[DocData][`FACE DESCRIPTOR - ${this._props.faceDoc[DocData].label}`]) { doc[DocData][`FACE DESCRIPTOR - ${this._props.faceDoc[DocData].label}`] = new List>([...(doc[DocData][`FACE DESCRIPTOR - ${this._props.faceDoc[DocData].label}`] as List>), cur_matching_face]); } else { doc[DocData][`FACE DESCRIPTOR - ${this._props.faceDoc[DocData].label}`] = new List>([cur_matching_face]); } this._props.faceDoc[DocData].associatedDocs = new List([...DocListCast(this._props.faceDoc[DocData].associatedDocs), doc]); this._props.faceDoc[DocData].faceDescriptors = new List>([...(this._props.faceDoc[DocData].faceDescriptors as List>), cur_matching_face]); //const match = faceMatcher.findBestMatch(cur_descriptor); } }); return false; } return false; } /** * Toggles whether a Face Document displays its associated docs. */ @action onDisplayClick() { this._displayImages = !this._displayImages; } /** * Deletes a Face Document. */ @action deleteFaceDocument = () => { if (Doc.ActiveDashboard) { Doc.ActiveDashboard[DocData].faceDocuments = new List(DocListCast(Doc.ActiveDashboard[DocData].faceDocuments).filter(doc => doc !== this._props.faceDoc)); } }; /** * Deletes a document from a Face Document's associated docs list. * @param doc */ @action deleteAssociatedDoc = (doc: Doc) => { this._props.faceDoc[DocData].faceDescriptors = new List>( (this._props.faceDoc[DocData].faceDescriptors as List>).filter(fd => !(doc[DocData][`FACE DESCRIPTOR - ${this._props.faceDoc[DocData].label}`] as List>).includes(fd)) ); doc[DocData][`FACE DESCRIPTOR - ${this._props.faceDoc[DocData].label}`] = new List>(); this._props.faceDoc[DocData].associatedDocs = new List(DocListCast(this._props.faceDoc[DocData].associatedDocs).filter(associatedDoc => associatedDoc !== doc)); }; render() { return (
this.createDropTarget(ele!)}>

{StrCast(this._props.faceDoc[DocData].label)}

this.onDisplayClick()} icon={this._displayImages ? : } color={MarqueeOptionsMenu.Instance.userColor} style={{ width: '19px' }} /> {this._displayImages ? (
{DocListCast(this._props.faceDoc[DocData].associatedDocs).map(doc => { const [name, type] = ImageCast(doc[Doc.LayoutFieldKey(doc)]).url.href.split('.'); return (
{ await DocumentView.showDocument(doc, { willZoomCentered: true }); }} style={{ maxWidth: '60px', margin: '10px' }} src={`${name}_o.${type}`} />
{ this.deleteAssociatedDoc(doc); }} icon={'x'} style={{ width: '4px' }} size={Size.XSMALL} />
); })}
) : (
)}
); } } @observer export class FaceCollectionBox extends ViewBoxBaseComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FaceCollectionBox, fieldKey); } public static Instance: FaceCollectionBox; @computed get currentDocs() { if (Doc.ActiveDashboard) { return DocListCast(Doc.ActiveDashboard[DocData].faceDocuments); } else { return []; } } constructor(props: FieldViewProps) { super(props); makeObservable(this); FaceCollectionBox.Instance = this; } render() { return (
{this.currentDocs.map(doc => { return ; })}
); } } Docs.Prototypes.TemplateMap.set(DocumentType.FACECOLLECTION, { layout: { view: FaceCollectionBox, dataField: 'data' }, options: { acl: '', _width: 400 }, });