diff options
4 files changed, 54 insertions, 31 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index c2211fb80..ac271526f 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -362,6 +362,7 @@ export class DocumentOptions { data?: FieldType; data_useCors?: BOOLt = new BoolInfo('whether CORS protocol should be used for web page'); + _face_showImages?: BOOLt = new BoolInfo('whether to show images in uniqe face Doc'); columnHeaders?: List<SchemaHeaderField>; // headers for stacking views schemaHeaders?: List<SchemaHeaderField>; // headers for schema view dockingConfig?: STRt = new StrInfo('configuration of golden layout windows (applies only if doc is rendered as a CollectionDockingView)', false); diff --git a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.scss b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.scss index 86120f966..00297a97d 100644 --- a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.scss +++ b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.scss @@ -1,11 +1,10 @@ .face-document-item { - margin-top: 10px; - margin-bottom: 10px; - padding: 10px; - border-radius: inherit; - position: relative; + display: flex; + height: max-content; + flex-direction: column; + top: 0; + position: absolute; width: 100%; - height: 100%; h1 { color: white; @@ -24,11 +23,12 @@ left: 10px; } .face-document-top { - position: absolute; + position: relative; top: 0; margin: auto; width: 100%; left: 0; + height: 60px; } .face-document-image-container { @@ -37,9 +37,8 @@ flex-wrap: wrap; overflow-x: hidden; overflow-y: auto; - position: absolute; - top: 75px; - height: calc(100% - 75px); + position: relative; + padding: 10px; .image-wrapper { position: relative; diff --git a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx index 6a0d51e32..33fc3c210 100644 --- a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx +++ b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx @@ -3,14 +3,14 @@ import { IconButton, Size } from 'browndash-components'; import * as faceapi from 'face-api.js'; import { FaceMatcher } from 'face-api.js'; import 'ldrs/ring'; -import { action, makeObservable, observable } from 'mobx'; +import { IReactionDisposer, action, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import React from 'react'; -import { lightOrDark, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils'; +import { DivHeight, lightOrDark, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils'; import { emptyFunction } from '../../../../Utils'; import { Doc, Opt } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; -import { ImageCast, StrCast } from '../../../../fields/Types'; +import { ImageCast, NumCast, StrCast } from '../../../../fields/Types'; import { DocumentType } from '../../../documents/DocumentTypes'; import { Docs } from '../../../documents/Documents'; import { DragManager } from '../../../util/DragManager'; @@ -48,10 +48,29 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() { super(props); makeObservable(this); } + @observable _headerRef: HTMLDivElement | null = null; + @observable _listRef: HTMLDivElement | null = null; + _disposers: { [key: string]: IReactionDisposer } = {}; + _lastHeight = 0; + observer = new ResizeObserver(() => this._props.setHeight?.((this.props.Document._face_showImages ? 20 : 0) + (!this._headerRef ? 0 : DivHeight(this._headerRef)) + (!this._listRef ? 0 : DivHeight(this._listRef)))); + + componentDidMount(): void { + this._disposers.refList = reaction( + () => ({ refList: [this._headerRef, this._listRef], autoHeight: this.layoutDoc._layout_autoHeight && !DocumentView.LightboxContains(this.DocumentView?.()) }), + ({ refList, autoHeight }) => { + this.observer.disconnect(); + if (autoHeight) refList.filter(r => r).forEach(r => this.observer.observe(r!)); + }, + { fireImmediately: true } + ); + } + componentWillUnmount(): void { + //this._disposers?.(); + } protected createDropTarget = (ele: HTMLDivElement) => { this._dropDisposer?.(); - ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this._props.Document)); + ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.Document)); }; protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean { @@ -59,12 +78,12 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() { ?.filter(doc => doc.type === DocumentType.IMG) .forEach(imgDoc => { // If the current Face Document has no faces, and the doc has more than one face descriptor, don't let the user add the document first. Or should we just use the first face ? - if (FaceRecognitionHandler.UniqueFaceDescriptors(this._props.Document).length === 0 && FaceRecognitionHandler.ImageDocFaceDescriptors(imgDoc).length > 1) { + if (FaceRecognitionHandler.UniqueFaceDescriptors(this.Document).length === 0 && FaceRecognitionHandler.ImageDocFaceDescriptors(imgDoc).length > 1) { alert('Cannot add a document with multiple faces as the first item!'); } else { // Loop through the documents' face descriptors and choose the face in the iage with the smallest distance (most similar to the face colleciton) - const faceDescriptorsAsFloat32Array = FaceRecognitionHandler.UniqueFaceDescriptors(this._props.Document).map(fd => new Float32Array(Array.from(fd))); - const labeledFaceDescriptor = new faceapi.LabeledFaceDescriptors(FaceRecognitionHandler.UniqueFaceLabel(this._props.Document), faceDescriptorsAsFloat32Array); + const faceDescriptorsAsFloat32Array = FaceRecognitionHandler.UniqueFaceDescriptors(this.Document).map(fd => new Float32Array(Array.from(fd))); + const labeledFaceDescriptor = new faceapi.LabeledFaceDescriptors(FaceRecognitionHandler.UniqueFaceLabel(this.Document), faceDescriptorsAsFloat32Array); const faceMatcher = new FaceMatcher([labeledFaceDescriptor], 1); const { face_match } = FaceRecognitionHandler.ImageDocFaceDescriptors(imgDoc).reduce( (prev, face) => { @@ -76,7 +95,7 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() { // assign the face in the image that's closest to the face collection to be the face that's assigned to the collection if (face_match) { - FaceRecognitionHandler.UniqueFaceAddFaceImage(imgDoc, face_match, this._props.Document); + FaceRecognitionHandler.UniqueFaceAddFaceImage(imgDoc, face_match, this.Document); } } }); @@ -87,17 +106,17 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() { /** * Toggles whether a Face Document displays its associated docs. */ - @action onDisplayClick() { - this._props.Document._displayImages = !this.props.Document._displayImages; - this._props.Document.height = this._props.Document._displayImages ? 400 : 100; + this.Document._face_showImages && (this._lastHeight = NumCast(this.Document.height)); + this.Document._face_showImages = !this.Document._face_showImages; + setTimeout(action(() => (!this.Document.layout_autoHeight || !this.Document._face_showImages) && (this.Document.height = this.Document._face_showImages ? this._lastHeight : 60))); } /** * Removes a unique face Doc from the colelction of unique faces. */ deleteUniqueFace = undoable(() => { - FaceRecognitionHandler.DeleteUniqueFace(this._props.Document); + FaceRecognitionHandler.DeleteUniqueFace(this.Document); }, 'delete face'); /** @@ -105,7 +124,7 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() { * @param imgDoc - image Doc to remove */ removeFaceImageFromUniqueFace = undoable((imgDoc: Doc) => { - FaceRecognitionHandler.UniqueFaceRemoveFaceImage(imgDoc, this._props.Document); + FaceRecognitionHandler.UniqueFaceRemoveFaceImage(imgDoc, this.Document); }, 'remove doc from face'); onPassiveWheel = (e: WheelEvent) => e.stopPropagation(); @@ -116,31 +135,32 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() { <div className="face-collection-buttons"> <IconButton tooltip="Delete Face From Collection" onPointerDown={this.deleteUniqueFace} icon={'x'} style={{ width: '4px' }} size={Size.XSMALL} /> </div> - <div className="face-document-top"> - <h1 style={{ color: lightOrDark(StrCast(this._props.Document.backgroundColor)) }}>{FaceRecognitionHandler.UniqueFaceLabel(this._props.Document)}</h1> + <div className="face-document-top" ref={action((r: HTMLDivElement | null) => (this._headerRef = r))}> + <h1 style={{ color: lightOrDark(StrCast(this.Document.backgroundColor)) }}>{FaceRecognitionHandler.UniqueFaceLabel(this.Document)}</h1> </div> <div className="face-collection-toggle"> <IconButton tooltip="See image information" onPointerDown={() => this.onDisplayClick()} - icon={<FontAwesomeIcon icon={this._props.Document._displayImages ? 'caret-up' : 'caret-down'} />} + icon={<FontAwesomeIcon icon={this.Document._face_showImages ? 'caret-up' : 'caret-down'} />} color={MarqueeOptionsMenu.Instance.userColor} style={{ width: '19px' }} /> </div> - {this.props.Document._displayImages ? ( + {this.props.Document._face_showImages ? ( <div className="face-document-image-container" style={{ pointerEvents: this._props.isContentActive() ? undefined : 'none', }} - ref={ele => { + ref={action((ele: HTMLDivElement | null) => { + this._listRef = ele; this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel); this._oldWheel = ele; // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); - }}> - {FaceRecognitionHandler.UniqueFaceImages(this._props.Document).map((doc, i) => { + })}> + {FaceRecognitionHandler.UniqueFaceImages(this.Document).map((doc, i) => { const [name, type] = ImageCast(doc[Doc.LayoutFieldKey(doc)]).url.href.split('.'); return ( <div diff --git a/src/client/views/search/FaceRecognitionHandler.tsx b/src/client/views/search/FaceRecognitionHandler.tsx index 1ab084eaa..f613f78ce 100644 --- a/src/client/views/search/FaceRecognitionHandler.tsx +++ b/src/client/views/search/FaceRecognitionHandler.tsx @@ -180,9 +180,12 @@ export class FaceRecognitionHandler { _layout_reflowVertical: true, _layout_nativeDimEditable: true, _layout_borderRounding: '20px', + _layout_fitWidth: true, + _layout_autoHeight: true, + _face_showImages: true, backgroundColor: '#555555', _width: 400, - _height: 400, + _height: 100, }); const uface = uniqueFaceDoc[DocData]; uface.face = ''; // just to make prettyprinting look better |