From 4c1933b67dad98c7e5e7f77b0bebc8ae2a612bca Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 12 Sep 2024 10:24:55 -0400 Subject: added menu option --- .../collections/collectionFreeForm/CollectionFreeFormView.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index dbf781e63..38c85edce 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1949,6 +1949,14 @@ export class CollectionFreeFormView extends CollectionSubView { + this.unprocessedDocs.push(...this.childDocs.filter(doc => doc.type === DocumentType.INK)); + CollectionFreeFormView.collectionsWithUnprocessedInk.add(this); + }, + icon: 'pen', + }); !mores && ContextMenu.Instance.addItem({ description: 'More...', subitems: moreItems, icon: 'eye' }); }; -- cgit v1.2.3-70-g09d2 From 03374621c3739768850e4e183fbb5d1c278b2290 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 20 Sep 2024 15:01:28 -0400 Subject: removed assignment of OR filter mode from card view. not sure it's needed, but shouldn't be set automatically anyway. --- src/client/documents/Documents.ts | 4 ++-- src/client/views/collections/CollectionCardDeckView.tsx | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index aac15059e..00edaddcc 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -319,11 +319,11 @@ export class DocumentOptions { contextMenuLabels?: List; contextMenuIcons?: List; childContentPointerEvents?: string; // 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 - childFilters_boolean?: string; + childFilters_boolean?: STRt = new StrInfo('boolean operator to apply to filters on different metadata fields. Value should be AND or OR. Default is AND'); childFilters?: List; childLimitHeight?: NUMt = new NumInfo('whether to limit the height of collection children. 0 - means height can be no bigger than width', false); childLayoutTemplate?: Doc; // template for collection to use to render its children (see PresBox layout in tree view) - childLayoutString?: string; // template string for collection to use to render its children + childLayoutString?: STRt = new StrInfo('JSX layout string for rendering children of a (collection) Doc'); // template string for collection to use to render its children childDocumentsActive?: BOOLt = new BoolInfo('whether child documents are active when parent is document active'); childLayoutFitWidth?: BOOLt = new BoolInfo("whether a child doc's fitWith should be overriden by collection"); childDontRegisterViews?: BOOLt = new BoolInfo('whether child document views should be registered so that they can be found when following links, etc'); diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index d9c27254a..70b751f4c 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -99,8 +99,6 @@ export class CollectionCardView extends CollectionSubView() { }; componentDidMount() { - 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( () => GPTPopup.Instance.visible, -- cgit v1.2.3-70-g09d2 From 124c69af0941bd1ed5b03f3275aef37007f06ea7 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 23 Sep 2024 03:45:04 -0400 Subject: made dropping links marks them as svgs. fixed doc drag interactions when items are selected to not have decorations bounce. fixed card deck to not shrink cards when card deck is short small and items are selected. fixied dragging cards when colletcion is nested to not have decorations bounce, and to allow rapid selection and dragging of cards. fixed dragging docs into/out of card view. --- src/client/views/DocumentDecorations.tsx | 11 ++- .../views/collections/CollectionCardDeckView.tsx | 83 +++++++++++++--------- src/client/views/linking/LinkMenuItem.tsx | 2 +- 3 files changed, 62 insertions(+), 34 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index c3bcfdcca..fc4b1f0af 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -2,7 +2,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; import { IconButton } from 'browndash-components'; -import { action, computed, makeObservable, observable, runInAction } from 'mobx'; +import { action, computed, makeObservable, observable, runInAction, trace } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { FaUndo } from 'react-icons/fa'; @@ -36,6 +36,7 @@ import { ImageBox } from './nodes/ImageBox'; import { OpenWhere, OpenWhereMod } from './nodes/OpenWhere'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { TagsView } from './TagsView'; +import { setTime } from 'react-datepicker/dist/date_utils'; interface DocumentDecorationsProps { PanelWidth: number; @@ -643,7 +644,9 @@ export class DocumentDecorations extends ObservableReactComponent this.forceRender++)); + return null; + } + console.log('Renderedr'); + // sharing const acl = GetEffectiveAcl(!this._showLayoutAcl ? Doc.GetProto(seldocview.Document) : seldocview.Document); const docShareMode = HierarchyMapping.get(acl)!.name; diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index 70b751f4c..e5a6ebc7f 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -1,4 +1,4 @@ -import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction } from 'mobx'; +import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, trace } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { ClientUtils, DashColor, returnFalse, returnZero } from '../../../ClientUtils'; @@ -46,8 +46,9 @@ export class CollectionCardView extends CollectionSubView() { private _dropDisposer?: DragManager.DragDropDisposer; private _disposers: { [key: string]: IReactionDisposer } = {}; private _textToDoc = new Map(); + private _dropped = false; // indicate when a card doc has just moved; - @observable _forceChildXf = false; + @observable _forceChildXf = 0; @observable _hoveredNodeIndex = -1; @observable _docRefs = new ObservableMap(); @observable _maxRowCount = 10; @@ -99,6 +100,7 @@ export class CollectionCardView extends CollectionSubView() { }; componentDidMount() { + this._props.setContentViewBox?.(this); // Reaction to cardSort changes this._disposers.sort = reaction( () => GPTPopup.Instance.visible, @@ -110,6 +112,17 @@ export class CollectionCardView extends CollectionSubView() { } } ); + // if card deck moves, then the child doc views are hidden so their screen to local transforms will return empty rectangles + // when inquired from the dom (below in childScreenToLocal). When the doc is actually renders, we need to act like the + // dash data just changed and trigger a React involidation with the correct data (read from the dom). + this._disposers.child = reaction( + () => [this.Document.x, this.Document.y], + () => { + if (!Array.from(this._docRefs.values()).every(dv => dv.ContentDiv?.getBoundingClientRect().width)) { + setTimeout(action(() => this._forceChildXf++)); + } + } + ); } componentWillUnmount() { @@ -174,6 +187,7 @@ export class CollectionCardView extends CollectionSubView() { inactiveDocs = () => this.childDocsWithoutLinks.filter(d => !DocumentView.SelectedDocs().includes(d)); childPanelWidth = () => NumCast(this.layoutDoc.childPanelWidth, this._props.PanelWidth() / 2); + childPanelHeight = () => this._props.PanelHeight() * this.fitContentScale; onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick); isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive(); isChildContentActive = () => !!this.isContentActive(); @@ -265,14 +279,7 @@ export class CollectionCardView extends CollectionSubView() { @action onPointerMove = (x: number, y: number) => { - this._docDraggedIndex = -1; - if (DragManager.docsBeingDragged.length) { - const newIndex = this.findCardDropIndex(x, y); - - if (newIndex !== this._docDraggedIndex && newIndex != -1) { - this._docDraggedIndex = newIndex; - } - } + this._docDraggedIndex = DragManager.docsBeingDragged.length ? this.findCardDropIndex(x, y) : -1; }; /** @@ -304,6 +311,7 @@ export class CollectionCardView extends CollectionSubView() { if (de.complete.docDragData.removeDocument?.(draggedDoc)) { this.dataDoc[this.fieldKey] = new List(sorted); } + this._dropped = true; } e.stopPropagation(); return true; @@ -338,7 +346,8 @@ export class CollectionCardView extends CollectionSubView() { * @param isDesc * @returns */ - sort = (docs: Doc[], sortType: cardSortings, isDesc: boolean, dragIndex: number) => { + sort = (docsIn: Doc[], sortType: cardSortings, isDesc: boolean, dragIndex: number) => { + const docs = docsIn.slice(); // need make new object list since sort() modifies the incoming list which confuses mobx caching sortType && docs.sort((docA, docB) => { const [typeA, typeB] = (() => { @@ -362,21 +371,21 @@ export class CollectionCardView extends CollectionSubView() { const out = typeA < typeB ? -1 : typeA > typeB ? 1 : 0; return isDesc ? out : -out; }); - if (dragIndex != -1) { + if (dragIndex !== -1) { const draggedDoc = DragManager.docsBeingDragged[0]; const originalIndex = docs.findIndex(doc => doc === draggedDoc); originalIndex !== -1 && docs.splice(originalIndex, 1); - docs.splice(dragIndex, 0, draggedDoc); + draggedDoc && docs.splice(dragIndex, 0, draggedDoc); } - return [...docs]; // need to spread docs into a new object list since sort() modifies the incoming list which confuses mobx caching + return docs; }; displayDoc = (doc: Doc, screenToLocalTransform: () => Transform) => ( r?.ContentDiv && this._docRefs.set(doc, r))} + ref={action((r: DocumentView) => (!r?.ContentDiv ? this._docRefs.delete(doc) : this._docRefs.set(doc, r)))} Document={doc} NativeWidth={returnZero} NativeHeight={returnZero} @@ -385,11 +394,12 @@ export class CollectionCardView extends CollectionSubView() { renderDepth={this._props.renderDepth + 1} LayoutTemplate={this._props.childLayoutTemplate} LayoutTemplateString={this._props.childLayoutString} + containerViewPath={this.childContainerViewPath} 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.childPanelWidth} - PanelHeight={() => this._props.PanelHeight() * this.fitContentScale} + PanelHeight={this.childPanelHeight} 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} @@ -591,7 +601,10 @@ export class CollectionCardView extends CollectionSubView() { const isSelected = view?.ComponentView?.isAnyChildContentActive?.() || view?.IsSelected ? true : false; const childScreenToLocal = () => { + // need to explicitly trigger an invalidation since we're reading everything from the Dom this._forceChildXf; + this._props.ScreenToLocalTransform(); + const dref = this._docRefs.get(doc); const { translateX, translateY, scale } = ClientUtils.GetScreenTransform(dref?.ContentDiv); return new Transform(-translateX + (dref?.centeringX || 0) * scale, @@ -606,25 +619,30 @@ export class CollectionCardView extends CollectionSubView() { return (rowCenterIndex - indexInRow) * 100 - 50; }; const aspect = NumCast(doc.height) / NumCast(doc.width, 1); - const vscale = Math.min((this._props.PanelHeight() * 0.95 * this.fitContentScale) / (aspect * this.childPanelWidth()), - (this._props.PanelHeight() - 80) / (aspect * (this._props.PanelWidth() / 10))); // prettier-ignore + const vscale = Math.max(1,Math.min((this._props.PanelHeight() * 0.95 * this.fitContentScale) / (aspect * this.childPanelWidth()), + (this._props.PanelHeight() - 80) / (aspect * (this._props.PanelWidth() / 10)))); // prettier-ignore const hscale = Math.min(this.sortedDocs.length, 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 (
{ - if (DocumentView.SelectedDocs().includes(doc)) return; - // this turns off documentDecorations during a transition, then turns them back on afterward. - SnappingManager.SetIsResizing(doc[Id]); - setTimeout( - action(() => { - SnappingManager.SetIsResizing(undefined); - this._forceChildXf = !this._forceChildXf; - }), - 900 - ); - }} + onPointerUp={action(() => { + // if a card doc has just moved, or a card is selected and in front, then ignore this event + if (DocumentView.SelectedDocs().includes(doc) || this._dropped) { + this._dropped = false; + } else { + // otherwise, turn off documentDecorations becase we're in a selection transition and want to avoid artifacts. + // Turn them back on when the animation has completed and the render and backend structures are in synch + SnappingManager.SetIsResizing(doc[Id]); + setTimeout( + action(() => { + SnappingManager.SetIsResizing(undefined); + this._forceChildXf++; + }), + 600 + ); + } + })} style={{ width: this.childPanelWidth(), height: 'max-content', @@ -633,7 +651,7 @@ export class CollectionCardView extends CollectionSubView() { rotate(${!isSelected ? this.rotate(amCards, calcRowIndex) : 0}deg) scale(${isSelected ? `${Math.min(hscale, vscale) * 100}%` : this._hoveredNodeIndex === index ? 1.05 : 1})`, }} // prettier-ignore - onMouseEnter={() => this.setHoveredNodeIndex(index)}> + onPointerEnter={() => !SnappingManager.IsDragging && this.setHoveredNodeIndex(index)}> {this.displayDoc(doc, childScreenToLocal)}
); @@ -647,7 +665,8 @@ export class CollectionCardView extends CollectionSubView() {
this.createDashEventsTarget(ele)} - onPointerMove={e => this.onPointerMove(e.clientX, e.clientY)} + onPointerLeave={action(() => (this._docDraggedIndex = -1))} + onPointerMove={e => this.onPointerMove(...this._props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY))} onDrop={this.onExternalDrop.bind(this)} style={{ background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string, diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index f54d8311d..b24fca8e2 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -90,7 +90,7 @@ export class LinkMenuItem extends ObservableReactComponent { moveEv => { const dragData = new DragManager.DocumentDragData([this._props.linkDoc], dropActionType.embed); dragData.dropPropertiesToRemove = ['hidden']; - DragManager.StartDocumentDrag([this._editRef.current!], dragData, moveEv.x, moveEv.y); + DragManager.StartDocumentDrag([this._editRef.current!], dragData, moveEv.x, moveEv.y, undefined, e => (this._props.linkDoc._layout_isSvg = true)); return true; }, emptyFunction, -- cgit v1.2.3-70-g09d2 From 0da361d49f063d43a0ad87d13665595f9e383e13 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 24 Sep 2024 17:44:49 -0400 Subject: fixed screen to local for carousel views (especially when in lightbox) --- .../views/collections/CollectionCarousel3DView.tsx | 35 +++++++++++++++++----- .../views/collections/CollectionCarouselView.tsx | 3 ++ 2 files changed, 30 insertions(+), 8 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index 54cc02825..139aebb02 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -14,6 +14,7 @@ import { DocumentView } from '../nodes/DocumentView'; import { FocusViewOptions } from '../nodes/FocusViewOptions'; import './CollectionCarousel3DView.scss'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; +import { Transform } from '../../util/Transform'; // eslint-disable-next-line @typescript-eslint/no-var-requires const { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } = require('../global/globalCssVariables.module.scss'); @@ -46,15 +47,32 @@ export class CollectionCarousel3DView extends CollectionSubView() { } centerScale = Number(CAROUSEL3D_CENTER_SCALE); + sideScale = Number(CAROUSEL3D_SIDE_SCALE); panelWidth = () => this._props.PanelWidth() / 3; - panelHeight = () => this._props.PanelHeight() * Number(CAROUSEL3D_SIDE_SCALE); + panelHeight = () => this._props.PanelHeight() * this.sideScale; onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick); isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive(); isChildContentActive = () => !!this.isContentActive(); - childScreenToLocal = () => - this._props // document's left is the panel shifted by the doc's index * panelWidth/#docs. But it scales by centerScale around its center, so it's left moves left by the distance of the left from the center (panelwidth/2) * the scale delta (centerScale-1) - .ScreenToLocalTransform() // the top behaves the same way ecept it's shifted by the 'top' amount specified for the panel in css and then by the scale factor. - .translate(-this.panelWidth() + ((this.centerScale - 1) * this.panelWidth()) / 2, -((Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight()) + ((this.centerScale - 1) * this.panelHeight()) / 2) + childScreenLeftToLocal = () => + this._props + .ScreenToLocalTransform() + .scale(this._props.NativeDimScaling?.() || 1) + .translate(-(this.panelWidth() - this.panelWidth() * this.sideScale) / 2, -(this.panelHeight() - this.panelHeight() * this.sideScale) / 2 - (Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight()) + .scale(1 / this.sideScale); + childScreenRightToLocal = () => + this._props + .ScreenToLocalTransform() + .scale(this._props.NativeDimScaling?.() || 1) + .translate(-2 * this.panelWidth() - (this.panelWidth() - this.panelWidth() * this.sideScale) / 2, -(this.panelHeight() - this.panelHeight() * this.sideScale) / 2 - (Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight()) + .scale(1 / this.sideScale); + childCenterScreenToLocal = () => + this._props + .ScreenToLocalTransform() + .scale(this._props.NativeDimScaling?.() || 1) + .translate( + -this.panelWidth() + ((this.centerScale - 1) * this.panelWidth()) / 2, // Focused Doc is shifted right by 1/3 panel width then left by increased size percent of center * 1/2 * panel width / 3 + -((Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight()) + ((this.centerScale - 1) * this.panelHeight()) / 2 + ) // top is top margin % of panelHeight - increased size percent of center * panelHeight / 2 .scale(1 / this.centerScale); focus = (anchor: Doc, options: FocusViewOptions): Opt => { @@ -66,9 +84,10 @@ export class CollectionCarousel3DView extends CollectionSubView() { index !== -1 && (this.layoutDoc._carousel_index = index); return undefined; }; + @computed get content() { const currentIndex = NumCast(this.layoutDoc._carousel_index); - const displayDoc = (childPair: { layout: Doc; data: Doc }) => ( + const displayDoc = (childPair: { layout: Doc; data: Doc }, dxf: () => Transform) => ( (
- {displayDoc(childPair)} + {displayDoc(childPair, index < currentIndex ? this.childScreenLeftToLocal : index === currentIndex ? this.childCenterScreenToLocal : this.childScreenRightToLocal)}
)); } diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 974cd3e36..75886b30d 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -154,6 +154,8 @@ export class CollectionCarouselView extends CollectionSubView() { ? false : undefined; + childScreenToLocalXf = () => this._props.ScreenToLocalTransform().scale(this._props.NativeDimScaling?.() || 1); + renderDoc = (doc: Doc, showCaptions: boolean, overlayFunc?: (r: DocumentView | null) => void) => { return ( Date: Wed, 25 Sep 2024 11:35:42 -0400 Subject: removed star icon from carousel -- added showTags instead. --- src/client/views/collections/CollectionCarouselView.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 75886b30d..6ea263215 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -71,7 +71,7 @@ export class CollectionCarouselView extends CollectionSubView() { @computed get marginX() { return NumCast(this.layoutDoc.caption_xMargin, 50); } // prettier-ignore @computed get carouselIndex() { return NumCast(this.layoutDoc._carousel_index) % this.carouselItems.length; } // prettier-ignore @computed get carouselItems() { - return DocListCast(this.childDocList) + return this.childDocs .filter(doc => doc.type !== DocumentType.LINK) .filter(doc => { switch (StrCast(this.layoutDoc.filterOp)) { @@ -165,6 +165,7 @@ export class CollectionCarouselView extends CollectionSubView() { NativeWidth={returnZero} NativeHeight={returnZero} fitWidth={undefined} + showTags={true} containerViewPath={this.childContainerViewPath} setContentViewBox={undefined} ScreenToLocalTransform={this.childScreenToLocalXf} @@ -247,9 +248,6 @@ export class CollectionCarouselView extends CollectionSubView() {
-
- -
this.setPracticeVal(e, practiceVal.MISSED)} style={{ visibility: this.layoutDoc.filterOp === cardMode.PRACTICE ? 'visible' : 'hidden' }}>
-- cgit v1.2.3-70-g09d2 From 9808c01be554ab210083af9c1ef7fd45fbd84c19 Mon Sep 17 00:00:00 2001 From: Zachary Zhang Date: Wed, 25 Sep 2024 21:26:31 -0400 Subject: fix scribble gesture to use bezier curve intersections --- src/client/views/GestureOverlay.tsx | 196 ++++++++++----------- .../collectionFreeForm/CollectionFreeFormView.tsx | 1 - 2 files changed, 91 insertions(+), 106 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 935eeb251..7e396d475 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -1,4 +1,5 @@ import * as fitCurve from 'fit-curve'; +import { Bezier } from 'bezier-js'; import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -95,7 +96,6 @@ export class GestureOverlay extends ObservableReactComponent { if (!(e.target as HTMLElement)?.className?.toString().startsWith('lm_')) { @@ -139,52 +139,52 @@ export class GestureOverlay extends ObservableReactComponent view.ComponentView instanceof CollectionFreeFormView); - const cuspArray = this.getCusps(); - const cuspBooleanArray: boolean[] = []; + console.log(inkData.map(ink => ({ x: ink.X, y: ink.Y }))); + let intersectArray: boolean[] = []; + const cuspArray = this.getCusps(inkData); + console.log(cuspArray.length); + for (let i = 0; i < cuspArray.length; i++) { + intersectArray[i] = false; + } const docsToDelete: Doc[] = []; - const childDocs = (ffView?.ComponentView as CollectionFreeFormView).childDocs; + const childDocs = (ffView?.ComponentView as CollectionFreeFormView).childDocs.slice(0, -1); childDocs.filter(doc => doc.type === 'ink').map(doc => DocumentView.getDocumentView(doc, DocumentView.getDocumentView(doc))); if ((ffView?.ComponentView as CollectionFreeFormView).childDocs) { //how many cusps the scribble hsa - if (cuspArray.length > 4) { - for (let i = 0; i < cuspArray.length - 2; i++) { - let hasDocInTriangle = false; - for (const doc of childDocs) { - const point1 = cuspArray[i]; - const point2 = cuspArray[i + 1]; - const point3 = cuspArray[i + 2]; - const triangleObject = { p1: { X: point1.X, Y: point1.Y }, p2: { X: point2.X, Y: point2.Y }, p3: { X: point3.X, Y: point3.Y } }; - const otherInk = DocumentView.getDocumentView(doc)?.ComponentView as InkingStroke; - if (otherInk instanceof InkingStroke) { - const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] }; - const otherScreenPts = otherInkData.map(point => otherInk.ptToScreen(point)); - if (doc.title === 'line') { - if (this.doesLineIntersectTriangle(otherScreenPts, triangleObject)) { - docsToDelete.push(doc); - hasDocInTriangle = true; - cuspBooleanArray.push(true); - } + for (const doc of childDocs) { + const otherInk = DocumentView.getDocumentView(doc)?.ComponentView as InkingStroke; + if (otherInk instanceof InkingStroke) { + const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] }; + const otherScreenPts = otherInkData.map(point => otherInk.ptToScreen(point)); + if (this.isRectangleOverlap(this.getExtremeCoordinates(otherScreenPts), this.getExtremeCoordinates(inkData))) { + console.log('somethings in'); + const intersects = this.doInksIntersect(inkData, otherScreenPts); + console.log(intersects); + intersects.forEach(intersect => { + let percentage = ''; + if (intersect.includes('/')) { + const leftOfSlash = intersect.split('/')[0]; + percentage = leftOfSlash; } else { - if (this.isAnyPointInTriangle(triangleObject, otherScreenPts)) { - docsToDelete.push(doc); - hasDocInTriangle = true; - cuspBooleanArray.push(true); - } + percentage = intersect; } - } + intersectArray[Math.floor((percentage as unknown as number) * cuspArray.length)] = true; + docsToDelete.push(doc); + }); } - cuspBooleanArray.push(hasDocInTriangle); - } - if (this.determineIfScribble(cuspBooleanArray)) { - docsToDelete.forEach(doc => { - ffView?.ComponentView?.removeDocument?.(doc); - }); - this._points = []; - return true; } } + console.log(intersectArray); + if (intersectArray.length > 3 && this.determineIfScribble(intersectArray)) { + console.log('is a scribble'); + docsToDelete.forEach(doc => { + ffView?.ComponentView?.removeDocument?.(doc); + }); + this._points = []; + return true; + } } return false; } @@ -198,7 +198,7 @@ export class GestureOverlay extends ObservableReactComponent value).length; const percentageTrues = trueCount / cuspBooleanArray.length; - return percentageTrues > 0.65 || hasObjectInFirstAndLast25; + return percentageTrues > 0.5 || hasObjectInFirstAndLast25; } /** * determines if two rectangles are overlapping each other @@ -221,38 +221,6 @@ export class GestureOverlay extends ObservableReactComponent Math.abs((v1.X * (v2.Y - v3.Y) + v2.X * (v3.Y - v1.Y) + v3.X * (v1.Y - v2.Y)) / 2.0); - - const A = area(triangle.p1, triangle.p2, triangle.p3); - - const A1 = area(pt, triangle.p2, triangle.p3); - const A2 = area(triangle.p1, pt, triangle.p3); - const A3 = area(triangle.p1, triangle.p2, pt); - - return A === A1 + A2 + A3; - } - /** - * determines if any points in an array are in a triangle - * @param triangle the triangle with 3 points - * @param points the point in question - * @returns true or false if point is in triangle - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - isAnyPointInTriangle(triangle: any, points: any[]): boolean { - for (const point of points) { - if (this.isPointInTriangle(point, triangle)) { - return true; - } - } - return false; - } /** * determines if a line intersects a triangle. used for scribble gesture since the line doesnt have a lot * of points across is so isPointInTriangle will not work for it. @@ -261,20 +229,25 @@ export class GestureOverlay extends ObservableReactComponent view.ComponentView instanceof CollectionFreeFormView); + if (ffView && ffView.ComponentView instanceof CollectionFreeFormView) { + for (let i = 0; i < scribble.length - 3; i += 4) { + // iterate over each segment of bezier curve + for (let j = 0; j < inkStroke.length - 3; j += 4) { + const intersectCurve: Bezier = InkField.Segment(scribble, i); // other curve + const eraserCurve: Bezier = InkField.Segment(inkStroke, j); // eraser curve + if (ffView && ffView.ComponentView instanceof CollectionFreeFormView) { + const result = (ffView.ComponentView as CollectionFreeFormView).bintersects(intersectCurve, eraserCurve)[0]; + if (result !== undefined) { + intersectArray.push(result.toString()); + } + } + } } } - - return false; + return intersectArray; } /** * used in doesLineIntersectTriangle, splits up the triangle into 3 lines and runs this method @@ -284,20 +257,21 @@ export class GestureOverlay extends ObservableReactComponent= 0 && r <= 1 && s >= 0 && s <= 1; + doLinesIntersect(points: any, line: any): boolean { + const ffView = DocumentView.allViews().find(view => view.ComponentView instanceof CollectionFreeFormView); + if (ffView && ffView.ComponentView instanceof CollectionFreeFormView) { + for (let i = 0; i < points.length - 3; i += 4) { + // iterate over each segment of bezier curve + for (let j = 0; j < line.length - 3; j += 4) { + const intersectCurve: Bezier = InkField.Segment(points, i); // other curve + const eraserCurve: Bezier = InkField.Segment(line, j); // eraser curve + if (ffView && ffView.ComponentView instanceof CollectionFreeFormView) { + //(ffView.ComponentView as CollectionFreeFormView).bintersects(); + } + } + } + } + return false; } dryInk = () => { @@ -343,18 +317,31 @@ export class GestureOverlay extends ObservableReactComponent { + const ffView = DocumentView.allViews().find(view => view.ComponentView instanceof CollectionFreeFormView); + const scribbleInk = (ffView?.ComponentView as CollectionFreeFormView).childDocs[(ffView?.ComponentView as CollectionFreeFormView).childDocs.length - 1]; + //this.isScribble((DocumentView.getDocumentView(scribbleInk)?.ComponentView as InkingStroke).inkScaledData().inkData); + }, 1); return this.dispatchGesture(name); case Gestures.RightAngle: return this.convertToText().length > 0; default: - case Gestures.Stroke: - return this.isScribble(); } })(Score < 0.7 ? Gestures.Stroke : (Name as Gestures)); // if no gesture (or if the gesture was unsuccessful), "dry" the stroke into an ink document - if (!actionPerformed) this.dryInk(); + if (!actionPerformed) { + this.dryInk(); + setTimeout(() => { + const ffView = DocumentView.allViews().find(view => view.ComponentView instanceof CollectionFreeFormView); + const scribbleInk = (ffView?.ComponentView as CollectionFreeFormView).childDocs[(ffView?.ComponentView as CollectionFreeFormView).childDocs.length - 1]; + if (this.isScribble((DocumentView.getDocumentView(scribbleInk)?.ComponentView as InkingStroke).inkScaledData().inkData)) { + ffView?.ComponentView?.removeDocument?.(scribbleInk); + } + }, 1); + } } this._points.length = 0; + //console.log((DocumentView.getDocumentView(scribbleInk)?.ComponentView as InkingStroke).inkScaledData()); }; /** * used in the rightAngle gesture to convert handwriting into text. will only work on collections @@ -374,7 +361,7 @@ export class GestureOverlay extends ObservableReactComponent ({ X: p.X, Y: p.Y })); + getCusps(points: InkData) { const arrayOfPoints: { X: number; Y: number }[] = []; arrayOfPoints.push(points[0]); for (let i = 0; i < points.length - 2; i++) { @@ -424,8 +410,8 @@ export class GestureOverlay extends ObservableReactComponent Date: Fri, 27 Sep 2024 14:19:40 -0400 Subject: fixed linting. cleaned up scribble erase code and fixed several issues with determining if cusp intersections amounted to a scribble. also fixed recognition of lines to not recognize scribbles. --- eslint.config.mjs | 2 +- package-lock.json | 14 ++ src/.DS_Store | Bin 14340 -> 10244 bytes src/Utils.ts | 3 +- src/client/views/GestureOverlay.tsx | 276 +++++++-------------- src/client/views/InkingStroke.tsx | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 24 +- src/fields/InkField.ts | 20 ++ 8 files changed, 137 insertions(+), 208 deletions(-) (limited to 'src/client/views/collections') diff --git a/eslint.config.mjs b/eslint.config.mjs index aebdc20d0..f7063caa5 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -48,7 +48,7 @@ export default [ 'no-return-assign': 'error', 'no-await-in-loop': 'error', 'no-loop-func': 'error', - '@typescript-eslint/no-cond-assign': 'error', + 'no-cond-assign': 'error', 'no-use-before-define': 'error', '@typescript-eslint/no-explicit-any': 'error', 'no-restricted-globals': ['error', 'event'], diff --git a/package-lock.json b/package-lock.json index fac1cc20b..727b22f34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -130,6 +130,7 @@ "https": "^1.0.0", "https-browserify": "^1.0.0", "i": "^0.3.7", + "iink-ts": "^1.0.5", "image-data-uri": "^2.0.1", "image-size": "^1.0.2", "image-size-stream": "^1.1.0", @@ -20506,6 +20507,14 @@ "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==" }, + "node_modules/iink-ts": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/iink-ts/-/iink-ts-1.0.5.tgz", + "integrity": "sha512-LAWWPvgcsLtouI9ExVijZbPGIWMfJtVCcuznbIDyboaPb+cHYsftcuJdjDU7TQwIpGP4hmcjNQA57XPCw4MK2A==", + "dependencies": { + "json-css": "^1.5.6" + } + }, "node_modules/image-data-uri": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/image-data-uri/-/image-data-uri-2.0.1.tgz", @@ -21989,6 +21998,11 @@ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" }, + "node_modules/json-css": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/json-css/-/json-css-1.5.6.tgz", + "integrity": "sha512-B/0T0OxZH9tSb93tXV6VOYtXqrPz/Vgz2QrCT/4NXen8HGElYkYr9V+8IrSVTMj/ftxa8cG1kcu7f3iAMlaFlQ==" + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", diff --git a/src/.DS_Store b/src/.DS_Store index 19eea5fce..b91453587 100644 Binary files a/src/.DS_Store and b/src/.DS_Store differ diff --git a/src/Utils.ts b/src/Utils.ts index 0590c6930..724725c23 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-namespace */ import * as uuid from 'uuid'; export function clamp(n: number, lower: number, upper: number) { @@ -205,7 +204,6 @@ export function intersectRect(r1: { left: number; top: number; width: number; he } export function stringHash(s?: string) { - // eslint-disable-next-line no-bitwise return !s ? undefined : Math.abs(s.split('').reduce((a, b) => (n => n & n)((a << 5) - a + b.charCodeAt(0)), 0)); } @@ -254,6 +252,7 @@ export namespace JSONUtils { try { results = JSON.parse(source); } catch (e) { + console.log('JSONparse error: ', e); results = source; } return results; diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 1cd6de55c..e9e12fc3b 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -1,10 +1,9 @@ import * as fitCurve from 'fit-curve'; -import { Bezier } from 'bezier-js'; import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { returnEmptyFilter, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../ClientUtils'; -import { emptyFunction } from '../../Utils'; +import { emptyFunction, intersectRect } from '../../Utils'; import { Doc, Opt, returnEmptyDoclist } from '../../fields/Doc'; import { InkData, InkField, InkTool } from '../../fields/InkField'; import { NumCast } from '../../fields/Types'; @@ -16,6 +15,7 @@ import { Docs } from '../documents/Documents'; import { InteractionUtils } from '../util/InteractionUtils'; import { ScriptingGlobals } from '../util/ScriptingGlobals'; import { Transform } from '../util/Transform'; +import { undoable } from '../util/UndoManager'; import './GestureOverlay.scss'; import { InkingStroke } from './InkingStroke'; import { ObservableReactComponent } from './ObservableReactComponent'; @@ -37,7 +37,6 @@ import { SetActiveInkColor, SetActiveInkWidth, } from './nodes/DocumentView'; -import { docs } from 'googleapis/build/src/apis/docs'; export enum ToolglassTools { InkToText = 'inktotext', IgnoreGesture = 'ignoregesture', @@ -53,7 +52,9 @@ interface GestureOverlayProps { * drew or perform the gesture's action */ export class GestureOverlay extends ObservableReactComponent> { + // eslint-disable-next-line no-use-before-define static Instance: GestureOverlay; + // eslint-disable-next-line no-use-before-define static Instances: GestureOverlay[] = []; @observable public InkShape: Opt = undefined; @@ -79,10 +80,6 @@ export class GestureOverlay extends ObservableReactComponent(); - private _d1: Doc | undefined; - private _inkToTextDoc: Doc | undefined; - private thumbIdentifier?: number; - private pointerIdentifier?: number; constructor(props: GestureOverlayProps) { super(props); @@ -136,119 +133,67 @@ export class GestureOverlay extends ObservableReactComponent view.ComponentView instanceof CollectionFreeFormView); - console.log(inkData.map(ink => ({ x: ink.X, y: ink.Y }))); - let intersectArray: boolean[] = []; - const cuspArray = this.getCusps(inkData); - console.log(cuspArray.length); - for (let i = 0; i < cuspArray.length; i++) { - intersectArray[i] = false; - } - const docsToDelete: Doc[] = []; - let childDocs = (ffView?.ComponentView as CollectionFreeFormView).childDocs.slice(0, -1); - childDocs.filter(doc => doc.type === 'ink').map(doc => DocumentView.getDocumentView(doc, DocumentView.getDocumentView(doc))); - if ((ffView?.ComponentView as CollectionFreeFormView).childDocs) { - for (const doc of childDocs) { - const otherInk = DocumentView.getDocumentView(doc)?.ComponentView as InkingStroke; - const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] }; - const otherScreenPts = otherInkData.map(point => otherInk.ptToScreen(point)); - if (this.isRectangleOverlap(this.getExtremeCoordinates(otherScreenPts), this.getExtremeCoordinates(inkData))) { - const intersects = this.doInksIntersect(inkData, otherScreenPts); - intersects.forEach(intersect => { - let percentage = ''; - if (intersect.includes('/')) { - const leftOfSlash = intersect.split('/')[0]; - percentage = leftOfSlash; - } else { - percentage = intersect; - } - intersectArray[Math.floor((percentage as unknown as number) * cuspArray.length)] = true; - const docsInBoundingBox = this.docsInBoundingBox(doc, childDocs); - childDocs = childDocs.filter(doc => !docsInBoundingBox.includes(doc)); - docsToDelete.push(...docsInBoundingBox); - docsToDelete.push(doc); + isScribble = (ffView: CollectionFreeFormView, cuspArray: { X: number; Y: number }[], scribbleStroke: { X: number; Y: number }[]) => { + const intersectArray = cuspArray.map(() => false); + const scribbleBounds = InkField.getBounds(scribbleStroke); + const docsToDelete = ffView.childDocs + .map(doc => DocumentView.getDocumentView(doc)) + .filter(dv => dv?.ComponentView instanceof InkingStroke) + .map(dv => dv?.ComponentView as InkingStroke) + .filter(otherInk => { + const otherScreenPts = otherInk.inkScaledData?.().inkData.map(otherInk.ptToScreen); + if (intersectRect(InkField.getBounds(otherScreenPts), scribbleBounds)) { + const intersects = this.findInkIntersections(scribbleStroke, otherScreenPts).map(intersect => { + const percentage = intersect.split('/')[0]; + intersectArray[Math.floor(Number(percentage) * cuspArray.length)] = true; }); + return intersects.length > 0; } - } - console.log(intersectArray); - if (intersectArray.length > 3 && this.determineIfScribble(intersectArray)) { - const uniqueArray = Array.from(new Set(docsToDelete)); - console.log(uniqueArray.length); - console.log('is a scribble'); - docsToDelete.forEach(doc => { - ffView?.ComponentView?.removeDocument?.(doc); - }); - this._points = []; - return true; - } - } - return false; - } - /** - * this will return all the docs overlapping with the maindocs bounding box - * @param mainDoc the bounding box of this doc will be used - * @param childDocs the array of all docs in collection - * @returns - */ - docsInBoundingBox(mainDoc: Doc, childDocs: Doc[]): Doc[] { - return childDocs.filter( - doc => - typeof doc.x === 'number' && - typeof doc.y === 'number' && - typeof doc.width === 'number' && - typeof doc.height === 'number' && - typeof mainDoc.x === 'number' && - typeof mainDoc.y === 'number' && - typeof mainDoc.width === 'number' && - typeof mainDoc.height === 'number' && - doc.x < mainDoc.x + mainDoc.width && - doc.x + doc.width > mainDoc.x && - doc.y < mainDoc.y + mainDoc.height && - doc.y + doc.height > mainDoc.y - ); - } + }); + return !this.determineIfScribble(intersectArray) ? undefined : + [ ...docsToDelete.map(stroke => stroke.Document), + // bcz: NOTE: docsInBoundingBox test should be replaced with a docsInConvexHull test + ...this.docsInBoundingBox({ topLeft : ffView.ScreenToContentsXf().transformPoint(scribbleBounds.left, scribbleBounds.top), + bottomRight: ffView.ScreenToContentsXf().transformPoint(scribbleBounds.right,scribbleBounds.bottom)}, + ffView.childDocs.filter(doc => !docsToDelete.map(s => s.Document).includes(doc)) )]; // prettier-ignore + }; /** - * this method determines if what the user drew is a scribble based on certain criteria. - * @param cuspBooleanArray will take in an array of booleans tht represent what sections(seperated by a cusp) in the scribble - * has an object in it. - * @returns + * Returns all docs in array that overlap bounds. Note that the bounds should be given in screen space coordinates. + * @param boundingBox screen space bounding box + * @param childDocs array of docs to test against bounding box + * @returns list of docs that overlap rect */ - determineIfScribble(cuspBooleanArray: boolean[]) { - if (!cuspBooleanArray) { - return false; - } - const quarterArrayLength = Math.ceil((cuspBooleanArray.length - 2) * 0.3); - let hasObjectInFirstAndLast25 = true; - for (let i = 0; i < quarterArrayLength; i++) { - if (cuspBooleanArray[i] == false || cuspBooleanArray[cuspBooleanArray.length - 1 - i] == false) { - hasObjectInFirstAndLast25 = false; - } - } - const trueCount = cuspBooleanArray.filter(value => value).length; - const percentageTrues = trueCount / cuspBooleanArray.length; - return percentageTrues >= 0.5 || hasObjectInFirstAndLast25; - } + docsInBoundingBox = (boundingBox: { topLeft: number[]; bottomRight: number[] }, childDocs: Doc[]): Doc[] => { + const rect = { left: boundingBox.topLeft[0], top: boundingBox.topLeft[1], width: boundingBox.bottomRight[0] - boundingBox.topLeft[0], height: boundingBox.bottomRight[1] - boundingBox.topLeft[1] }; + return childDocs.filter(doc => intersectRect(rect, { left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) })); + }; /** - * determines if two rectangles are overlapping each other - * @param rect1 the rectangle object has has a minX,maxX,minY, and maxY - * @param rect2 + * Determines if what the array of cusp/intersection data corresponds to a scribble. + * true if there are at least 4 cusps and either: + * 1) the initial and final quarters of the array contain objects + * 2) or half of the cusps contain objects + * @param intersectArray array of booleans coresponding to which scribble sections (regions separated by a cusp) contain Docs * @returns */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - isRectangleOverlap(rect1: any, rect2: any): boolean { - const noOverlap = rect1.maxX < rect2.minX || rect1.minX > rect2.maxX || rect1.maxY < rect2.minY || rect1.minY > rect2.maxY; - - return !noOverlap; - } + determineIfScribble = (intersectArray: boolean[]) => { + const quarterArrayLength = Math.ceil(intersectArray.length / 3.9); // use 3.9 instead of 4 to work better with strokes with only 4 cusps + const { start, end } = intersectArray.reduce((res, val, i) => ({ // test for scribbles at start and end of scribble stroke + start: res.start || (val && i <= quarterArrayLength), + end: res.end || (val && i >= intersectArray.length - quarterArrayLength) + }), { start: false, end: false }); // prettier-ignore + + const percentCuspsWithContent = intersectArray.filter(value => value).length / intersectArray.length; + return intersectArray.length > 3 && (percentCuspsWithContent >= 0.5 || (start && end)); + }; /** * determines if inks intersect * @param line is pointData @@ -260,32 +205,24 @@ export class GestureOverlay extends ObservableReactComponent view.ComponentView instanceof CollectionFreeFormView); - if (ffView && ffView.ComponentView instanceof CollectionFreeFormView) { - for (let i = 0; i < scribble.length - 3; i += 4) { - // iterate over each segment of bezier curve - for (let j = 0; j < inkStroke.length - 3; j += 4) { - const scribbleCurve: Bezier = InkField.Segment(scribble, i); - const strokeCurve: Bezier = InkField.Segment(inkStroke, j); - const points = strokeCurve.points.map(point => ({ - X: point.x, - Y: point.y, - })); - if (ffView && ffView.ComponentView instanceof CollectionFreeFormView && this.isRectangleOverlap(extremeScribble, this.getExtremeCoordinates(points))) { - const result = (ffView.ComponentView as CollectionFreeFormView).bintersects(scribbleCurve, strokeCurve)[0]; - if (result !== undefined) { - intersectArray.push(result.toString()); - } + findInkIntersections = (scribble: InkData, inkStroke: InkData): string[] => { + const intersectArray: string[] = []; + const scribbleBounds = InkField.getBounds(scribble); + for (let i = 0; i < scribble.length - 3; i += 4) { // for each segment of scribble + for (let j = 0; j < inkStroke.length - 3; j += 4) { // for each segment of ink stroke + const scribbleSeg = InkField.Segment(scribble, i); + const strokeSeg = InkField.Segment(inkStroke, j); + const strokeBounds = InkField.getBounds(strokeSeg.points.map(pt => ({ X: pt.x, Y: pt.y }))); + if (intersectRect(scribbleBounds, strokeBounds)) { + const result = InkField.bintersects(scribbleSeg, strokeSeg)[0]; + if (result !== undefined) { + intersectArray.push(result.toString()); } } - } - } + } // prettier-ignore + } // prettier-ignore return intersectArray; - } + }; dryInk = () => { const newPoints = this._points.reduce((p, pts) => { p.push([pts.X, pts.Y]); @@ -309,6 +246,7 @@ export class GestureOverlay extends ObservableReactComponent { + const ffView = DocumentView.DownDocView?.ComponentView instanceof CollectionFreeFormView && DocumentView.DownDocView.ComponentView; DocumentView.DownDocView = undefined; if (this._points.length > 1) { const B = this.svgBounds; @@ -320,31 +258,34 @@ export class GestureOverlay extends ObservableReactComponent { switch (name) { case Gestures.Line: + if (cuspArray.length > 2) return undefined; + // eslint-disable-next-line no-fallthrough case Gestures.Triangle: case Gestures.Rectangle: case Gestures.Circle: this.makeBezierPolygon(Name, true); return this.dispatchGesture(name); case Gestures.RightAngle: - return this.convertToText().length > 0; + return ffView && this.convertToText(ffView).length > 0; default: } })(Score < 0.7 ? Gestures.Stroke : (Name as Gestures)); // if no gesture (or if the gesture was unsuccessful), "dry" the stroke into an ink document + if (!actionPerformed) { - this.dryInk(); - setTimeout(() => { - const ffView = DocumentView.allViews().find(view => view.ComponentView instanceof CollectionFreeFormView); - const scribbleInk = (ffView?.ComponentView as CollectionFreeFormView).childDocs[(ffView?.ComponentView as CollectionFreeFormView).childDocs.length - 1]; - if (this.isScribble((DocumentView.getDocumentView(scribbleInk)?.ComponentView as InkingStroke).inkScaledData().inkData)) { - ffView?.ComponentView?.removeDocument?.(scribbleInk); - } - }, 1); + const scribbledOver = ffView && this.isScribble(ffView, cuspArray, this._points); + if (scribbledOver) { + undoable(() => ffView.removeDocument(scribbledOver), 'scribble erase')(); + } else { + this.dryInk(); + } } } this._points.length = 0; @@ -353,21 +294,19 @@ export class GestureOverlay extends ObservableReactComponent view.ComponentView instanceof CollectionFreeFormView); + convertToText = (ffView: CollectionFreeFormView) => { let minX = 999999999; let maxX = -999999999; let minY = 999999999; let maxY = -999999999; const textDocs: Doc[] = []; - (ffView?.ComponentView as CollectionFreeFormView).childDocs + ffView.childDocs .filter(doc => doc.type === DocumentType.COL) .forEach(doc => { if (typeof doc.width === 'number' && typeof doc.height === 'number' && typeof doc.x === 'number' && typeof doc.y === 'number') { const bounds = DocumentView.getDocumentView(doc)?.getBounds; if (bounds) { - const rect1 = { minX: bounds.left, maxX: bounds.right, minY: bounds.top, maxY: bounds.bottom }; - if (this.isRectangleOverlap(rect1, this.getExtremeCoordinates(this._points))) { + if (intersectRect({ ...bounds, width: bounds.right - bounds.left, height: bounds.bottom - bounds.top }, InkField.getBounds(this._points))) { if (doc.x < minX) { minX = doc.x; } @@ -383,9 +322,9 @@ export class GestureOverlay extends ObservableReactComponent { - if (coord.X < minX) minX = coord.X; - if (coord.X > maxX) maxX = coord.X; - if (coord.Y < minY) minY = coord.Y; - if (coord.Y > maxY) maxY = coord.Y; - }); - return { - minX, - maxX, - minY, - maxY, - }; - } /** * takes in three points and then determines the angle of the points. used to determine if the cusp * is sharp enoug @@ -770,6 +683,7 @@ export class GestureOverlay extends ObservableReactComponent { GestureOverlay.Instance.SavedColor = ActiveInkColor(); @@ -782,6 +696,7 @@ ScriptingGlobals.add(function setPen(width: string, color: string, fill: string, SetActiveDash(dash); }); }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function resetPen() { runInAction(() => { SetActiveInkColor(GestureOverlay.Instance.SavedColor ?? 'rgb(0, 0, 0)'); @@ -789,6 +704,7 @@ ScriptingGlobals.add(function resetPen() { }); }, 'resets the pen tool'); ScriptingGlobals.add( + // eslint-disable-next-line prefer-arrow-callback function createText(text: string, X: number, Y: number) { GestureOverlay.Instance.dispatchGesture(Gestures.Text, [{ X, Y }], text); }, diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index f04e3eece..177400882 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -49,7 +49,7 @@ import { PinDocView, PinProps } from './PinFuncs'; import { StyleProp } from './StyleProp'; import { ViewBoxInterface } from './ViewBoxInterface'; -// eslint-disable-next-line @typescript-eslint/no-var-requires +// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports const { INK_MASK_SIZE } = require('./global/globalCssVariables.module.scss'); // prettier-ignore @observer @@ -107,10 +107,10 @@ export class InkingStroke extends ViewBoxAnnotatableComponent() * analyzes the ink stroke and saves the analysis of the stroke to the 'inkAnalysis' field, * and the recognized words to the 'handwriting' */ - analyzeStrokes=()=> { + analyzeStrokes = () => { const data: InkData = this.inkScaledData().inkData ?? []; CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ['inkAnalysis', 'handwriting'], [data]); - } + }; /** * Toggles whether the ink stroke is displayed as an overlay mask or as a regular stroke. diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 3e2c55114..36772e8ec 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -894,7 +894,7 @@ export class CollectionFreeFormView extends CollectionSubView { + InkField.bintersects(intersectCurve, eraserCurve).forEach((val: string | number, k: number) => { // Converting the Bezier.js Split type to a t-value number. const t = +val.toString().split('/')[0]; if (k % 2 === 0) { @@ -1168,26 +1168,6 @@ export class CollectionFreeFormView extends CollectionSubView { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if ((curve as any)._linear) { - // bezier.js doesn't intersect properly if the curve is actually a line -- so get intersect other curve against this line, then figure out the t coordinates of the intersection on this line - const intersections = otherCurve.lineIntersects({ p1: curve.points[0], p2: curve.points[3] }); - if (intersections.length) { - const intPt = otherCurve.get(intersections[0]); - const intT = curve.project(intPt).t; - return intT ? [intT] : []; - } - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if ((otherCurve as any)._linear) { - return curve.lineIntersects({ p1: otherCurve.points[0], p2: otherCurve.points[3] }); - } - return curve.intersects(otherCurve); - }; - /** * Determines all possible intersections of the current curve of the intersected ink stroke with all other curves of all * ink strokes in the current collection. @@ -1219,7 +1199,7 @@ export class CollectionFreeFormView extends CollectionSubView { + InkField.bintersects(curve, otherCurve).forEach((val: string | number, ival: number) => { // Converting the Bezier.js Split type to a t-value number. const t = +val.toString().split('/')[0]; if (ival % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical). diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts index 32abf0076..ad524f73f 100644 --- a/src/fields/InkField.ts +++ b/src/fields/InkField.ts @@ -102,6 +102,26 @@ export class InkField extends ObjectField { const top = Math.min(...ys); return { right, left, bottom, top, width: right - left, height: bottom - top }; } + + // for some reason bezier.js doesn't handle the case of intersecting a linear curve, so we wrap the intersection + // call in a test for linearity + public static bintersects(curve: Bezier, otherCurve: Bezier) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if ((curve as any)._linear) { + // bezier.js doesn't intersect properly if the curve is actually a line -- so get intersect other curve against this line, then figure out the t coordinates of the intersection on this line + const intersections = otherCurve.lineIntersects({ p1: curve.points[0], p2: curve.points[3] }); + if (intersections.length) { + const intPt = otherCurve.get(intersections[0]); + const intT = curve.project(intPt).t; + return intT ? [intT] : []; + } + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if ((otherCurve as any)._linear) { + return curve.lineIntersects({ p1: otherCurve.points[0], p2: otherCurve.points[3] }); + } + return curve.intersects(otherCurve); + } } ScriptingGlobals.add('InkField', InkField); -- cgit v1.2.3-70-g09d2 From b4d9626be5dd3fc158fd05589b5deca59a08dd4f Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 1 Oct 2024 14:39:42 -0400 Subject: cleaning up smart drawing menu options. --- src/client/views/InkTranscription.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 4 +- .../collections/collectionFreeForm/MarqueeView.tsx | 17 +++--- src/client/views/global/globalScripts.ts | 69 +--------------------- src/client/views/smartdraw/SmartDrawHandler.tsx | 5 +- 5 files changed, 14 insertions(+), 85 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx index c6b55d55a..b4f4ebf63 100644 --- a/src/client/views/InkTranscription.tsx +++ b/src/client/views/InkTranscription.tsx @@ -344,7 +344,7 @@ export class InkTranscription extends React.Component { const bounds: { x: number; y: number; width?: number; height?: number }[] = []; // calculate the necessary bounds from the selected ink docs - selected.map( + selected.forEach( action(d => { const x = NumCast(d.x); const y = NumCast(d.y); @@ -367,7 +367,7 @@ export class InkTranscription extends React.Component { } // map through all the selected ink strokes and create the groupings - selected.map( + selected.forEach( action(d => { const dx = NumCast(d.x); const dy = NumCast(d.y); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index fe5ced29e..cdcc2328f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1246,9 +1246,7 @@ export class CollectionFreeFormView extends CollectionSubView { - // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those - const selected = ffView.unprocessedDocs; - // loop through selected an get the bound - const bounds: { x: number; y: number; width?: number; height?: number }[] = []; - - selected.map( - action(d => { - const x = NumCast(d.x); - const y = NumCast(d.y); - const width = NumCast(d._width); - const height = NumCast(d._height); - bounds.push({ x, y, width, height }); - }) - ); - - const aggregBounds = aggregateBounds(bounds, 0, 0); - const marqViewRef = ffView._marqueeViewRef.current; - - // set the vals for bounds in marqueeView - if (marqViewRef) { - marqViewRef._downX = aggregBounds.x; - marqViewRef._downY = aggregBounds.y; - marqViewRef._lastX = aggregBounds.r; - marqViewRef._lastY = aggregBounds.b; - } - - selected.map( - action(d => { - const dx = NumCast(d.x); - const dy = NumCast(d.y); - delete d.x; - delete d.y; - delete d.activeFrame; - delete d._timecodeToShow; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection - delete d._timecodeToHide; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection - // calculate pos based on bounds - if (marqViewRef?.Bounds) { - d.x = dx - marqViewRef.Bounds.left - marqViewRef.Bounds.width / 2; - d.y = dy - marqViewRef.Bounds.top - marqViewRef.Bounds.height / 2; - } - return d; - }) - ); - ffView._props.removeDocument?.(selected); - // TODO: nda - this is the code to actually get a new grouped collection - const newCollection = MarqueeView.getCollection(selected, undefined, true, { top: 1, left: 1, width: 1, height: 1 }); - if (newCollection) { - newCollection.height = NumCast(newCollection._height); - newCollection.width = NumCast(newCollection._width); - } - - // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs - newCollection && ffView._props.addDocument?.(newCollection); - // TODO: nda - will probably need to go through and only remove the unprocessed selected docs - ffView.unprocessedDocs = []; - - // InkTranscription.Instance.transcribeInk(newCollection, selected, false); - }); - } - CollectionFreeFormView.collectionsWithUnprocessedInk.clear(); -} - function setActiveTool(toolIn: InkTool | Gestures, keepPrim: boolean, checkResult?: boolean) { InkTranscription.Instance?.createInkGroup(); const tool = toolIn === InkTool.Eraser ? Doc.UserDoc().activeEraserTool : toolIn; diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx index a77092a1a..6d1277a08 100644 --- a/src/client/views/smartdraw/SmartDrawHandler.tsx +++ b/src/client/views/smartdraw/SmartDrawHandler.tsx @@ -228,9 +228,8 @@ export class SmartDrawHandler extends ObservableReactComponent { console.error('GPT call failed'); return; } - console.log(res); const strokeData = await this.parseSvg(res, startPt, false, autoColor); - const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData?.data, strokeData?.lastInput, strokeData?.lastRes); + const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData.data, strokeData.lastInput, strokeData.lastRes); drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res); this._errorOccurredOnce = false; @@ -261,7 +260,7 @@ export class SmartDrawHandler extends ObservableReactComponent { } const strokeData = await this.parseSvg(res, { X: this._lastInput.x, Y: this._lastInput.y }, true, lastInput?.autoColor || this._autoColor); this.RemoveDrawing !== unimplementedFunction && this.RemoveDrawing(true, this._selectedDoc); - const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData?.data, strokeData?.lastInput, strokeData?.lastRes); + const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData.data, strokeData.lastInput, strokeData.lastRes); drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res); return strokeData; } catch (err) { -- cgit v1.2.3-70-g09d2 From b56d2c2ae115e81fbf7aeaae0d2fed9ba073f11d Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 1 Oct 2024 15:19:52 -0400 Subject: more annopalette related cleanup --- src/client/util/DropConverter.ts | 39 ++++------------------ src/client/views/DocumentDecorations.tsx | 14 ++++---- src/client/views/InkingStroke.tsx | 3 -- .../views/collections/CollectionCarouselView.tsx | 3 +- src/client/views/nodes/DocumentView.tsx | 4 +-- .../views/nodes/formattedText/FormattedTextBox.tsx | 2 +- src/client/views/smartdraw/AnnotationPalette.tsx | 4 +-- src/client/views/smartdraw/SmartDrawHandler.tsx | 23 +++++++++---- 8 files changed, 37 insertions(+), 55 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index 0ede44298..71cdaa58b 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -5,7 +5,7 @@ import { RichTextField } from '../../fields/RichTextField'; import { ComputedField, ScriptField } from '../../fields/ScriptField'; import { StrCast } from '../../fields/Types'; import { ImageField } from '../../fields/URLField'; -import { Docs } from '../documents/Documents'; +import { Docs, DocumentOptions } from '../documents/Documents'; import { DocumentType } from '../documents/DocumentTypes'; import { ButtonType, FontIconBox } from '../views/nodes/FontIconBox/FontIconBox'; import { DragManager } from './DragManager'; @@ -64,49 +64,24 @@ export function MakeTemplate(doc: Doc) { return doc; } -export function makeUserTemplateButton(doc: Doc) { - const layoutDoc = doc; // doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc; - if (layoutDoc.type !== DocumentType.FONTICON) { - !layoutDoc.isTemplateDoc && makeTemplate(layoutDoc); - } - layoutDoc.isTemplateDoc = true; - const dbox = Docs.Create.FontIconDocument({ - _nativeWidth: 100, - _nativeHeight: 100, - _width: 100, - _height: 100, - backgroundColor: StrCast(doc.backgroundColor), - title: StrCast(layoutDoc.title), - btnType: ButtonType.ClickButton, - icon: 'bolt', - isSystem: false, - }); - dbox.title = ComputedField.MakeFunction('this.dragFactory.title'); - dbox.dragFactory = layoutDoc; - dbox.dropPropertiesToRemove = doc.dropPropertiesToRemove instanceof ObjectField ? ObjectField.MakeCopy(doc.dropPropertiesToRemove) : undefined; - dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory)'); - return dbox; -} - /** - * Similar to makeUserTemplateButton, but rather than creating a draggable button for the template, it takes in - * an ImageField that will display. + * Makes a draggable button or image that will create a template doc Instance */ -export function makeUserTemplateImage(doc: Doc, imageHref: string | undefined) { - const image = imageHref ?? 'http://www.cs.brown.edu/~bcz/noImage.png'; +export function makeUserTemplateButtonOrImage(doc: Doc, image: string | undefined) { const layoutDoc = doc; // doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc; if (layoutDoc.type !== DocumentType.FONTICON) { !layoutDoc.isTemplateDoc && makeTemplate(layoutDoc); } layoutDoc.isTemplateDoc = true; - const dbox = Docs.Create.ImageDocument(image, { + const docOptions: DocumentOptions = { _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, title: StrCast(layoutDoc.title), isSystem: false, - }); + }; + const dbox = image ? Docs.Create.ImageDocument(image, docOptions) : Docs.Create.FontIconDocument({ ...docOptions, backgroundColor: StrCast(doc.backgroundColor), btnType: ButtonType.ClickButton, icon: 'bolt' }); dbox.title = ComputedField.MakeFunction('this.dragFactory.title'); dbox.dragFactory = layoutDoc; dbox.dropPropertiesToRemove = doc.dropPropertiesToRemove instanceof ObjectField ? ObjectField.MakeCopy(doc.dropPropertiesToRemove) : undefined; @@ -129,7 +104,7 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) { }); } } else if (!doc.onDragStart && !doc.isButtonBar) { - dbox = makeUserTemplateButton(doc); + dbox = makeUserTemplateButtonOrImage(doc); } else if (doc.isButtonBar) { dbox.ignoreClick = true; } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index df9ec1bbf..e569c7dc8 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -231,15 +231,17 @@ export class DocumentDecorations extends ObservableReactComponent { const iconViewDoc = iconView.Document; Doc.setNativeView(iconViewDoc); + // bcz: hacky ... when closing a Doc do different things depending on the contet ... if (iconViewDoc.activeFrame) { - iconViewDoc.opacity = 0; // bcz: hacky ... allows inkMasks and other documents to be "turned off" without removing them from the animated collection which allows them to function properly in a presenation. + iconViewDoc.opacity = 0; // if in an animation collection, set opacity to 0 to allow inkMasks and other documents to remain in the collection and to smoothly animate when they are activated in a different animation frame } else { - // to mark annotations as no longer saved if they're deleted from the palette - const dragFactory: Doc = DocCast(iconView.Document.dragFactory); - if (dragFactory && DocCast(dragFactory.cloneOf).savedAsAnno) { - DocCast(dragFactory.cloneOf).savedAsAnno = undefined; - } + // if Doc is in the annotation palette, remove the flag indicating that it's saved + const dragFactory = DocCast(iconView.Document.dragFactory); + if (dragFactory && DocCast(dragFactory.cloneOf).savedAsAnno) DocCast(dragFactory.cloneOf).savedAsAnno = undefined; + + // if this is a face Annotation doc, then just hide it. if (iconView.Document.annotationOn && iconView.Document.face) iconView.Document.hidden = true; + // otherwise actually remove the Doc from its parent collection else iconView._props.removeDocument?.(iconView.Document); } }); diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index bc2b4badb..f44c1720d 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -30,7 +30,6 @@ import { InkData, InkField } from '../../fields/InkField'; import { BoolCast, Cast, NumCast, RTFCast, StrCast } from '../../fields/Types'; import { TraceMobx } from '../../fields/util'; import { Gestures } from '../../pen-gestures/GestureTypes'; -import { CognitiveServices } from '../cognitive_services/CognitiveServices'; import { Docs } from '../documents/Documents'; import { DocumentType } from '../documents/DocumentTypes'; import { InteractionUtils } from '../util/InteractionUtils'; @@ -122,8 +121,6 @@ export class InkingStroke extends ViewBoxAnnotatableComponent() InkTranscription.Instance.transcribeInk(newCollection, selected, false); } - // const data: InkData = this.inkScaledData().inkData ?? []; - // CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ['inkAnalysis', 'handwriting'], [data]); }; /** diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 143cfe0e8..db75c9e8b 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -144,7 +144,6 @@ export class CollectionCarouselView extends CollectionSubView() { revealItems.push({description: 'Quiz Cards', event: () => {this.layoutDoc.filterOp = cardMode.QUIZ;}, icon: 'pencil',}); // prettier-ignore !revealOptions && cm.addItem({ description: 'Filter Flashcards', addDivider: false, noexpand: true, subitems: revealItems, icon: 'layer-group' }); }; - childFitWidth = (doc: Doc) => Cast(this.Document.childLayoutFitWidth, 'boolean', this._props.childLayoutFitWidth?.(doc) ?? Cast(doc.layout_fitWidth, 'boolean', null)); isChildContentActive = () => this._props.isContentActive?.() === false @@ -165,7 +164,7 @@ export class CollectionCarouselView extends CollectionSubView() { Document={doc} NativeWidth={returnZero} NativeHeight={returnZero} - fitWidth={undefined} + fitWidth={this._props.childLayoutFitWidth} showTags={true} containerViewPath={this.childContainerViewPath} setContentViewBox={undefined} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 85fd42ddf..80b61b6a9 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -27,7 +27,7 @@ import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes' import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { dropActionType } from '../../util/DropActionTypes'; -import { MakeTemplate, makeUserTemplateButton } from '../../util/DropConverter'; +import { MakeTemplate, makeUserTemplateButtonOrImage } from '../../util/DropConverter'; import { UPDATE_SERVER_CACHE } from '../../util/LinkManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SearchUtil } from '../../util/SearchUtil'; @@ -1348,7 +1348,7 @@ export class DocumentView extends DocComponent() { tempDoc = view.Document; MakeTemplate(tempDoc); Doc.AddDocToList(Doc.UserDoc(), 'template_user', tempDoc); - Doc.AddDocToList(DocListCast(Doc.MyTools.data)[1], 'data', makeUserTemplateButton(tempDoc)); + Doc.AddDocToList(DocListCast(Doc.MyTools.data)[1], 'data', makeUserTemplateButtonOrImage(tempDoc)); tempDoc && Doc.AddDocToList(Cast(Doc.UserDoc().template_user, Doc, null), 'data', tempDoc); } else { tempDoc = DocCast(view.Document[StrCast(view.Document.layout_fieldKey)]); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 84d3fc748..d1b0dc7bf 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -268,7 +268,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { this._canInteract = false; if (this.ShowRegenerate) { await this.regenerate(); - this._regenInput = ''; - this._showEditBox = false; + runInAction(() => { + this._regenInput = ''; + this._showEditBox = false; + }); } else { - this._showOptions = false; + runInAction(() => { + this._showOptions = false; + }); try { await this.drawWithGPT({ X: this._pageX, Y: this._pageY }, this._userInput, this._complexity, this._size, this._autoColor); this.hideSmartDrawHandler(); - this.ShowRegenerate = true; + + runInAction(() => { + this.ShowRegenerate = true; + }); } catch (err) { if (this._errorOccurredOnce) { console.error('GPT call failed', err); @@ -212,8 +219,10 @@ export class SmartDrawHandler extends ObservableReactComponent { } } } - this._isLoading = false; - this._canInteract = true; + runInAction(() => { + this._isLoading = false; + this._canInteract = true; + }); }; /** -- cgit v1.2.3-70-g09d2 From 8d7bf0588ca63c2d505a494865dcf9921eb1383d Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 1 Oct 2024 15:57:57 -0400 Subject: more lint fixes --- src/client/util/CurrentUserUtils.ts | 2 +- src/client/util/DropConverter.ts | 2 +- src/client/util/Scripting.ts | 5 ----- src/client/views/DocumentDecorations.tsx | 2 +- src/client/views/InkTranscription.tsx | 1 + src/client/views/InkingStroke.tsx | 4 +--- src/client/views/LightboxView.tsx | 2 -- src/client/views/Main.tsx | 1 - src/client/views/MainView.tsx | 19 +++++++------------ .../views/collections/CollectionCarousel3DView.tsx | 2 +- .../views/collections/CollectionCarouselView.tsx | 4 ++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 11 ++++++----- .../collections/collectionFreeForm/MarqueeView.tsx | 3 +-- src/client/views/nodes/FontIconBox/FontIconBox.tsx | 1 - src/client/views/nodes/ImageBox.tsx | 1 - src/client/views/nodes/PDFBox.tsx | 5 +---- .../views/nodes/formattedText/FormattedTextBox.tsx | 8 -------- src/client/views/nodes/formattedText/RichTextMenu.tsx | 1 - src/client/views/pdf/PDFViewer.tsx | 2 -- src/client/views/smartdraw/AnnotationPalette.tsx | 4 +--- src/client/views/smartdraw/SmartDrawHandler.tsx | 3 ++- src/fields/SchemaHeaderField.ts | 1 - 22 files changed, 26 insertions(+), 58 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 59760ba32..96d69e7a1 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, StrListCast } from "../../fields/Doc"; +import { Doc, DocListCast, DocListCastAsync, FieldType, Opt } from "../../fields/Doc"; import { DocData } from "../../fields/DocSymbols"; import { InkTool } from "../../fields/InkField"; import { List } from "../../fields/List"; diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index 71cdaa58b..b5d29be4c 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -67,7 +67,7 @@ export function MakeTemplate(doc: Doc) { /** * Makes a draggable button or image that will create a template doc Instance */ -export function makeUserTemplateButtonOrImage(doc: Doc, image: string | undefined) { +export function makeUserTemplateButtonOrImage(doc: Doc, image?: string) { const layoutDoc = doc; // doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc; if (layoutDoc.type !== DocumentType.FONTICON) { !layoutDoc.isTemplateDoc && makeTemplate(layoutDoc); diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index c63d3d7cb..c7b86815a 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -60,7 +60,6 @@ function Run(script: string | undefined, customParams: string[], diagnostics: ts // let params: any[] = [Docs, ...fieldTypes]; const compiledFunction = (() => { try { - // eslint-disable-next-line no-new-func return new Function(...paramNames, `return ${script}`); } catch (e) { console.log(e); @@ -69,10 +68,8 @@ function Run(script: string | undefined, customParams: string[], diagnostics: ts })(); if (!compiledFunction) return { compiled: false, errors }; const { capturedVariables = {} } = options; - // eslint-disable-next-line default-param-last const run = (args: { [name: string]: unknown } = {}, onError?: (e: string) => void, errorVal?: ts.Diagnostic): ScriptResult => { const argsArray: unknown[] = []; - // eslint-disable-next-line no-restricted-syntax for (const name of customParams) { if (name !== 'this') { argsArray.push(name in args ? args[name] : capturedVariables[name]); @@ -224,7 +221,6 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp if ('this' in params || 'this' in capturedVariables) { paramNames.push('this'); } - // eslint-disable-next-line no-restricted-syntax for (const key in params) { if (key !== 'this') { paramNames.push(key); @@ -234,7 +230,6 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp const val = params[key]; return `${key}: ${val}`; }); - // eslint-disable-next-line no-restricted-syntax for (const key in capturedVariables) { if (key !== 'this') { const val = capturedVariables[key]; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index e569c7dc8..1c0d51e17 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -2,7 +2,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; import { IconButton } from 'browndash-components'; -import { action, computed, makeObservable, observable, runInAction, trace } from 'mobx'; +import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { FaUndo } from 'react-icons/fa'; diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx index b4f4ebf63..24d53a8c8 100644 --- a/src/client/views/InkTranscription.tsx +++ b/src/client/views/InkTranscription.tsx @@ -18,6 +18,7 @@ import { URLField } from '../../fields/URLField'; * Class component that handles inking in writing mode */ export class InkTranscription extends React.Component { + // eslint-disable-next-line no-use-before-define static Instance: InkTranscription; // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index f44c1720d..270266a94 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -51,7 +51,7 @@ import { InkTranscription } from './InkTranscription'; import { CollectionFreeFormView } from './collections/collectionFreeForm'; import { DocumentView } from './nodes/DocumentView'; -// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports +// eslint-disable-next-line @typescript-eslint/no-require-imports const { INK_MASK_SIZE } = require('./global/globalCssVariables.module.scss'); // prettier-ignore @observer @@ -473,7 +473,6 @@ export class InkingStroke extends ViewBoxAnnotatableComponent() // mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset', cursor: this._props.isSelected() ? 'default' : undefined, }} - // eslint-disable-next-line react/jsx-props-no-spreading {...interactions}> {clickableLine(this.onPointerDown, isInkMask)} {isInkMask ? null : inkLine} @@ -490,7 +489,6 @@ export class InkingStroke extends ViewBoxAnnotatableComponent() // top: (this._props.PanelHeight() - (lineHeightGuess * fsize + 20) * (this._props.NativeDimScaling?.() || 1)) / 2, }}> { ); } // prettier-ignore public static LightboxDoc = () => LightboxView.Instance?._doc; - // eslint-disable-next-line no-use-before-define static Instance: LightboxView; private _path: { doc: Opt; // @@ -341,7 +340,6 @@ export class LightboxView extends ObservableReactComponent { } interface LightboxTourBtnProps { navBtn: (left: Opt, bottom: Opt, top: number, icon: IconProp, display: boolean, click: () => void, color?: string) => JSX.Element; - // eslint-disable-next-line react/no-unused-prop-types future: () => Opt; stepInto: () => void; lightboxDoc: () => Opt; diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 94294e97a..73d2872d1 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -1,4 +1,3 @@ -/* eslint-disable no-new */ // if ((module as any).hot) { // (module as any).hot.accept(); // } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 8e921ca5e..fa2e94d12 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -3,7 +3,7 @@ import { faBuffer, faHireAHelper } from '@fortawesome/free-brands-svg-icons'; import * as far from '@fortawesome/free-regular-svg-icons'; import * as fa from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, configure, makeObservable, observable, reaction, runInAction, trace } from 'mobx'; +import { action, computed, configure, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import ResizeObserver from 'resize-observer-polyfill'; @@ -77,7 +77,7 @@ import { TopBar } from './topbar/TopBar'; import { SmartDrawHandler } from './smartdraw/SmartDrawHandler'; import { InkTranscription } from './InkTranscription'; -// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports +// eslint-disable-next-line @typescript-eslint/no-require-imports const { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } = require('./global/globalCssVariables.module.scss'); // prettier-ignore @observer @@ -977,13 +977,11 @@ export class MainView extends ObservableReactComponent {
{[ - ...SnappingManager.HorizSnapLines.map((l, i) => ( - // eslint-disable-next-line react/no-array-index-key - + ...SnappingManager.HorizSnapLines.map(l => ( + )), - ...SnappingManager.VertSnapLines.map((l, i) => ( - // eslint-disable-next-line react/no-array-index-key - + ...SnappingManager.VertSnapLines.map(l => ( + )), ]} @@ -1071,10 +1069,7 @@ export class MainView extends ObservableReactComponent { docView={DocButtonState.Instance.LinkEditorDocView} /> ) : null} - {LinkInfo.Instance?.LinkInfo ? ( - // eslint-disable-next-line react/jsx-props-no-spreading - - ) : null} + {LinkInfo.Instance?.LinkInfo ? : null} {((page: string) => { // prettier-ignore switch (page) { diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index cf86a0a4e..c5da8e037 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -16,6 +16,7 @@ import './CollectionCarousel3DView.scss'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; import { Transform } from '../../util/Transform'; +// eslint-disable-next-line @typescript-eslint/no-require-imports const { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } = require('../global/globalCssVariables.module.scss'); @observer @@ -88,7 +89,6 @@ export class CollectionCarousel3DView extends CollectionSubView() { const currentIndex = NumCast(this.layoutDoc._carousel_index); const displayDoc = (childPair: { layout: Doc; data: Doc }, dxf: () => Transform) => ( this.showSmartDraw(e.pageX, e.pageY), hit !== -1); + setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, () => this.showSmartDraw(e.pageX, e.pageY), hit !== -1); e.stopPropagation(); + break; case InkTool.None: if (!(this._props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) { - const hit = this._clusters.handlePointerDown(this.screenToFreeformContentsXf.transformPoint(e.clientX, e.clientY)); - setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, hit !== -1, false); + const ahit = this._clusters.handlePointerDown(this.screenToFreeformContentsXf.transformPoint(e.clientX, e.clientY)); + setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, ahit !== -1, false); } break; default: @@ -655,13 +656,13 @@ export class CollectionFreeFormView extends CollectionSubView { + onEraserClick = (e: PointerEvent) => { e.preventDefault(); e.stopImmediatePropagation(); this.erase(e, [0, 0]); }; - forceStrokeGesture = (e: PointerEvent, gesture: Gestures, points: InkData, text?: any) => { + forceStrokeGesture = (e: PointerEvent, gesture: Gestures, points: InkData, text?: string) => { this.onGesture(e, new GestureUtils.GestureEvent(gesture, points, InkField.getBounds(points), text)); }; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 7614b5337..b1f6815b3 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -57,6 +57,7 @@ export class MarqueeView extends ObservableReactComponent tl[0] && truePoint[0] < r1.left && truePoint[1] > r1.top && truePoint[1] < r1.top + r1.height); diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index b7e1350ca..feaf84b7b 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/jsx-props-no-spreading */ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Button, ColorPicker, Dropdown, DropdownType, IconButton, IListItemProps, MultiToggle, NumberDropdown, NumberDropdownType, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components'; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index aa4376bb2..ec5e062c8 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -493,7 +493,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { }}> () { return ComponentTag === CollectionStackingView ? ( () { ) : (
setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this._props.select(false), true)}> () { top: 0, }}> -1) { const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + blockOffset + foundAt + 1), pm.state.doc.resolve(ep.from + index + blockOffset + foundAt + find.length + 1)); ret.push(sel); @@ -714,7 +713,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-hr-' + (hr - i), { opacity: ((10 - i - 1) / 10).toString() })); } - // eslint-disable-next-line operator-assignment this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css changes happen outside of react/mobx. so we need to set a flag that will notify anyone interested in layout changes triggered by css changes (eg., CollectionLinkView) }; @@ -1131,7 +1129,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - // eslint-disable-next-line no-use-before-define const examinedNode = findAnchorNode(node, editor); if (examinedNode?.node && (examinedNode.node.textContent || examinedNode.node.type === this._editorView?.state.schema.nodes.dashDoc || examinedNode.node.type === this._editorView?.state.schema.nodes.audiotag)) { nodes.push(examinedNode.node); @@ -1285,7 +1282,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { this.prepareForTyping(); if (FormattedTextBox._globalHighlights.has('Bold Text')) { - // eslint-disable-next-line operator-assignment this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css change happens outside of mobx/react, so this will notify anyone interested in the layout that it has changed } if (RichTextMenu.Instance?.view === this._editorView && !selected) { @@ -1711,7 +1707,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent kids?.reduce((p, child) => p + toHgt(child), margins) ?? 0; const toNum = (val: string) => Number(val.replace('px', '')); const toHgt = (node: Element): number => { @@ -1880,7 +1874,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => DocumentView.SelectView(this.DocumentView?.(), false), true)}> { - // eslint-disable-next-line react/no-unused-class-component-methods update(view: EditorView & { TextView?: FormattedTextBox }, lastState: EditorState | undefined) { RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps, view.TextView?.layoutDoc); } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 01242ba48..7a86ee802 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -504,7 +504,6 @@ export class PDFViewer extends ObservableReactComponent { return (
{inlineAnnos.map(anno => ( - // eslint-disable-next-line react/jsx-props-no-spreading ))}
@@ -537,7 +536,6 @@ export class PDFViewer extends ObservableReactComponent { pointerEvents: Doc.ActiveTool !== InkTool.None ? 'all' : undefined, }}> { + // eslint-disable-next-line no-use-before-define static Instance: SmartDrawHandler; private _lastInput: DrawingOptions = { text: '', complexity: 5, size: 350, autoColor: true, x: 0, y: 0 }; @@ -395,7 +396,7 @@ export class SmartDrawHandler extends ObservableReactComponent { defaultChecked={true} value={this._autoColor} size="small" - onChange={action(e => this._canInteract && (this._autoColor = !this._autoColor))} + onChange={action(() => this._canInteract && (this._autoColor = !this._autoColor))} />
diff --git a/src/fields/SchemaHeaderField.ts b/src/fields/SchemaHeaderField.ts index 0a8dd1d9e..5f4d59cf9 100644 --- a/src/fields/SchemaHeaderField.ts +++ b/src/fields/SchemaHeaderField.ts @@ -79,7 +79,6 @@ export class SchemaHeaderField extends ObjectField { @serializable(primitive()) desc: boolean | undefined; // boolean determines sort order, undefined when no sort - // eslint-disable-next-line default-param-last constructor(heading: string = '', color: string = RandomPastel(), type?: ColumnType, width?: number, desc?: boolean, collapsed?: boolean) { super(); -- cgit v1.2.3-70-g09d2