aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2024-09-18 20:46:38 -0400
committerbobzel <zzzman@gmail.com>2024-09-18 20:46:38 -0400
commitd95730d904612640184ca6fdc00864b0c81b0c0c (patch)
tree29176e9a4ea75fca8a146e8a49774fd1b1fb3fc9
parentcf13604b06b8d8cf37f6e69f19a4092bf2c29d65 (diff)
lots of changes to fix dragging cards, integrate iconTags with other tags, sizing docs when selected to fit window,
-rw-r--r--eslint.config.mjs1
-rw-r--r--src/client/util/CurrentUserUtils.ts14
-rw-r--r--src/client/util/DragManager.ts3
-rw-r--r--src/client/views/DocumentButtonBar.tsx44
-rw-r--r--src/client/views/DocumentDecorations.tsx4
-rw-r--r--src/client/views/StyleProvider.tsx4
-rw-r--r--src/client/views/TagsView.tsx20
-rw-r--r--src/client/views/collections/CollectionCardDeckView.scss8
-rw-r--r--src/client/views/collections/CollectionCardDeckView.tsx152
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx1
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx3
-rw-r--r--src/client/views/collections/CollectionSubView.tsx4
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx5
-rw-r--r--src/client/views/nodes/DocumentView.tsx5
-rw-r--r--src/client/views/nodes/IconTagBox.scss24
-rw-r--r--src/client/views/nodes/IconTagBox.tsx106
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx1
-rw-r--r--src/client/views/search/FaceRecognitionHandler.tsx3
18 files changed, 198 insertions, 204 deletions
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 8926afd7c..f7063caa5 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -36,6 +36,7 @@ export default [
'no-multi-assign': 'off',
'no-underscore-dangle': 'off',
'no-nested-ternary': 'off',
+ 'no-param-reassign': 'error',
'lines-between-class-members': 'off',
'no-shadow': 'off',
'@typescript-eslint/no-shadow': 'warn',
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index eee6be937..262916312 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -2,7 +2,7 @@
import { reaction, runInAction } from "mobx";
import * as rp from 'request-promise';
import { ClientUtils, OmitKeys } from "../../ClientUtils";
-import { Doc, DocListCast, DocListCastAsync, FieldType, Opt } from "../../fields/Doc";
+import { Doc, DocListCast, DocListCastAsync, FieldType, Opt, StrListCast } from "../../fields/Doc";
import { DocData } from "../../fields/DocSymbols";
import { InkTool } from "../../fields/InkField";
import { List } from "../../fields/List";
@@ -688,18 +688,18 @@ pie title Minerals in my tap water
static tagGroupTools(): Button[] {
if (!Doc.UserDoc().activeDashboard) {
- Doc.UserDoc().myFilterHotKeyTitles = new List<string>(['Star', 'Heart', 'Bolt', 'Cloud']);
+ Doc.UserDoc().myFilterHotKeyTitles = new List<string>(['star', 'heart', 'bolt', 'cloud']);
- ['Star', 'Heart', 'Bolt', 'Cloud'].forEach(key => {
+ StrListCast(Doc.UserDoc().myFilterHotKeyTitles).forEach(key => {
Doc.UserDoc()[key] = key.toLowerCase();
});
}
// hack: if there's no dashboard, create default filters. otherwise, just make sure that the Options button is preserved
return (Doc.UserDoc().activeDashboard ? [] : [
- { title: "Star", isSystem: false, icon: "star", toolTip:"Click to toggle the star group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
- { title: "Heart", isSystem: false,icon: "heart", toolTip:"Click to toggle the heart group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
- { title: "Bolt", isSystem: false,icon: "bolt", toolTip:"Click to toggle the bolt group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
- { title: "Cloud", isSystem: false,icon: "cloud", toolTip:"Click to toggle the cloud group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+ { title: "star", isSystem: false,icon: "star", toolTip:"Click to toggle the star group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+ { title: "heart", isSystem: false,icon: "heart", toolTip:"Click to toggle the heart group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+ { title: "bolt", isSystem: false,icon: "bolt", toolTip:"Click to toggle the bolt group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+ { title: "cloud", isSystem: false,icon: "cloud", toolTip:"Click to toggle the cloud group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
]).concat([
{ title: "Options", isSystem: true,icon: "gear", toolTip:"Click to customize your filter panel", btnType: ButtonType.ClickButton, expertMode: false, toolType:"opts", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}
])
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index d55d193cc..81ea840f1 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -29,7 +29,7 @@ import { dropActionType } from './DropActionTypes';
import { SnappingManager } from './SnappingManager';
import { UndoManager } from './UndoManager';
-// eslint-disable-next-line @typescript-eslint/no-var-requires
+// eslint-disable-next-line @typescript-eslint/no-require-imports
const { contextMenuZindex } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore
/**
@@ -101,7 +101,6 @@ export namespace DragManager {
// event called when the drag operation results in a drop action
export class DropEvent {
- // eslint-disable-next-line no-useless-constructor
constructor(
readonly x: number,
readonly y: number,
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 87dd5f45a..32bf67df1 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -9,7 +9,7 @@ import * as React from 'react';
import { FaEdit } from 'react-icons/fa';
import { returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../ClientUtils';
import { emptyFunction } from '../../Utils';
-import { Doc } from '../../fields/Doc';
+import { Doc, DocListCast } from '../../fields/Doc';
import { Cast, DocCast } from '../../fields/Types';
import { DocUtils, IsFollowLinkScript } from '../documents/DocUtils';
import { CalendarManager } from '../util/CalendarManager';
@@ -28,7 +28,6 @@ import { DocumentLinksButton } from './nodes/DocumentLinksButton';
import { DocumentView } from './nodes/DocumentView';
import { OpenWhere } from './nodes/OpenWhere';
import { DashFieldView } from './nodes/formattedText/DashFieldView';
-import { DocData } from '../../fields/DocSymbols';
@observer
export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (DocumentView | undefined)[]; stack?: unknown }> {
@@ -287,44 +286,27 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
get keywordButton() {
const targetDoc = this.view0?.Document;
- const metaBtn = (name: string, icon: IconProp) => {
- const tooltip = `Toggle ${name}`;
- return (
- <Tooltip title={<div className="dash-tooltip">{tooltip}</div>}>
- <div className="documentButtonBar-pinIcon">
- <FontAwesomeIcon
- className="documentdecorations-icon"
- style={{ width: 20 }}
- key={icon.toString()}
- size="sm"
- icon={icon}
- onClick={() => {
- if (name === 'tags') {
- targetDoc && (targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags);
- } else {
- targetDoc && (targetDoc[DocData].showLabels = !targetDoc[DocData].showLabels);
- }
- }}
- />
- </div>
- </Tooltip>
- );
- };
-
return !targetDoc ? null : (
<div className="documentButtonBar-icon">
- <div className="documentButtonBar-pinTypes" style={{ width: '40px' }}>
+ {/* <div className="documentButtonBar-pinTypes" style={{ width: '40px' }}>
{metaBtn('tags', 'star')}
{metaBtn('keywords', 'id-card')}
- </div>
+ </div> */}
<Tooltip title={<div className="dash-keyword-button">Open keyword menu</div>}>
<div
className="documentButtonBar-icon"
style={{ color: 'white' }}
- onClick={() => {
- // targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags;
- }}>
+ onClick={undoable(e => {
+ const showing = DocumentView.Selected().some(dv => dv.showTags);
+ DocumentView.Selected().forEach(dv => {
+ dv.layoutDoc._layout_showTags = !showing;
+ if (e.shiftKey)
+ DocListCast(dv.Document[Doc.LayoutFieldKey(dv.Document) + '_annotations']).forEach(doc => {
+ if (doc.face) doc.hidden = showing;
+ });
+ });
+ }, 'show Doc tags')}>
<FontAwesomeIcon className="documentdecorations-icon" icon="tag" />
</div>
</Tooltip>
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 5e7908725..2ba96c2a9 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -837,7 +837,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
<div
className="link-button-container"
style={{
- top: DocumentView.Selected().length > 1 ? 0 : `${seldocview.Document._layout_showTags ? 4 + seldocview.TagPanelHeight : 4}px`,
+ top: DocumentView.Selected().length > 1 ? 0 : `${seldocview.showTags ? 4 + seldocview.TagPanelHeight : 4}px`,
transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
}}>
<DocumentButtonBar views={() => DocumentView.Selected()} />
@@ -846,7 +846,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
<div
className="documentDecorations-tagsView"
style={{
- top: `${seldocview.Document._layout_showTags ? 4 + seldocview.TagPanelHeight : 4}px`,
+ top: `${seldocview.showTags ? 4 + seldocview.TagPanelHeight : 4}px`,
transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
}}>
{DocumentView.Selected().length > 1 ? <TagsView Views={DocumentView.Selected()} /> : null}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index eb434db40..3545afcee 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -9,7 +9,6 @@ import { BsArrowDown, BsArrowDownUp, BsArrowUp } from 'react-icons/bs';
import { FaFilter } from 'react-icons/fa';
import { ClientUtils, DashColor, lightOrDark } from '../../ClientUtils';
import { Doc, Opt, StrListCast } from '../../fields/Doc';
-import { DocData } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { ScriptField } from '../../fields/ScriptField';
import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../fields/Types';
@@ -22,7 +21,6 @@ import { TreeSort } from './collections/TreeSort';
import { Colors } from './global/globalEnums';
import { DocumentView, DocumentViewProps } from './nodes/DocumentView';
import { FieldViewProps } from './nodes/FieldView';
-import { IconTagBox } from './nodes/IconTagBox';
import { StyleProp } from './StyleProp';
import './StyleProvider.scss';
import { TagsView } from './TagsView';
@@ -366,7 +364,6 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
);
};
const tags = () => docView?.() ? <TagsView Views={[docView?.()]}/> : null;
- const iconTags = () => doc?.[DocData].showIconTags ? <IconTagBox doc= {doc}/> : null;
return (
<>
@@ -375,7 +372,6 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
{filter()}
{audio()}
{tags()}
- {iconTags()}
</>
);
}
diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx
index be2c28185..a678d5580 100644
--- a/src/client/views/TagsView.tsx
+++ b/src/client/views/TagsView.tsx
@@ -18,6 +18,7 @@ import { ObservableReactComponent } from './ObservableReactComponent';
import './TagsView.scss';
import { DocumentView } from './nodes/DocumentView';
import { FaceRecognitionHandler } from './search/FaceRecognitionHandler';
+import { IconTagBox } from './nodes/IconTagBox';
/**
* The TagsView is a metadata input/display panel shown at the bottom of a DocumentView in a freeform collection.
@@ -59,7 +60,7 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
* @param tag tag string
* @returns tag collection Doc or undefined
*/
- public static findTagCollectionDoc = (tag: String) => TagItem.AllTagCollectionDocs.find(doc => doc.title === tag);
+ public static findTagCollectionDoc = (tag: string) => TagItem.AllTagCollectionDocs.find(doc => doc.title === tag);
/**
* Creates a Doc that collects Docs with the specified tag / value
@@ -148,7 +149,7 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
private _ref: React.RefObject<HTMLDivElement>;
- constructor(props: any) {
+ constructor(props: TagItemProps) {
super(props);
makeObservable(this);
this._ref = React.createRef();
@@ -225,7 +226,7 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
type="checkbox"
onClick={e => e.stopPropagation()}
onPointerDown={e => e.stopPropagation()}
- onChange={undoable(e => (this.doc[metadata] = !this.doc[metadata]), 'metadata toggle')}
+ onChange={undoable(() => (this.doc[metadata] = !this.doc[metadata]), 'metadata toggle')}
checked={this.doc[metadata] as boolean}
/>
) : (
@@ -257,7 +258,7 @@ interface TagViewProps {
*/
@observer
export class TagsView extends ObservableReactComponent<TagViewProps> {
- constructor(props: any) {
+ constructor(props: TagViewProps) {
super(props);
makeObservable(this);
}
@@ -271,7 +272,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
componentDidMount() {
this._heightDisposer = reaction(
() => this.View.screenToContentsTransform(),
- xf => {
+ () => {
this._panelHeightDirty = this._panelHeightDirty + 1;
}
);
@@ -288,7 +289,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
return this._props.Views.length > 1 ? 1 : Math.max(1, 1 / this.View.screenToLocalScale());
}
@computed get isEditing() {
- return this._isEditing && (this._props.Views.length > 1 || DocumentView.SelectedDocs().includes(this.View.Document));
+ return this._isEditing && (this._props.Views.length > 1 || (DocumentView.Selected().length === 1 && DocumentView.Selected().includes(this.View)));
}
/**
@@ -336,7 +337,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
);
this._panelHeightDirty;
- return this.View.ComponentView?.isUnstyledView?.() || (!this.View.Document._layout_showTags && this._props.Views.length === 1) ? null : (
+ return this.View.ComponentView?.isUnstyledView?.() || (!this.View.showTags && this._props.Views.length === 1) ? null : (
<div
className="tagsView-container"
ref={r => r && new ResizeObserver(action(() => this._props.Views.length === 1 && (this.View.TagPanelHeight = Math.max(0, (r?.getBoundingClientRect().height ?? 0) - this.InsetDist)))).observe(r)}
@@ -352,9 +353,10 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
}}>
<div className="tagsView-content" style={{ width: '100%' }}>
<div className="tagsView-list">
- {!tagsList.size && !facesList.size ? null : ( //
+ {this._props.Views.length === 1 && !this.View.showTags ? null : ( //
<IconButton style={{ width: '8px' }} tooltip="Close Menu" onPointerDown={() => this.setToEditing(!this._isEditing)} icon={<FontAwesomeIcon icon={this._isEditing ? 'chevron-up' : 'chevron-down'} size="sm" />} />
)}
+ <IconTagBox Views={this._props.Views} IsEditing={this._isEditing} />
{Array.from(tagsList).map((tag, i) => (
<TagItem
key={i}
@@ -388,7 +390,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
/>
</div>
<div className="tagsView-suggestions-box">
- {TagItem.AllTagCollectionDocs.map((doc, i) => {
+ {TagItem.AllTagCollectionDocs.map(doc => {
const tag = StrCast(doc.title);
return (
<Button
diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss
index 5ccc3d9a8..e5fb7aba6 100644
--- a/src/client/views/collections/CollectionCardDeckView.scss
+++ b/src/client/views/collections/CollectionCardDeckView.scss
@@ -29,18 +29,13 @@
transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955);
}
-.no-card-span{
+.no-card-span {
position: relative;
width: fit-content;
text-align: center;
font-size: 65px;
-
-
-
}
-
-
.card-item-inactive,
.card-item-active,
.card-item {
@@ -50,7 +45,6 @@
flex-direction: column;
}
-
.card-item-inactive {
opacity: 0.5;
}
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index fced9fd37..1952cc707 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -21,6 +21,7 @@ import { DocumentView } from '../nodes/DocumentView';
import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
import './CollectionCardDeckView.scss';
import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
+import { List } from '../../../fields/List';
enum cardSortings {
Time = 'time',
@@ -42,7 +43,6 @@ enum cardSortings {
@observer
export class CollectionCardView extends CollectionSubView() {
private _dropDisposer?: DragManager.DragDropDisposer;
- private _childDocumentWidth = 600; // target width of a Doc...
private _disposers: { [key: string]: IReactionDisposer } = {};
private _textToDoc = new Map<string, Doc>();
@@ -77,6 +77,12 @@ export class CollectionCardView extends CollectionSubView() {
this.setRegenerateCallback();
}
+ protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
+ this._dropDisposer?.();
+ if (ele) {
+ this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
+ }
+ };
/**
* Callback to ensure gpt's text versions of the child docs are updated
*/
@@ -94,10 +100,7 @@ export class CollectionCardView extends CollectionSubView() {
};
componentDidMount() {
- this.Document.childFilters_boolean = 'OR';
- this.childDocsWithoutLinks.forEach(c => {
- c[DocData].showIconTags = true;
- });
+ this.Document.childFilters_boolean = 'OR'; // bcz: really shouldn't be assigning to fields from within didMount -- this should be a default/override beahavior somehow
// Reaction to cardSort changes
this._disposers.sort = reaction(
@@ -117,28 +120,24 @@ export class CollectionCardView extends CollectionSubView() {
this._dropDisposer?.();
}
- @computed get cardSort_customField() {
- return StrCast(this.Document.cardSort_customField) as 'chat' | 'star' | 'idea' | 'like';
- }
-
@computed get cardSort() {
return StrCast(this.Document.cardSort) as cardSortings;
}
/**
- * how much to scale down the contents of the view so that everything will fit
+ * The child documents to be rendered-- either all of them except the Links or the docs in the currently active
+ * custom group
*/
- @computed get fitContentScale() {
- const length = Math.min(this.childDocsWithoutLinks.length, this._maxRowCount);
- return (this._childDocumentWidth * length) / this._props.PanelWidth();
+ @computed get childDocsWithoutLinks() {
+ return (this.childDocList as Doc[]).filter(l => l.type !== DocumentType.LINK);
}
/**
- * The child documents to be rendered-- either all of them except the Links or the docs in the currently active
- * custom group
+ * how much to scale down the contents of the view so that everything will fit
*/
- @computed get childDocsWithoutLinks() {
- return this.childDocs.filter(l => l.type !== DocumentType.LINK);
+ @computed get fitContentScale() {
+ const length = Math.min(this.childDocsWithoutLinks.length, this._maxRowCount);
+ return (this.childPanelWidth() * length) / this._props.PanelWidth();
}
/**
@@ -154,7 +153,7 @@ export class CollectionCardView extends CollectionSubView() {
* Number of rows of cards to be rendered
*/
@computed get numRows() {
- return Math.ceil(this.sortedDocs.length / 10);
+ return Math.ceil(this.sortedDocs.length / this._maxRowCount);
}
@action
@@ -177,8 +176,7 @@ export class CollectionCardView extends CollectionSubView() {
*/
inactiveDocs = () => this.childDocsWithoutLinks.filter(d => !DocumentView.SelectedDocs().includes(d));
- panelWidth = () => this._childDocumentWidth;
- panelHeight = (layout: Doc) => () => (this.panelWidth() * NumCast(layout._height)) / NumCast(layout._width);
+ childPanelWidth = () => NumCast(this.layoutDoc.childPanelWidth, this._props.PanelWidth() / 2);
onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive();
isChildContentActive = () => !!this.isContentActive();
@@ -245,6 +243,7 @@ export class CollectionCardView extends CollectionSubView() {
const currRow = Math.floor((mouseY - 100) / rowHeight); //rows start at 0
if (adjustedX < 0) {
+ console.log('DROP INDEX NO ');
return 0; // Before the first column
}
@@ -259,6 +258,7 @@ export class CollectionCardView extends CollectionSubView() {
index = Math.floor(adjustedX / cardWidth) + currRow * this._maxRowCount;
}
+ console.log('DROP INDEX = ' + index);
return index;
};
@@ -281,20 +281,41 @@ export class CollectionCardView extends CollectionSubView() {
};
/**
+ * Handles external drop of images/PDFs etc from outside Dash.
+ */
+ onExternalDrop = async (e: React.DragEvent): Promise<void> => {
+ super.onExternalDrop(e, {});
+ };
+
+ /**
* Resets all the doc dragging vairables once a card is dropped
* @param e
* @param de drop event
* @returns true if a card has been dropped, falls if not
*/
- onInternalDrop = undoable((e: Event, de: DragManager.DropEvent) => {
- if (de.complete.docDragData) {
- this._isACardBeingDragged = false;
- this._docDraggedIndex = -1;
- e.stopPropagation();
- return true;
- }
- return false;
- }, '');
+ onInternalDrop = undoable(
+ action((e: Event, de: DragManager.DropEvent) => {
+ if (de.complete.docDragData) {
+ this._isACardBeingDragged = false;
+ const dragIndex = this._docDraggedIndex;
+ if (dragIndex > -1) {
+ this._docDraggedIndex = -1;
+ const draggedDoc = DragManager.docsBeingDragged[0];
+ const sorted = this.sortedDocs;
+ const originalIndex = sorted.findIndex(doc => doc === draggedDoc);
+
+ this.Document.cardSort = '';
+ sorted.splice(originalIndex, 1);
+ sorted.splice(dragIndex, 0, draggedDoc);
+ this.dataDoc[this.fieldKey] = new List<Doc>(sorted);
+ }
+ e.stopPropagation();
+ return true;
+ }
+ return false;
+ }),
+ ''
+ );
@computed get sortedDocs() {
return this.sort(this.childDocsWithoutLinks, this.cardSort, BoolCast(this.Document.cardSort_isDesc), this._docDraggedIndex);
@@ -348,28 +369,29 @@ export class CollectionCardView extends CollectionSubView() {
* @returns
*/
sort = (docs: Doc[], sortType: cardSortings, isDesc: boolean, dragIndex: number) => {
- docs.sort((docA, docB) => {
- const [typeA, typeB] = (() => {
- switch (sortType) {
- case cardSortings.Time:
- return [DateCast(docA.author_date)?.date ?? Date.now(), DateCast(docB.author_date)?.date ?? Date.now()];
- case cardSortings.Color: {
- const d1 = DashColor(StrCast(docA.backgroundColor));
- const d2 = DashColor(StrCast(docB.backgroundColor));
- return [d1.hsv().hue(), d2.hsv().hue()];
+ sortType &&
+ docs.sort((docA, docB) => {
+ const [typeA, typeB] = (() => {
+ switch (sortType) {
+ case cardSortings.Time:
+ return [DateCast(docA.author_date)?.date ?? Date.now(), DateCast(docB.author_date)?.date ?? Date.now()];
+ case cardSortings.Color: {
+ const d1 = DashColor(StrCast(docA.backgroundColor));
+ const d2 = DashColor(StrCast(docB.backgroundColor));
+ return [d1.hsv().hue(), d2.hsv().hue()];
+ }
+ case cardSortings.Tag:
+ return [this.tagValue(docA) ?? 9999, this.tagValue(docB) ?? 9999];
+ case cardSortings.Chat:
+ return [NumCast(docA.chatIndex) ?? 9999, NumCast(docB.chatIndex) ?? 9999];
+ default:
+ return [StrCast(docA.type), StrCast(docB.type)];
}
- case cardSortings.Tag:
- return [this.tagValue(docA) ?? 9999, this.tagValue(docB) ?? 9999];
- case cardSortings.Chat:
- return [NumCast(docA.chatIndex) ?? 9999, NumCast(docB.chatIndex) ?? 9999];
- default:
- return [StrCast(docA.type), StrCast(docB.type)];
- }
- })();
+ })();
- const out = typeA < typeB ? -1 : typeA > typeB ? 1 : 0;
- return isDesc ? out : -out;
- });
+ const out = typeA < typeB ? -1 : typeA > typeB ? 1 : 0;
+ return isDesc ? out : -out;
+ });
if (dragIndex != -1) {
const draggedDoc = DragManager.docsBeingDragged[0];
const originalIndex = docs.findIndex(doc => doc === draggedDoc);
@@ -396,9 +418,11 @@ export class CollectionCardView extends CollectionSubView() {
ScreenToLocalTransform={screenToLocalTransform} // makes sure the box wrapper thing is in the right spot
isContentActive={emptyFunction}
isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
- PanelWidth={this.panelWidth}
- PanelHeight={this.panelHeight(doc)}
+ PanelWidth={this.childPanelWidth}
+ PanelHeight={() => this._props.PanelHeight() * this.fitContentScale}
+ dontCenter="y" // Don't center it vertically, because the grid it's in is already doing that and we don't want to do it twice.
dragAction={(this.Document.childDragAction ?? this._props.childDragAction) as dropActionType}
+ showTags={true}
dontHideOnDrag
/>
);
@@ -415,18 +439,18 @@ export class CollectionCardView extends CollectionSubView() {
// 13 - 3 = 10
const totalCards = this.sortedDocs.length;
// if 9 or less
- if (index < totalCards - (totalCards % 10)) {
+ if (index < totalCards - (totalCards % this._maxRowCount)) {
return this._maxRowCount;
}
// (3)
- return totalCards % 10;
+ return totalCards % this._maxRowCount;
};
/**
* Determines the index a card is in in a row
* @param realIndex
* @returns
*/
- overflowIndexCalc = (realIndex: number) => realIndex % 10;
+ overflowIndexCalc = (realIndex: number) => realIndex % this._maxRowCount;
/**
* Translates the cards in the second rows and beyond over to the right
* @param realIndex
@@ -434,7 +458,7 @@ export class CollectionCardView extends CollectionSubView() {
* @param calcRowCards
* @returns
*/
- translateOverflowX = (realIndex: number, calcRowCards: number) => (realIndex < this._maxRowCount ? 0 : (10 - calcRowCards) * (this.panelWidth() / 2));
+ translateOverflowX = (realIndex: number, calcRowCards: number) => (realIndex < this._maxRowCount ? 0 : (this._maxRowCount - calcRowCards) * (this.childPanelWidth() / 2));
/**
* Determines how far to translate a card in the y direction depending on its index, whether or not its being hovered, or if it's selected
@@ -613,6 +637,9 @@ export class CollectionCardView extends CollectionSubView() {
const rowCenterIndex = Math.min(this._maxRowCount, sortedDocs.length - rowIndex * this._maxRowCount) / 2;
return (rowCenterIndex - indexInRow) * 100 - 50;
};
+ const aspect = NumCast(doc.height) / NumCast(doc.width, 1);
+ const vscale = ((this._props.PanelHeight() * .95) * this.fitContentScale) / (aspect * this.childPanelWidth());
+ const hscale = this._maxRowCount / 2; // bcz: hack - the grid is divided evenly into maxRowCount cells, so the max scaling would be maxRowCount -- but making things that wide is ugly, so cap it off at half the window size
return (
<div
key={doc[Id]}
@@ -629,13 +656,13 @@ export class CollectionCardView extends CollectionSubView() {
);
}}
style={{
- width: this.panelWidth(),
+ width: this.childPanelWidth(),
height: 'max-content',
transform: `translateY(${this.calculateTranslateY(this._hoveredNodeIndex === index, isSelected, realIndex, amCards, calcRowIndex)}px)
- translateX(calc(${(isSelected ? translateIfSelected() : 0) + '% + ' + this.translateOverflowX(realIndex, amCards) + 'px'}))
+ translateX(calc(${isSelected ? translateIfSelected() : 0}% + ${this.translateOverflowX(realIndex, amCards)}px))
rotate(${!isSelected ? this.rotate(amCards, calcRowIndex) : 0}deg)
- scale(${isSelected ? 2 : this._hoveredNodeIndex === index ? 1.05 : 1})`,
- }}
+ scale(${isSelected ? `${Math.min(hscale, vscale) * 100}%` : this._hoveredNodeIndex === index ? 1.05 : 1})`,
+ }} // prettier-ignore
onMouseEnter={() => this.setHoveredNodeIndex(index)}>
{this.displayDoc(doc, childScreenToLocal)}
</div>
@@ -645,14 +672,13 @@ export class CollectionCardView extends CollectionSubView() {
render() {
const isEmpty = this.childDocsWithoutLinks.length === 0;
- const transformValue = `scale(${1 / this.fitContentScale})`;
- const heightValue = `${100 * this.fitContentScale}%`;
return (
<div
onPointerMove={e => this.onPointerMove(e)}
className="collectionCardView-outer"
ref={(ele: HTMLDivElement | null) => this.createDashEventsTarget(ele)}
+ onDrop={this.onExternalDrop.bind(this)}
style={{
background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string,
color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string,
@@ -660,8 +686,8 @@ export class CollectionCardView extends CollectionSubView() {
<div
className="card-wrapper"
style={{
- ...(!isEmpty && { transform: transformValue }),
- ...(!isEmpty && { height: heightValue }),
+ ...(!isEmpty && { transform: `scale(${1 / this.fitContentScale})` }),
+ ...(!isEmpty && { height: `${100 * this.fitContentScale}%` }),
gridAutoRows: `${100 / this.numRows}%`,
}}
onMouseLeave={() => this.setHoveredNodeIndex(-1)}>
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index ad868fd1e..4609be374 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable react/jsx-props-no-spreading */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index e97ee713e..1ac0b6d70 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable react/jsx-props-no-spreading */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import * as CSS from 'csstype';
import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
@@ -540,7 +539,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
if (this.pivotField) {
const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]);
if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
- // eslint-disable-next-line prefer-destructuring
type = types[0];
}
}
@@ -577,7 +575,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
let type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined;
const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]);
if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
- // eslint-disable-next-line prefer-destructuring
type = types[0];
}
const rows = () => (!this.isStackingView ? 1 : Math.max(1, Math.min(docList.length, Math.floor((this._props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))));
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 6aca8f2ca..99373da04 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -122,6 +122,9 @@ export function CollectionSubView<X>() {
);
return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types
}
+ /**
+ * This is the raw, stored list of children on a collection. If you modify this list, the database will be updated
+ */
@computed get childDocList() {
return Cast(this.dataField, listSpec(Doc));
}
@@ -218,7 +221,6 @@ export function CollectionSubView<X>() {
if (!cursors) {
proto.cursors = cursors = new List<CursorField>();
}
- // eslint-disable-next-line no-cond-assign
if (cursors.length > 0 && (ind = cursors.findIndex(entry => entry.data.metadata.id === id)) > -1) {
cursors[ind].setPosition(pos);
} else {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index dbf781e63..cbbf063b4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable react/jsx-props-no-spreading */
import { Bezier } from 'bezier-js';
import { Colors } from 'browndash-components';
import { Property } from 'csstype';
@@ -1211,7 +1210,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
for (let j = 0; j < otherCtrlPts.length - 3; j += 4) {
const neighboringSegment = i === j || i === j - 4 || i === j + 4;
// Ensuring that the curve intersected by the eraser is not checked for further ink intersections.
- // eslint-disable-next-line no-continue
if (ink?.Document === otherInk.Document && neighboringSegment) continue;
const otherCurve = new Bezier(otherCtrlPts.slice(j, j + 4).map(p => ({ x: p.X, y: p.Y })));
@@ -1481,8 +1479,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const childData = entry.pair.data;
return (
<CollectionFreeFormDocumentView
- // eslint-disable-next-line react/jsx-props-no-spreading, @typescript-eslint/no-explicit-any
- {...(OmitKeys(entry, ['replica', 'pair']).omit as any)}
+ {...(OmitKeys(entry, ['replica', 'pair']).omit as { x: number; y: number; z: number; width: number; height: number })}
key={childLayout[Id] + (entry.replica || '')}
Document={childLayout}
reactParent={this}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 4c357cf45..758e70508 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -67,6 +67,7 @@ export interface DocumentViewProps extends FieldViewSharedProps {
hideCaptions?: boolean;
contentPointerEvents?: Property.PointerEvents | undefined; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents
dontCenter?: 'x' | 'y' | 'xy';
+ showTags?: boolean;
childHideDecorationTitle?: boolean;
childHideResizeHandles?: boolean;
childDragAction?: dropActionType; // allows child documents to be dragged out of collection without holding the embedKey or dragging the doc decorations title bar.
@@ -1126,6 +1127,10 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
@observable public static CurrentlyPlaying: DocumentView[] = []; // audio or video media views that are currently playing
@observable public TagPanelHeight = 0;
+ @computed get showTags() {
+ return this.Document._layout_showTags || this._props.showTags;
+ }
+
@computed private get shouldNotScale() {
return (this.layout_fitWidth && !this.nativeWidth) || this.ComponentView?.isUnstyledView?.();
}
diff --git a/src/client/views/nodes/IconTagBox.scss b/src/client/views/nodes/IconTagBox.scss
index 211a961c1..90cc06092 100644
--- a/src/client/views/nodes/IconTagBox.scss
+++ b/src/client/views/nodes/IconTagBox.scss
@@ -2,23 +2,25 @@
.card-button-container {
display: flex;
- padding: 3px;
- position: absolute;
+ position: relative;
pointer-events: none;
- background-color: rgb(218, 218, 218);
+ background-color: rgb(218, 218, 218);
border-radius: 50px;
- transform: translateY(25px);
- align-items: center;
- justify-content: start;
+ align-items: center;
+ gap: 5px;
+ padding-left: 5px;
+ padding-right: 5px;
+ padding-top: 2px;
+ padding-bottom: 2px;
button {
pointer-events: auto;
- transform: translateY(-7.5px);
- width: 30px;
- height: 30px;
+ width: 20px;
+ height: 20px;
+ margin: auto;
+ padding: 0;
border-radius: 50%;
background-color: $dark-gray;
- margin: 5px;
background-color: transparent;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx
index f86372ec0..3d021fd73 100644
--- a/src/client/views/nodes/IconTagBox.tsx
+++ b/src/client/views/nodes/IconTagBox.tsx
@@ -6,18 +6,18 @@ import { observer } from 'mobx-react';
import React from 'react';
import { numberRange } from '../../../Utils';
import { Doc, StrListCast } from '../../../fields/Doc';
-import { DocData } from '../../../fields/DocSymbols';
-import { BoolCast, DocCast, NumCast, StrCast } from '../../../fields/Types';
-import { CollectionViewType } from '../../documents/DocumentTypes';
+import { StrCast } from '../../../fields/Types';
import { SnappingManager } from '../../util/SnappingManager';
import { undoable } from '../../util/UndoManager';
import { MainView } from '../MainView';
import { ObservableReactComponent } from '../ObservableReactComponent';
import { PropertiesView } from '../PropertiesView';
+import { DocumentView } from './DocumentView';
import './IconTagBox.scss';
export interface IconTagProps {
- doc: Doc;
+ Views: DocumentView[];
+ IsEditing: boolean;
}
/**
@@ -26,59 +26,16 @@ export interface IconTagProps {
*/
@observer
export class IconTagBox extends ObservableReactComponent<IconTagProps> {
- @computed
- get currentScale() {
- return NumCast((this._props.doc.embedContainer as Doc)?._freeform_scale, 1);
- }
-
constructor(props: IconTagProps) {
super(props);
}
- componentDidUpdate(): void {
- this._props.doc[DocData].tagHeight = 36 * this.currentScale;
+ @computed get View() {
+ return this._props.Views.lastElement();
+ }
+ @computed get currentScale() {
+ return this.View?.screenToLocalScale();
}
-
- /**
- * Renders the buttons to customize sorting depending on which group the card belongs to and the amount of total groups
- * @param doc
- * @param cardSort
- * @returns
- */
- renderButtons = (doc: Doc): JSX.Element | null => {
- const amButtons = StrListCast(Doc.UserDoc().myFilterHotKeyTitles).length + 1;
-
- const keys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles);
-
- const totalWidth = (amButtons - 1) * 35 + (amButtons - 1) * 2 * 5 + 6;
-
- const iconMap = (buttonID: number) => {
- return StrCast(Doc.UserDoc()[keys[buttonID]]) as IconProp;
- };
-
- const isCard = DocCast(this._props.doc.embedContainer).type_collection === CollectionViewType.Card;
-
- return (
- <div
- className="card-button-container"
- style={{
- transformOrigin: 'top left',
- transform: `scale(${isCard ? 2 : 0.6 / this.currentScale})
- translateY(${doc[DocData].showLabels ? NumCast(doc[DocData].keywordHeight) * (1 - this.currentScale) : 0}px)
- `,
- width: `${totalWidth}px`,
- fontSize: '50px',
- }}>
- {numberRange(amButtons - 1).map(i => (
- <Tooltip key={i} title={<div className="dash-tooltip">Click to add/remove this card from the {iconMap(i).toString()} group</div>}>
- <button key={i} type="button" onClick={() => this.toggleButton(doc, iconMap(i))}>
- {this.getButtonIcon(doc, iconMap(i))}
- </button>
- </Tooltip>
- ))}
- </div>
- );
- };
/**
* Opens the filter panel in the properties menu
@@ -93,13 +50,13 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
};
/**
- * Toggles the buttons between on and off when creating custom sort groupings/changing those created by gpt
- * @param childPairIndex
* @param buttonID
* @param doc
*/
- toggleButton = undoable((doc: Doc, icon: string) => {
- BoolCast(doc[icon]) ? (doc[icon] = false) : (doc[icon] = true);
+ setIconTag = undoable((icon: string, state: boolean) => {
+ this._props.Views.forEach(view => {
+ view.dataDoc[icon] = state;
+ });
}, 'toggle card tag');
/**
@@ -112,10 +69,43 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
const isActive = doc[icon.toString()];
const color = isActive ? '#4476f7' : '#323232';
- return <FontAwesomeIcon icon={icon} style={{ color, height: '30px', width: '30px' }} />;
+ return <FontAwesomeIcon icon={icon} style={{ color, height: '20px', width: '20px' }} />;
};
+ /**
+ * Renders the buttons to customize sorting depending on which group the card belongs to and the amount of total groups
+ */
render() {
- return <>{this.renderButtons(this._props.doc)}</>;
+ const amButtons = StrListCast(Doc.UserDoc().myFilterHotKeyTitles).length + 1;
+
+ const keys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles);
+
+ const iconMap = (buttonID: number) => {
+ return StrCast(Doc.UserDoc()[keys[buttonID]]) as IconProp;
+ };
+ const buttons = numberRange(amButtons - 1)
+ .filter(i => this._props.IsEditing || this.View.Document[iconMap(i).toString()] || (DocumentView.Selected.length === 1 && this.View.IsSelected))
+ .map(i => (
+ <Tooltip key={i} title={<div className="dash-tooltip">Click to add/remove this card from the {iconMap(i).toString()} group</div>}>
+ <button
+ key={i}
+ type="button"
+ onClick={() => {
+ const state = this.View.Document[iconMap(i).toString()];
+ this.setIconTag(iconMap(i), !state);
+ }}>
+ {this.getButtonIcon(this.View.Document, iconMap(i))}
+ </button>
+ </Tooltip>
+ ));
+ return !buttons.length ? null : (
+ <div
+ className="card-button-container"
+ style={{
+ fontSize: '50px',
+ }}>
+ {buttons}
+ </div>
+ );
}
}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 343e255dc..e0331a422 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -275,6 +275,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
ele.append(contents);
}
this._selectionHTML = ele?.innerHTML;
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e) {
/* empty */
}
diff --git a/src/client/views/search/FaceRecognitionHandler.tsx b/src/client/views/search/FaceRecognitionHandler.tsx
index c507e54b6..6f70e96ab 100644
--- a/src/client/views/search/FaceRecognitionHandler.tsx
+++ b/src/client/views/search/FaceRecognitionHandler.tsx
@@ -30,6 +30,7 @@ import { DocumentManager } from '../../util/DocumentManager';
* face_annos - a list of face annotations, where each anno has
*/
export class FaceRecognitionHandler {
+ // eslint-disable-next-line no-use-before-define
static _instance: FaceRecognitionHandler;
private _apiModelReady = false;
private _pendingAPIModelReadyDocs: Doc[] = [];
@@ -221,7 +222,7 @@ export class FaceRecognitionHandler {
const annos = [] as Doc[];
const scale = NumCast(imgDoc.data_nativeWidth) / img.width;
const showTags= imgDocFaceDescriptions.length > 1;
- imgDocFaceDescriptions.forEach((fd, i) => {
+ imgDocFaceDescriptions.forEach(fd => {
const faceDescriptor = new List<number>(Array.from(fd.descriptor));
const matchedUniqueFace = this.findMatchingFaceDoc(fd.descriptor) ?? this.createUniqueFaceDoc(activeDashboard);
const faceAnno = Docs.Create.FreeformDocument([], {