diff options
author | eleanor-park <eleanor_park@brown.edu> | 2024-10-01 18:45:03 -0400 |
---|---|---|
committer | eleanor-park <eleanor_park@brown.edu> | 2024-10-01 18:45:03 -0400 |
commit | 5d859cab5fa714860723fa252498c407d5909cdc (patch) | |
tree | a8366cbcacdbb18fb24204e8b89187db49a26785 | |
parent | 06ab521c759e44a26be58fdf7ffc8d790e551236 (diff) | |
parent | 39dc004c0f8e4bcc21ca0c2ecb0b665037f3d1ad (diff) |
Merge branch 'eleanor-gptdraw' of https://github.com/brown-dash/Dash-Web into eleanor-gptdraw
43 files changed, 676 insertions, 619 deletions
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 a393ff5dc..571076db0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,7 +51,7 @@ "@types/reveal": "^4.2.0", "@types/supercluster": "^7.1.3", "@types/textfit": "^2.4.4", - "@types/web": "^0.0.163", + "@types/web": "^0.0.167", "@types/webpack-hot-middleware": "^2.25.9", "@webscopeio/react-textarea-autocomplete": "^4.9.2", "adm-zip": "^0.5.10", @@ -70,7 +70,7 @@ "body-parser": "^1.20.2", "bootstrap": "^5.3.2", "brotli": "^1.3.3", - "browndash-components": "^0.1.47", + "browndash-components": "^0.1.50", "browser-assert": "^1.2.1", "bson": "^6.2.0", "canvas": "^2.11.2", @@ -269,7 +269,7 @@ "@types/d3": "^7.4.3", "@types/dom-mediacapture-record": "^1.0.19", "@types/exif": "^0.6.5", - "@types/express": "^4.17.21", + "@types/express": "^5.0.0", "@types/express-session": "^1.17.10", "@types/file-saver": "^2.0.7", "@types/howler": "^2.2.11", @@ -9452,21 +9452,21 @@ } }, "node_modules/@types/express": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", - "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", + "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", "dev": true, "dependencies": { "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", + "@types/express-serve-static-core": "^5.0.0", "@types/qs": "*", "@types/serve-static": "*" } }, "node_modules/@types/express-serve-static-core": { - "version": "4.19.5", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz", - "integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.0.tgz", + "integrity": "sha512-AbXMTZGt40T+KON9/Fdxx0B2WK5hsgxcfXJLr5bFpZ7b4JCex2WyQPTEKdXqfHiY5nKKBScZ7yCoO6Pvgxfvnw==", "dev": true, "dependencies": { "@types/node": "*", @@ -10102,9 +10102,9 @@ "dev": true }, "node_modules/@types/web": { - "version": "0.0.163", - "resolved": "https://registry.npmjs.org/@types/web/-/web-0.0.163.tgz", - "integrity": "sha512-5Pg2gKfulo186wFnv+YXx0luJGWQ94cCY2/Dy8lU5WAE50FdBoOK45uBbp8FceOSpLJ4UW3dmTW5tvsN9uuX7A==" + "version": "0.0.167", + "resolved": "https://registry.npmjs.org/@types/web/-/web-0.0.167.tgz", + "integrity": "sha512-IhdCUXL+vwofU/IzuOD+/T8Xteuk8fS9TvVJi7y7HTuURs7pWf3Kysxz4+lU0NSkj2LjDQ8AbKaaFFoAgeqYPg==" }, "node_modules/@types/webgl-ext": { "version": "0.0.30", @@ -11660,9 +11660,9 @@ } }, "node_modules/browndash-components": { - "version": "0.1.47", - "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.1.47.tgz", - "integrity": "sha512-562nWku81I1wccgEQbMU/30+RP/81sxsIGoPGLH7Otm9TdrHXWgW8yJS4tzBPXBF03Q2AX8DyQusi6T0LSc7Mw==", + "version": "0.1.50", + "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.1.50.tgz", + "integrity": "sha512-8EgU82os8/Tg3gayh+nYXCQZ8P7GGQWnibz0U2RM0sXTbc3BWTSYiC1Sj1VpL7HPkZ1KMGHzWynCo9QKIOWZBg==", "dependencies": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", @@ -20465,42 +20465,6 @@ "node": ">= 14" } }, - "node_modules/http-proxy-middleware": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", - "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", - "dev": true, - "dependencies": { - "@types/http-proxy": "^1.17.8", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@types/express": "^4.17.13" - }, - "peerDependenciesMeta": { - "@types/express": { - "optional": true - } - } - }, - "node_modules/http-proxy-middleware/node_modules/is-plain-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -34004,6 +33968,54 @@ } } }, + "node_modules/webpack-dev-server/node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/webpack-dev-server/node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dev": true, + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, "node_modules/webpack-dev-server/node_modules/ipaddr.js": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", @@ -34013,6 +34025,18 @@ "node": ">= 10" } }, + "node_modules/webpack-dev-server/node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/webpack-hot-middleware": { "version": "2.26.1", "resolved": "https://registry.npmjs.org/webpack-hot-middleware/-/webpack-hot-middleware-2.26.1.tgz", diff --git a/package.json b/package.json index 20fa219b3..4583ca679 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "@types/d3": "^7.4.3", "@types/dom-mediacapture-record": "^1.0.19", "@types/exif": "^0.6.5", - "@types/express": "^4.17.21", + "@types/express": "^5.0.0", "@types/express-session": "^1.17.10", "@types/file-saver": "^2.0.7", "@types/howler": "^2.2.11", @@ -130,7 +130,7 @@ "@types/reveal": "^4.2.0", "@types/supercluster": "^7.1.3", "@types/textfit": "^2.4.4", - "@types/web": "^0.0.163", + "@types/web": "^0.0.167", "@types/webpack-hot-middleware": "^2.25.9", "@webscopeio/react-textarea-autocomplete": "^4.9.2", "adm-zip": "^0.5.10", @@ -149,7 +149,7 @@ "body-parser": "^1.20.2", "bootstrap": "^5.3.2", "brotli": "^1.3.3", - "browndash-components": "^0.1.47", + "browndash-components": "^0.1.50", "browser-assert": "^1.2.1", "bson": "^6.2.0", "canvas": "^2.11.2", 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/documents/Documents.ts b/src/client/documents/Documents.ts index ba1bc8b7a..bf72a4bd4 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -319,11 +319,11 @@ export class DocumentOptions { contextMenuLabels?: List<string>; contextMenuIcons?: List<string>; 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<string>; 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/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index f296d26bd..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"; @@ -401,9 +401,7 @@ pie title Minerals in my tap water ]; emptyThings.forEach( - thing =>{ DocUtils.AssignDocField(doc, "empty"+thing.key, (opts) => thing.creator(opts), {...standardOps(thing.key), ...thing.opts}, undefined, thing.scripts, thing.funcs); - console.log(thing.key) - }); + thing => DocUtils.AssignDocField(doc, "empty"+thing.key, (opts) => thing.creator(opts), {...standardOps(thing.key), ...thing.opts}, undefined, thing.scripts, thing.funcs)); return [ { toolTip: "Tap or drag to create a note", title: "Note", icon: "sticky-note", dragFactory: doc.emptyNote as Doc, clickFactory: DocCast(doc.emptyNote)}, @@ -743,7 +741,7 @@ pie title Minerals in my tap water { title: "Bullets", toolTip: "Bullet List", btnType: ButtonType.ToggleButton, icon: "list", toolType:"bullet", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} }, { title: "#", toolTip: "Number List", btnType: ButtonType.ToggleButton, icon: "list-ol", toolType:"decimal", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} }, { title: "Vcenter", toolTip: "Vertical center", btnType: ButtonType.ToggleButton, icon: "pallet", toolType:"vcent", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} }, - { title: "Align", toolTip: "Alignment", btnType: ButtonType.MultiToggleButton, toolType:"alignment", ignoreClick: true, + { title: "Align", toolTip: "Alignment", btnType: ButtonType.MultiToggleButton, toolType:"alignment",ignoreClick: true, subMenu: [ { title: "Left", toolTip: "Left align (Cmd-[)", btnType: ButtonType.ToggleButton, icon: "align-left", toolType:"left", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} }, { title: "Center", toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center",ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} }, @@ -760,13 +758,13 @@ pie title Minerals in my tap water static inkTools():Button[] { return [ - { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", toolType: "pen", scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }}, - { title: "Highlight", toolTip: "Highlight (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter",toolType: "highlighter", scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }}, - { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", toolType: "write", scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }}, - { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.MultiToggleButton, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'}, funcs: {toolType:"activeEraserTool()"}, + { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", toolType: "pen", scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }}, + { title: "Highlight",toolTip: "Highlight (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter",toolType: "highlighter", scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }}, + { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", toolType: "write", scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }}, + { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.MultiToggleButton, toolType: InkTool.Eraser, scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }, subMenu: [ { title: "Stroke", toolTip: "Stroke Erase", btnType: ButtonType.ToggleButton, icon: "eraser", toolType:InkTool.StrokeEraser, ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} }, - { title: "Segment", toolTip: "Segment Erase", btnType: ButtonType.ToggleButton, icon: "xmark",toolType:InkTool.SegmentEraser,ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} }, + { title: "Segment", toolTip: "Segment Erase", btnType: ButtonType.ToggleButton, icon: "xmark", toolType:InkTool.SegmentEraser,ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} }, { title: "Radius", toolTip: "Radius Erase", btnType: ButtonType.ToggleButton, icon: "circle-xmark",toolType:InkTool.RadiusEraser, ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} }, ]}, { title: "Eraser Width", toolTip: "Eraser Width", btnType: ButtonType.NumberSliderButton, toolType: "eraserWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, numBtnMin: 1, funcs: {hidden:"NotRadiusEraser()"}}, @@ -806,7 +804,7 @@ pie title Minerals in my tap water { title: "Rotate",toolTip: "Rotate 90", btnType: ButtonType.ClickButton, icon: "redo-alt", scripts: { onClick: 'imageRotate90();' }}, ]; } - static contextMenuTools():Button[] { + static contextMenuTools(doc:Doc):Button[] { return [ { btnList: new List<string>([CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Tree, CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Multicolumn, @@ -823,6 +821,7 @@ pie title Minerals in my tap water { title: "Num", icon:"", toolTip: "Frame # (click to toggle edit mode)",btnType: ButtonType.TextButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}}, { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, width: 30, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}}, + { title: "Filter", icon: "=", toolTip: "Filter cards by tags", subMenu: CurrentUserUtils.tagGroupTools(),ignoreClick:true, toolType:DocumentType.COL, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, btnType: ButtonType.MultiToggleButton, width: 30, backgroundColor: doc.userVariantColor as string}, { title: "Text", icon: "Text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), expertMode: false, toolType:DocumentType.RTF, funcs: { linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available { title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: {hidden: `IsExploreMode()`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available { title: "Doc", icon: "Doc", toolTip: "Freeform Doc tools", subMenu: CurrentUserUtils.freeTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode, true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available @@ -835,8 +834,7 @@ pie title Minerals in my tap water { title: "Video", icon: "Video", toolTip: "Video functions", subMenu: CurrentUserUtils.videoTools(), expertMode: false, toolType:DocumentType.VID, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when video is selected { title: "Image", icon: "Image", toolTip: "Image functions", subMenu: CurrentUserUtils.imageTools(), expertMode: false, toolType:DocumentType.IMG, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when image is selected { title: "Schema", icon: "Schema", toolTip: "Schema functions", subMenu: CurrentUserUtils.schemaTools(), expertMode: false, toolType:CollectionViewType.Schema, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`}, linearBtnWidth:58 }, // Only when Schema is selected - { title: "Filter", icon: "=", toolTip: "Filter cards by tags", subMenu: CurrentUserUtils.tagGroupTools(),ignoreClick:true, toolType:DocumentType.COL, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, btnType: ButtonType.MultiToggleButton, width: 150}, - ]; + ]; } /// initializes a context menu button for the top bar context menu @@ -881,7 +879,7 @@ pie title Minerals in my tap water static setupContextMenuButtons(doc: Doc, field="myContextMenuBtns") { const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", undoIgnoreFields:new List<string>(['width', "linearView_IsOpen"]), flexGap: 0, childDragAction: dropActionType.embed, childDontRegisterViews: true, linearView_IsOpen: true, ignoreClick: true, linearView_Expandable: false, _height: 35 }; const ctxtMenuBtnsDoc = DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), reqdCtxtOpts, undefined); - const ctxtMenuBtns = CurrentUserUtils.contextMenuTools().map(params => this.setupContextMenuBtn(params, ctxtMenuBtnsDoc) ); + const ctxtMenuBtns = CurrentUserUtils.contextMenuTools(doc).map(params => this.setupContextMenuBtn(params, ctxtMenuBtnsDoc) ); return DocUtils.AssignOpts(ctxtMenuBtnsDoc, reqdCtxtOpts, ctxtMenuBtns); } /// Initializes all the default buttons for the top bar context menu diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index 0ede44298..b5d29be4c 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) { 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/util/Scripting.ts b/src/client/util/Scripting.ts index cb314e3f1..c7b86815a 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -1,7 +1,7 @@ // export const ts = (window as any).ts; // import * as typescriptlib from '!!raw-loader!../../../node_modules/typescript/lib/lib.d.ts' // import * as typescriptes5 from '!!raw-loader!../../../node_modules/typescript/lib/lib.es5.d.ts' -// import typescriptlib from 'type_decls.d'; +import typescriptlib from 'type_decls.d'; import * as ts from 'typescript'; import { Doc, FieldType } from '../../fields/Doc'; import { RefField } from '../../fields/RefField'; @@ -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]; @@ -248,7 +243,7 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp const funcScript = `(function(${paramString})${reqTypes} { ${body} })`; host.writeFile('file.ts', funcScript); - // if (typecheck) host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib); + if (typecheck) host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib); const program = ts.createProgram(['file.ts'], {}, host); const testResult = program.emit(); const outputText = host.readFile('file.js'); diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx index 5edb5fc0d..399e8a238 100644 --- a/src/client/views/ContextMenu.tsx +++ b/src/client/views/ContextMenu.tsx @@ -252,7 +252,7 @@ export class ContextMenu extends ObservableReactComponent<{ noexpand?: boolean } this._selectedIndex--; } e.preventDefault(); - } else if (e.key === 'Enter' || e.key === 'Tab') { + } else if ((e.key === 'Enter' || e.key === 'Tab') && this._selectedIndex >= 0) { const item = this.flatItems[this._selectedIndex]; if (item.event) { item.event({ x: this.pageX, y: this.pageY }); diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 28424c711..32bf67df1 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -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 { AnnotationPalette } from './smartdraw/AnnotationPalette'; @observer export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (DocumentView | undefined)[]; stack?: unknown }> { diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 7f6cfbb87..1c0d51e17 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -59,6 +59,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora private _interactionLock?: boolean; @observable _showNothing = true; + @observable private _forceRender = 0; @observable private _accumulatedTitle = ''; @observable private _titleControlString: string = '$title'; @observable private _editingTitle = false; @@ -230,15 +231,17 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora views.forEach(iconView => { 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); } }); @@ -625,8 +628,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora }); }; - @computed - get selectionTitle(): string { + @computed get selectionTitle(): string { if (DocumentView.Selected().length === 1) { const selected = DocumentView.Selected()[0]; if (this._titleControlString.startsWith('$')) { @@ -647,8 +649,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora } return this._rotCenter; } - render() { + this._forceRender; const { b, r, x, y } = this.Bounds; const seldocview = DocumentView.Selected().lastElement(); if (SnappingManager.IsDragging || r - x < 1 || x === Number.MAX_VALUE || !seldocview || this._hidden || isNaN(r) || isNaN(b) || isNaN(x) || isNaN(y)) { @@ -661,6 +663,11 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora return null; } + if (seldocview && !seldocview?.ContentDiv?.getBoundingClientRect().width) { + setTimeout(action(() => this._forceRender++)); // if the selected Doc has no width, then assume it's stil being layed out and try to render again later. + return null; + } + // sharing const acl = GetEffectiveAcl(!this._showLayoutAcl ? Doc.GetProto(seldocview.Document) : seldocview.Document); const docShareMode = HierarchyMapping.get(acl)!.name; diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index befd19f6e..5fddaec9a 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -3,34 +3,40 @@ 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'; +import { Gestures } from '../../pen-gestures/GestureTypes'; +import { GestureUtils } from '../../pen-gestures/GestureUtils'; +import { Result } from '../../pen-gestures/ndollar'; +import { DocumentType } from '../documents/DocumentTypes'; +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'; +import { returnEmptyDocViewList } from './StyleProvider'; +import { CollectionFreeFormView } from './collections/collectionFreeForm'; import { ActiveArrowEnd, ActiveArrowScale, ActiveArrowStart, ActiveDash, + ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, + DocumentView, SetActiveArrowStart, SetActiveDash, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, } from './nodes/DocumentView'; -import { Gestures } from '../../pen-gestures/GestureTypes'; -import { GestureUtils } from '../../pen-gestures/GestureUtils'; -import { InteractionUtils } from '../util/InteractionUtils'; -import { ScriptingGlobals } from '../util/ScriptingGlobals'; -import { Transform } from '../util/Transform'; -import './GestureOverlay.scss'; -import { ObservableReactComponent } from './ObservableReactComponent'; -import { returnEmptyDocViewList } from './StyleProvider'; -import { ActiveFillColor, DocumentView } from './nodes/DocumentView'; - export enum ToolglassTools { InkToText = 'inktotext', IgnoreGesture = 'ignoregesture', @@ -41,6 +47,10 @@ interface GestureOverlayProps { isActive: boolean; } @observer +/** + * class for gestures. will determine if what the user drew is a gesture, and will transform the ink stroke into the shape the user + * drew or perform the gesture's action + */ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChildren<GestureOverlayProps>> { // eslint-disable-next-line no-use-before-define static Instance: GestureOverlay; @@ -70,10 +80,6 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil } private _overlayRef = React.createRef<HTMLDivElement>(); - private _d1: Doc | undefined; - private _inkToTextDoc: Doc | undefined; - private thumbIdentifier?: number; - private pointerIdentifier?: number; constructor(props: GestureOverlayProps) { super(props); @@ -88,7 +94,6 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil componentDidMount() { GestureOverlay.Instance = this; } - @action onPointerDown = (e: React.PointerEvent) => { if (!(e.target as HTMLElement)?.className?.toString().startsWith('lm_')) { @@ -127,78 +132,241 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil // SetActiveArrowEnd('none'); } } + /** + * If what the user drew is a scribble, this returns the documents that were scribbled over + * I changed it so it doesnt use triangles. It will modify an intersect array, with its length being + * how many sharp cusps there are. The first index will have a boolean that is based on if there is an + * intersection in the first 1/length percent of the stroke. The second index will be if there is an intersection + * in the 2nd 1/length percent of the stroke. This array will be used in determineIfScribble(). + * @param ffview freeform view where scribble is drawn + * @param scribbleStroke scribble stroke in screen space coordinats + * @returns array of documents scribbled over + */ + 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; + } + }); + 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 + }; + /** + * 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 + */ + 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 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 + */ + 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 + * @param triangle triangle with 3 points + * @returns will return an array, with its lenght being equal to how many intersections there are betweent the 2 strokes. + * each item in the array will contain a number between 0-1 or a number 0-1 seperated by a comma. If one of the curves is a line, then + * then there will just be a number that reprents how far that intersection is along the scribble. For example, + * .1 means that the intersection occurs 10% into the scribble, so near the beginning of it. but if they are both curves, then + * it will return two numbers, one for each curve, seperated by a comma. Sometimes, the percentage it returns is inaccurate, + * espcially in the beginning and end parts of the stroke. dont know why. hope this makes sense + */ + 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]); + return p; + }, [] as number[][]); + newPoints.pop(); + const controlPoints: { X: number; Y: number }[] = []; + + const bezierCurves = fitCurve.default(newPoints, 10); + Array.from(bezierCurves).forEach(curve => { + controlPoints.push({ X: curve[0][0], Y: curve[0][1] }); + controlPoints.push({ X: curve[1][0], Y: curve[1][1] }); + controlPoints.push({ X: curve[2][0], Y: curve[2][1] }); + controlPoints.push({ X: curve[3][0], Y: curve[3][1] }); + }); + const dist = Math.sqrt((controlPoints[0].X - controlPoints.lastElement().X) * (controlPoints[0].X - controlPoints.lastElement().X) + (controlPoints[0].Y - controlPoints.lastElement().Y) * (controlPoints[0].Y - controlPoints.lastElement().Y)); + if (controlPoints.length > 4 && dist < 10) controlPoints[controlPoints.length - 1] = controlPoints[0]; + this._points.length = 0; + this._points.push(...controlPoints); + this.dispatchGesture(Gestures.Stroke); + }; @action onPointerUp = () => { + const ffView = DocumentView.DownDocView?.ComponentView instanceof CollectionFreeFormView && DocumentView.DownDocView.ComponentView; DocumentView.DownDocView = undefined; if (this._points.length > 1) { const B = this.svgBounds; const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top })); - + const { Name, Score } = + (this.InkShape + ? new Result(this.InkShape, 1, Date.now) + : Doc.UserDoc().recognizeGestures && points.length > 2 + ? GestureUtils.GestureRecognizer.Recognize([points]) + : undefined) ?? + new Result(Gestures.Stroke, 1, Date.now); // prettier-ignore + + const cuspArray = this.getCusps(points); // if any of the shape is activated in the CollectionFreeFormViewChrome - if (this.InkShape) { - GestureOverlay.makeBezierPolygon(this._points, this.InkShape, false); - this.dispatchGesture(this.InkShape); - this.primCreated(); - } - // if we're not drawing in a toolglass try to recognize as gesture - else { - // need to decide when to turn gestures back on - const result = points.length > 2 && GestureUtils.GestureRecognizer.Recognize([points]); - let actionPerformed = false; - if (Doc.UserDoc().recognizeGestures && result && result.Score > 0.7) { - switch (result.Name) { - case Gestures.Line: - case Gestures.Triangle: - case Gestures.Rectangle: - case Gestures.Circle: - GestureOverlay.makeBezierPolygon(this._points, result.Name, true); - actionPerformed = this.dispatchGesture(result.Name); - break; - case Gestures.Scribble: - console.log('scribble'); - break; - case Gestures.RightAngle: - console.log('RightAngle'); - break; - default: { - /* empty */ - } - } + // need to decide when to turn gestures back on + const actionPerformed = ((name: Gestures) => { + 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(this._points, Name, true); + return this.dispatchGesture(name); + case Gestures.RightAngle: + return ffView && this.convertToText(ffView).length > 0; + default: } - - // if no gesture (or if the gesture was unsuccessful), "dry" the stroke into an ink document - if (!actionPerformed) { - const newPoints = this._points.reduce((p, pts) => { - p.push([pts.X, pts.Y]); - return p; - }, [] as number[][]); - newPoints.pop(); - const controlPoints: { X: number; Y: number }[] = []; - - const bezierCurves = fitCurve.default(newPoints, 10); - Array.from(bezierCurves).forEach(curve => { - controlPoints.push({ X: curve[0][0], Y: curve[0][1] }); - controlPoints.push({ X: curve[1][0], Y: curve[1][1] }); - controlPoints.push({ X: curve[2][0], Y: curve[2][1] }); - controlPoints.push({ X: curve[3][0], Y: curve[3][1] }); - }); - const dist = Math.sqrt( - (controlPoints[0].X - controlPoints.lastElement().X) * (controlPoints[0].X - controlPoints.lastElement().X) + (controlPoints[0].Y - controlPoints.lastElement().Y) * (controlPoints[0].Y - controlPoints.lastElement().Y) - ); - // eslint-disable-next-line prefer-destructuring - if (controlPoints.length > 4 && dist < 10) controlPoints[controlPoints.length - 1] = controlPoints[0]; - this._points.length = 0; - this._points.push(...controlPoints); - this.dispatchGesture(Gestures.Stroke); + })(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) { + const scribbledOver = ffView && this.isScribble(ffView, cuspArray, this._points); + if (scribbledOver) { + undoable(() => ffView.removeDocument(scribbledOver), 'scribble erase')(); + } else { + this.dryInk(); } } } this._points.length = 0; }; - - public static makeBezierPolygon = (points: { X: number; Y: number }[], shape: string, gesture: boolean) => { - const xs = points.map(p => p.X); - const ys = points.map(p => p.Y); + /** + * used in the rightAngle gesture to convert handwriting into text. will only work on collections + * TODO: make it work on individual ink docs. + */ + convertToText = (ffView: CollectionFreeFormView) => { + let minX = 999999999; + let maxX = -999999999; + let minY = 999999999; + let maxY = -999999999; + const textDocs: Doc[] = []; + 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) { + if (intersectRect({ ...bounds, width: bounds.right - bounds.left, height: bounds.bottom - bounds.top }, InkField.getBounds(this._points))) { + if (doc.x < minX) { + minX = doc.x; + } + if (doc.x > maxX) { + maxX = doc.x; + } + if (doc.y < minY) { + minY = doc.y; + } + if (doc.y + doc.height > maxY) { + maxY = doc.y + doc.height; + } + const newDoc = Docs.Create.TextDocument(doc.transcription as string, { title: '', x: doc.x as number, y: minY }); + newDoc.height = doc.height; + newDoc.width = doc.width; + if (ffView.addDocument && ffView.removeDocument) { + ffView.addDocument(newDoc); + ffView.removeDocument(doc); + } + textDocs.push(newDoc); + } + } + } + }); + return textDocs; + }; + /** + * Returns array of coordinates corresponding to the sharp cusps in an input stroke + * @param points array of X,Y stroke coordinates + * @returns array containing the coordinates of the sharp cusps + */ + getCusps(points: InkData) { + const arrayOfPoints: { X: number; Y: number }[] = []; + arrayOfPoints.push(points[0]); + for (let i = 0; i < points.length - 2; i++) { + const point1 = points[i]; + const point2 = points[i + 1]; + const point3 = points[i + 2]; + if (this.find_angle(point1, point2, point3) < 90) { + // NOTE: this is not an accurate way to find cusps -- it is highly dependent on sampling rate and doesn't work well with slowly drawn scribbles + arrayOfPoints.push(point2); + } + } + arrayOfPoints.push(points[points.length - 1]); + return arrayOfPoints; + } + /** + * takes in three points and then determines the angle of the points. used to determine if the cusp + * is sharp enoug + * @returns + */ + find_angle(A: { X: number; Y: number }, B: { X: number; Y: number }, C: { X: number; Y: number }) { + const AB = Math.sqrt(Math.pow(B.X - A.X, 2) + Math.pow(B.Y - A.Y, 2)); + const BC = Math.sqrt(Math.pow(B.X - C.X, 2) + Math.pow(B.Y - C.Y, 2)); + const AC = Math.sqrt(Math.pow(C.X - A.X, 2) + Math.pow(C.Y - A.Y, 2)); + return Math.acos((BC * BC + AB * AB - AC * AC) / (2 * BC * AB)) * (180 / Math.PI); + } + makeBezierPolygon = (points: { X: number; Y: number }[], shape: string, gesture: boolean) => { + const xs = this._points.map(p => p.X); + const ys = this._points.map(p => p.Y); let right = Math.max(...xs); let left = Math.min(...xs); let bottom = Math.max(...ys); @@ -394,7 +562,6 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil this._strokes.map((l, i) => { const b = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; // this.getBounds(l, true); return ( - // eslint-disable-next-line react/no-array-index-key <svg key={i} width={b.width} height={b.height} style={{ top: 0, left: 0, transform: `translate(${b.left}px, ${b.top}px)`, pointerEvents: 'none', position: 'absolute', zIndex: 30000, overflow: 'visible' }}> {InteractionUtils.CreatePolyline( l, diff --git a/src/client/views/InkTranscription.scss b/src/client/views/InkTranscription.scss index 18d6b8b10..c77117ccc 100644 --- a/src/client/views/InkTranscription.scss +++ b/src/client/views/InkTranscription.scss @@ -2,9 +2,7 @@ .error-msg { display: none !important; } - .ms-editor{ - .smartguide{ - top:1000px; - } + .ms-editor { + top: 1000px; } -}
\ No newline at end of file +} diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx index 26e0aa372..24d53a8c8 100644 --- a/src/client/views/InkTranscription.tsx +++ b/src/client/views/InkTranscription.tsx @@ -3,7 +3,7 @@ import { action, observable } from 'mobx'; import * as React from 'react'; import { Doc, DocListCast } from '../../fields/Doc'; import { InkData, InkField, InkTool } from '../../fields/InkField'; -import { Cast, DateCast, DocCast, ImageCast, NumCast, StrCast } from '../../fields/Types'; +import { Cast, DateCast, ImageCast, NumCast } from '../../fields/Types'; import { aggregateBounds } from '../../Utils'; import { DocumentType } from '../documents/DocumentTypes'; import { CollectionFreeFormView, MarqueeView } from './collections/collectionFreeForm'; @@ -11,42 +11,40 @@ import { InkingStroke } from './InkingStroke'; import './InkTranscription.scss'; import { Docs } from '../documents/Documents'; import { DocumentView } from './nodes/DocumentView'; -import { Number } from 'mongoose'; -import { NumberArray } from 'd3'; import { ImageField } from '../../fields/URLField'; import { gptHandwriting } from '../apis/gpt/GPT'; -import * as fs from 'fs'; 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 @observable _mathRegister: any = undefined; + // eslint-disable-next-line @typescript-eslint/no-explicit-any @observable _mathRef: any = undefined; + // eslint-disable-next-line @typescript-eslint/no-explicit-any @observable _textRegister: any = undefined; + // eslint-disable-next-line @typescript-eslint/no-explicit-any @observable _textRef: any = undefined; + // eslint-disable-next-line @typescript-eslint/no-explicit-any @observable iinkEditor: any = undefined; + // eslint-disable-next-line @typescript-eslint/no-explicit-any private lastJiix: any; private currGroup?: Doc; private collectionFreeForm?: CollectionFreeFormView; - constructor(props: Readonly<{}>) { + constructor(props: Readonly<object>) { super(props); InkTranscription.Instance = this; } - - componentWillUnmount() { - // this._mathRef.removeEventListener('exported', (e: any) => this.exportInk(e, this._mathRef)); - // this._textRef.removeEventListener('exported', (e: any) => this.exportInk(e, this._textRef)); - } - @action + // eslint-disable-next-line @typescript-eslint/no-explicit-any setMathRef = async (r: any) => { if (!this._textRegister && r) { - let editor; const options = { configuration: { server: { @@ -61,21 +59,22 @@ export class InkTranscription extends React.Component { }, }, }; - - editor = new iink.Editor(r, options as any); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const editor = new iink.Editor(r, options as any); await editor.initialize(); this._textRegister = r; + // eslint-disable-next-line @typescript-eslint/no-explicit-any r?.addEventListener('exported', (e: any) => this.exportInk(e, this._textRef)); return (this._textRef = r); } }; @action + // eslint-disable-next-line @typescript-eslint/no-explicit-any setTextRef = async (r: any) => { if (!this._textRegister && r) { - let editor; const options = { configuration: { server: { @@ -90,12 +89,13 @@ export class InkTranscription extends React.Component { }, }, }; - - editor = new iink.Editor(r, options as any); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const editor = new iink.Editor(r, options as any); await editor.initialize(); this.iinkEditor = editor; this._textRegister = r; + // eslint-disable-next-line @typescript-eslint/no-explicit-any r?.addEventListener('exported', (e: any) => this.exportInk(e, this._textRef)); return (this._textRef = r); @@ -124,28 +124,9 @@ export class InkTranscription extends React.Component { strokes.push(d.inkData.map(pd => inkStroke.ptToScreen({ X: pd.X, Y: pd.Y }))); times.push(DateCast(i.author_date).getDate().getTime()); }); - console.log(strokes); - console.log( - `this.Multistrokes.push( - new Multistroke( - Gestures.Scribble, - useBoundedRotationInvariance, - new Array([ - ` + - this.convertPointsToString(strokes) + - ` - ]) - ) - ) - ` - ); - //console.log(this.convertPointsToString(strokes)); - //console.log(this.convertPointsToString2(strokes)); this.currGroup = groupDoc; const pointerData = strokes.map((stroke, i) => this.inkJSON(stroke, times[i])); - const processGestures = false; if (math) { - console.log('math'); this.iinkEditor.importPointEvents(pointerData); } else { this.iinkEditor.importPointEvents(pointerData); @@ -172,9 +153,9 @@ export class InkTranscription extends React.Component { t: number; p: number; } - let strokeObjects: strokeData[] = []; + const strokeObjects: strokeData[] = []; stroke.forEach(point => { - let tempObject: strokeData = { + const tempObject: strokeData = { x: point.X, y: point.Y, t: time, @@ -220,6 +201,7 @@ export class InkTranscription extends React.Component { * @param e the event objects * @param ref the ref to the editor */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any exportInk = async (e: any, ref: any) => { const exports = e.detail['application/vnd.myscript.jiix']; if (exports) { @@ -236,6 +218,7 @@ export class InkTranscription extends React.Component { this.lastJiix = JSON.parse(exports['application/vnd.myscript.jiix']); // map timestamp to strokes const timestampWord = new Map<number, string>(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any this.lastJiix.words.map((word: any) => { if (word.items) { word.items.forEach((i: { id: string; timestamp: string; X: Array<number>; Y: Array<number>; F: Array<number> }) => { @@ -271,12 +254,7 @@ export class InkTranscription extends React.Component { if (this.currGroup && text) { DocumentView.getDocumentView(this.currGroup)?.ComponentView?.updateIcon?.(); - this.currGroup.transcription = text; - this.currGroup.title = text; - let image = await DocumentView.GetDocImage(this.currGroup); - const pathname = image?.url.href as string; - console.log(image?.url); - console.log(image); + const image = await this.getIcon(); const { href } = (image as URLField).url; const hrefParts = href.split('.'); const hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`; @@ -284,11 +262,12 @@ export class InkTranscription extends React.Component { try { const hrefBase64 = await this.imageUrlToBase64(hrefComplete); response = await gptHandwriting(hrefBase64); - console.log(response); - } catch (error) { - console.log('bad things have happened'); + } catch { + console.error('Error getting image'); } const textBoxText = 'iink: ' + text + '\n' + '\n' + 'ChatGPT: ' + response; + this.currGroup.transcription = response; + this.currGroup.title = response; if (!this.currGroup.hasTextBox) { const newDoc = Docs.Create.TextDocument(textBoxText, { title: '', x: this.currGroup.x as number, y: (this.currGroup.y as number) + (this.currGroup.height as number) }); newDoc.height = 200; @@ -300,6 +279,22 @@ export class InkTranscription extends React.Component { } } }; + /** + * gets the icon of the collection that was just made + * @returns the image of the collection + */ + async getIcon() { + const docView = DocumentView.getDocumentView(this.currGroup); + if (docView) { + docView.ComponentView?.updateIcon?.(); + return new Promise<ImageField | undefined>(res => setTimeout(() => res(ImageCast(docView.Document.icon)), 1000)); + } + return undefined; + } + /** + * converts the image to base url formate + * @param imageUrl imageurl taken from the collection icon + */ imageUrlToBase64 = async (imageUrl: string): Promise<string> => { try { const response = await fetch(imageUrl); @@ -350,7 +345,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); @@ -373,7 +368,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); @@ -392,29 +387,23 @@ export class InkTranscription extends React.Component { ); docView.props.removeDocument?.(selected); // Gets a collection based on the selected nodes using a marquee view ref - const newCollection = MarqueeView.getCollection(selected, undefined, true, { top: 1, left: 1, width: 1, height: 1 }); - if (newCollection) { - newCollection.width = NumCast(newCollection._width); - newCollection.height = NumCast(newCollection._height); - // if the grouping we are creating is an individual word - if (word) { - newCollection.title = word; - } + const newCollection = MarqueeView.getCollection(selected, undefined, true, marqViewRef?.Bounds ?? { top: 1, left: 1, width: 1, height: 1 }); + // if the grouping we are creating is an individual word + if (word) { + newCollection.title = word; } // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs - newCollection && docView.props.addDocument?.(newCollection); - if (newCollection) { - newCollection.hasTextBox = false; - } + docView.props.addDocument?.(newCollection); + newCollection.hasTextBox = false; return newCollection; } render() { return ( <div className="ink-transcription"> - <div className="math-editor" ref={this.setMathRef} touch-action="none"></div> - <div className="text-editor" ref={this.setTextRef} touch-action="none"></div> + <div className="math-editor" ref={this.setMathRef}></div> + <div className="text-editor" ref={this.setTextRef}></div> </div> ); } diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 32bbd57c6..270266a94 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'; @@ -48,8 +47,11 @@ import { FormattedTextBox, FormattedTextBoxProps } from './nodes/formattedText/F import { PinDocView, PinProps } from './PinFuncs'; import { StyleProp } from './StyleProp'; import { ViewBoxInterface } from './ViewBoxInterface'; +import { InkTranscription } from './InkTranscription'; +import { CollectionFreeFormView } from './collections/collectionFreeForm'; +import { DocumentView } from './nodes/DocumentView'; -// eslint-disable-next-line @typescript-eslint/no-var-requires +// eslint-disable-next-line @typescript-eslint/no-require-imports const { INK_MASK_SIZE } = require('./global/globalCssVariables.module.scss'); // prettier-ignore @observer @@ -108,8 +110,17 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>() * and the recognized words to the 'handwriting' */ analyzeStrokes = () => { - const data: InkData = this.inkScaledData().inkData ?? []; - CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ['inkAnalysis', 'handwriting'], [data]); + const ffView = CollectionFreeFormView.from(this.DocumentView?.()); + if (ffView) { + const selected = DocumentView.SelectedDocs(); + const newCollection = InkTranscription.Instance.groupInkDocs( + selected.filter(doc => doc.embedContainer), + ffView + ); + ffView.unprocessedDocs = []; + + InkTranscription.Instance.transcribeInk(newCollection, selected, false); + } }; /** @@ -331,9 +342,10 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>() 1, 1, '' as Gestures, - 'none', + 'all', 1.0, - false + false, + this.onPointerDown )} <InkControlPtHandles inkView={this} inkDoc={inkDoc} inkCtrlPoints={inkData} screenCtrlPoints={this.screenCtrlPts} nearestScreenPt={this.nearestScreenPt} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} /> <InkTangentHandles inkView={this} inkDoc={inkDoc} screenCtrlPoints={this.screenCtrlPts} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} /> @@ -461,7 +473,6 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>() // 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} @@ -478,7 +489,6 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>() // top: (this._props.PanelHeight() - (lineHeightGuess * fsize + 20) * (this._props.NativeDimScaling?.() || 1)) / 2, }}> <FormattedTextBox - // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} setHeight={undefined} setContentViewBox={this.setSubContentView} // this makes the inkingStroke the "dominant" component - ie, it will show the inking UI when selected (not text) diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index 331b3db64..a543b4875 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -46,7 +46,6 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> { ); } // prettier-ignore public static LightboxDoc = () => LightboxView.Instance?._doc; - // eslint-disable-next-line no-use-before-define static Instance: LightboxView; private _path: { doc: Opt<Doc>; // @@ -341,7 +340,6 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> { } interface LightboxTourBtnProps { navBtn: (left: Opt<string | number>, bottom: Opt<number>, top: number, icon: IconProp, display: boolean, click: () => void, color?: string) => JSX.Element; - // eslint-disable-next-line react/no-unused-prop-types future: () => Opt<Doc[]>; stepInto: () => void; lightboxDoc: () => Opt<Doc>; 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 54e049c04..31d7e82a6 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 @@ -851,7 +851,6 @@ export class MainView extends ObservableReactComponent<object> { }; @computed get mainInnerContent() { - trace(); const leftMenuFlyoutWidth = this._leftMenuFlyoutWidth + this.leftMenuWidth(); const width = this.propertiesWidth() + leftMenuFlyoutWidth; return ( @@ -979,13 +978,11 @@ export class MainView extends ObservableReactComponent<object> { <div className="mainView-snapLines"> <svg style={{ width: '100%', height: '100%' }}> {[ - ...SnappingManager.HorizSnapLines.map((l, i) => ( - // eslint-disable-next-line react/no-array-index-key - <line key={'horiz' + i} x1="0" y1={l} x2="2000" y2={l} stroke={lightOrDark(StrCast(dragPar.layoutDoc.backgroundColor, 'gray'))} opacity={0.3} strokeWidth={1} strokeDasharray="2 2" /> + ...SnappingManager.HorizSnapLines.map(l => ( + <line key={'horiz' + l} x1="0" y1={l} x2="2000" y2={l} stroke={lightOrDark(StrCast(dragPar.layoutDoc.backgroundColor, 'gray'))} opacity={0.3} strokeWidth={1} strokeDasharray="2 2" /> )), - ...SnappingManager.VertSnapLines.map((l, i) => ( - // eslint-disable-next-line react/no-array-index-key - <line key={'vert' + i} y1={this.topOfMainDocContent.toString()} x1={l} y2="2000" x2={l} stroke={lightOrDark(StrCast(dragPar.layoutDoc.backgroundColor, 'gray'))} opacity={0.3} strokeWidth={1} strokeDasharray="2 2" /> + ...SnappingManager.VertSnapLines.map(l => ( + <line key={'vert' + l} y1={this.topOfMainDocContent.toString()} x1={l} y2="2000" x2={l} stroke={lightOrDark(StrCast(dragPar.layoutDoc.backgroundColor, 'gray'))} opacity={0.3} strokeWidth={1} strokeDasharray="2 2" /> )), ]} </svg> @@ -1073,10 +1070,7 @@ export class MainView extends ObservableReactComponent<object> { docView={DocButtonState.Instance.LinkEditorDocView} /> ) : null} - {LinkInfo.Instance?.LinkInfo ? ( - // eslint-disable-next-line react/jsx-props-no-spreading - <LinkDocPreview {...LinkInfo.Instance.LinkInfo} /> - ) : null} + {LinkInfo.Instance?.LinkInfo ? <LinkDocPreview {...LinkInfo.Instance.LinkInfo} /> : null} {((page: string) => { // prettier-ignore switch (page) { diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index fcac4d464..229ceffe2 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -18,11 +18,12 @@ import { Id } from '../../fields/FieldSymbols'; import { InkField } from '../../fields/InkField'; import { List } from '../../fields/List'; import { ComputedField } from '../../fields/ScriptField'; -import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../fields/Types'; +import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types'; import { GetEffectiveAcl, SharingPermissions, normalizeEmail } from '../../fields/util'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { GroupManager } from '../util/GroupManager'; import { LinkManager } from '../util/LinkManager'; +import { SettingsManager } from '../util/SettingsManager'; import { SharingManager } from '../util/SharingManager'; import { SnappingManager } from '../util/SnappingManager'; import { Transform } from '../util/Transform'; @@ -30,6 +31,7 @@ import { UndoManager, undoBatch, undoable } from '../util/UndoManager'; import { EditableView } from './EditableView'; import { FilterPanel } from './FilterPanel'; import { InkStrokeProperties } from './InkStrokeProperties'; +import { InkingStroke } from './InkingStroke'; import { ObservableReactComponent } from './ObservableReactComponent'; import { PropertiesButtons } from './PropertiesButtons'; import { PropertiesDocBacklinksSelector } from './PropertiesDocBacklinksSelector'; @@ -41,9 +43,6 @@ import { DocumentView } from './nodes/DocumentView'; import { StyleProviderFuncType } from './nodes/FieldView'; import { OpenWhere } from './nodes/OpenWhere'; import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails'; -import { InkingStroke } from './InkingStroke'; -import { SettingsManager } from '../util/SettingsManager'; -import { MarqueeOptionsMenu } from './collections/collectionFreeForm'; import { SmartDrawHandler } from './smartdraw/SmartDrawHandler'; interface PropertiesViewProps { @@ -75,6 +74,10 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps return 200; } + @computed get containsInkDoc() { + return this.containsInk(this.selectedDoc); + } + @computed get selectedDoc() { return DocumentView.SelectedSchemaDoc() || this.selectedDocumentView?.Document || Doc.ActiveDashboard; } @@ -118,7 +121,6 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps @observable openSlideOptions: boolean = false; // For ink groups - @observable containsInkDoc: boolean = false; @observable inkDoc: Doc | undefined = undefined; @observable _controlButton: boolean = false; @@ -814,7 +816,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps } @computed get selectedStrokes() { - return this.containsInkDoc ? DocListCast(this.selectedDoc[DocData].data) : this.selectedDoc ? [this.selectedDoc] : []; + return this.containsInkDoc ? DocListCast(this.selectedDoc[DocData].data) : DocumentView.SelectedSchemaDoc() ? [DocumentView.SelectedSchemaDoc()!] : DocumentView.SelectedDocs().filter(doc => doc.layout_isSvg); } @computed get shapeXps() { return NumCast(this.selectedDoc?.x); } // prettier-ignore set shapeXps(value) { this.selectedDoc && (this.selectedDoc.x = Math.round(value * 100) / 100); } // prettier-ignore @@ -1013,7 +1015,9 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps } @computed get widthStk() { return this.getField('stroke_width') || '1'; } // prettier-ignore set widthStk(value) { - this.selectedDoc && (this.selectedDoc[DocData].stroke_width = Number(value)); + this.selectedStrokes.forEach(doc => { + doc[DocData].stroke_width = Number(value); + }); } @computed get markScal() { return Number(this.getField('stroke_markerScale') || '1'); } // prettier-ignore set markScal(value) { @@ -1023,7 +1027,9 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps } @computed get smoothAmt() { return Number(this.getField('stroke_smoothAmount') || '5'); } // prettier-ignore set smoothAmt(value) { - this.selectedDoc && (this.selectedDoc[DocData].stroke_smoothAmount = Number(value)); + this.selectedStrokes.forEach(doc => { + doc[DocData].stroke_smoothAmount = Number(value); + }); } @computed get markHead() { return this.getField('stroke_startMarker') || ''; } // prettier-ignore set markHead(value) { @@ -1276,19 +1282,16 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps @computed get filtersSubMenu() { return ( - // prettier-ignore <PropertiesSection title="Filters" isOpen={this.openFilters} setIsOpen={action(bool => { this.openFilters = bool; })} onDoubleClick={this.CloseAll}> <div className="propertiesView-content filters" style={{ position: 'relative', height: 'auto' }}> <FilterPanel Document={this.selectedDoc ?? Doc.ActiveDashboard!} /> </div> </PropertiesSection> - ); + ); // prettier-ignore } @computed get inkSubMenu() { - this.containsInkDoc = false; return ( - // prettier-ignore <> <PropertiesSection title="Appearance" isOpen={this.openAppearance} setIsOpen={bool => { this.openAppearance = bool; }} onDoubleClick={this.CloseAll}> {this.selectedLayoutDoc?.layout_isSvg ? this.appearanceEditor : null} @@ -1297,7 +1300,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps {this.transformEditor} </PropertiesSection> </> - ); + ); // prettier-ignore } /** @@ -1306,26 +1309,19 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps */ containsInk = (selectedDoc: Doc) => { const childDocs: Doc[] = DocListCast(selectedDoc[DocData].data); - for (var i = 0; i < childDocs.length; i++) { + for (let i = 0; i < childDocs.length; i++) { if (DocumentView.getDocumentView(childDocs[i])?.layoutDoc?.layout_isSvg) { this.inkDoc = childDocs[i]; - this.containsInkDoc = true; return true; } } - this.containsInkDoc = false; return false; }; @computed get inkCollectionSubMenu() { - return ( - // prettier-ignore - <> - <PropertiesSection title="Ink Appearance" isOpen={this.openAppearance} setIsOpen={bool => { this.openAppearance = bool; }} onDoubleClick={this.CloseAll}> - {this.isGroup && this.containsInk(this.selectedDoc) ? this.appearanceEditor : null} - </PropertiesSection> - </> - ); + return <PropertiesSection title="Ink Appearance" isOpen={this.openAppearance} setIsOpen={bool => { this.openAppearance = bool; }} onDoubleClick={this.CloseAll}> + {this.isGroup && this.containsInk(this.selectedDoc) ? this.appearanceEditor : null} + </PropertiesSection>; // prettier-ignore } @computed get fieldsSubMenu() { diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index d9c27254a..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<string, Doc>(); + private _dropped = false; // indicate when a card doc has just moved; - @observable _forceChildXf = false; + @observable _forceChildXf = 0; @observable _hoveredNodeIndex = -1; @observable _docRefs = new ObservableMap<Doc, DocumentView>(); @observable _maxRowCount = 10; @@ -99,8 +100,7 @@ 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 - + this._props.setContentViewBox?.(this); // Reaction to cardSort changes this._disposers.sort = reaction( () => GPTPopup.Instance.visible, @@ -112,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() { @@ -176,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(); @@ -267,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; }; /** @@ -306,6 +311,7 @@ export class CollectionCardView extends CollectionSubView() { if (de.complete.docDragData.removeDocument?.(draggedDoc)) { this.dataDoc[this.fieldKey] = new List<Doc>(sorted); } + this._dropped = true; } e.stopPropagation(); return true; @@ -340,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] = (() => { @@ -364,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) => ( <DocumentView {...this._props} - ref={action((r: DocumentView) => 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} @@ -387,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} @@ -593,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, @@ -608,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 ( <div key={doc[Id]} className={`card-item${isSelected ? '-active' : anySelected ? '-inactive' : ''}`} - onPointerUp={() => { - 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', @@ -635,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)} </div> ); @@ -649,7 +665,8 @@ export class CollectionCardView extends CollectionSubView() { <div className="collectionCardView-outer" ref={(ele: HTMLDivElement | null) => 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/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index 4951590dc..c5da8e037 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -14,7 +14,9 @@ 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-require-imports const { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } = require('../global/globalCssVariables.module.scss'); @observer @@ -45,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<number> => { @@ -65,11 +84,11 @@ 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) => ( <DocumentView - // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} Document={childPair.layout} TemplateDataDocument={childPair.data} @@ -83,7 +102,7 @@ export class CollectionCarousel3DView extends CollectionSubView() { LayoutTemplate={this._props.childLayoutTemplate} LayoutTemplateString={this._props.childLayoutString} focus={this.focus} - ScreenToLocalTransform={this.childScreenToLocal} + ScreenToLocalTransform={dxf} isContentActive={this.isChildContentActive} isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive} PanelWidth={this.panelWidth} @@ -93,7 +112,7 @@ export class CollectionCarousel3DView extends CollectionSubView() { return this.carouselItems.map((childPair, index) => ( <div key={childPair.layout[Id]} className={`collectionCarousel3DView-item${index === currentIndex ? '-active' : ''} ${index}`} style={{ width: this.panelWidth() }}> - {displayDoc(childPair)} + {displayDoc(childPair, index < currentIndex ? this.childScreenLeftToLocal : index === currentIndex ? this.childCenterScreenToLocal : this.childScreenRightToLocal)} </div> )); } diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 9741c45fe..74cf580c9 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -3,8 +3,8 @@ import { IReactionDisposer, action, computed, makeObservable, observable, reacti import { observer } from 'mobx-react'; import * as React from 'react'; import { StopEvent, returnOne, returnZero } from '../../../ClientUtils'; -import { Doc, DocListCast, Opt } from '../../../fields/Doc'; -import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; +import { Doc, Opt } from '../../../fields/Doc'; +import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { DocumentType } from '../../documents/DocumentTypes'; import { DragManager } from '../../util/DragManager'; import { ContextMenu } from '../ContextMenu'; @@ -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)) { @@ -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 @@ -155,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 ( <DocumentView @@ -163,9 +164,11 @@ export class CollectionCarouselView extends CollectionSubView() { Document={doc} NativeWidth={returnZero} NativeHeight={returnZero} - fitWidth={undefined} + fitWidth={this._props.childLayoutFitWidth} + showTags={true} containerViewPath={this.childContainerViewPath} setContentViewBox={undefined} + ScreenToLocalTransform={this.childScreenToLocalXf} onDoubleClickScript={this.onContentDoubleClick} onClickScript={this.onContentClick} isDocumentActive={this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this._props.isContentActive} @@ -245,9 +248,6 @@ export class CollectionCarouselView extends CollectionSubView() { <div key="fwd" className="carouselView-fwd" onClick={this.advance}> <FontAwesomeIcon icon="chevron-right" size="2x" /> </div> - <div key="star" className="carouselView-star" onClick={this.star}> - <FontAwesomeIcon icon="star" color={TagItem.docHasTag(this.carouselItems?.[this.carouselIndex], this.starField) ? 'yellow' : 'gray'} size="1x" /> - </div> <div key="remove" className="carouselView-remove" onClick={e => this.setPracticeVal(e, practiceVal.MISSED)} style={{ visibility: this.layoutDoc.filterOp === cardMode.PRACTICE ? 'visible' : 'hidden' }}> <FontAwesomeIcon icon="xmark" color="red" size="1x" /> </div> diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index ccce2662a..0cc63d632 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -510,12 +510,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection e.stopPropagation(); break; case InkTool.SmartDraw: - setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, e => 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<Partial<collection }; @action - onEraserClick = (e: PointerEvent, doubleTap?: boolean) => { + 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)); }; @@ -857,13 +858,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection strokeToTVals.set(inkView, [Math.floor(inkData.length / 4) + 1]); } } - for (let i = 0; i < inkData.length - 3; i += 4) { // iterate over each segment of bezier curve for (let j = 0; j < eraserInkData.length - 3; j += 4) { const intersectCurve: Bezier = InkField.Segment(inkData, i); // other curve const eraserCurve: Bezier = InkField.Segment(eraserInkData, j); // eraser curve - this.bintersects(intersectCurve, eraserCurve).forEach((val: string | number, k: number) => { + 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) { @@ -1137,26 +1137,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return this.getClosestTs(tVals, excludeT, startIndex, mid - 1); }; - // 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 - 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); - }; - /** * 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. @@ -1188,7 +1168,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection if (apt.d !== undefined && apt.d < 1 && apt.t !== undefined && !tVals.includes(apt.t)) { tVals.push(apt.t); } - this.bintersects(curve, otherCurve).forEach((val: string | number, ival: number) => { + 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). @@ -1267,9 +1247,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection ); this._drawing.push(inkDoc); }); - const collection = MarqueeView.getCollection(this._drawing, undefined, true, { left: 1, top: 1, width: 1, height: 1 }); - console.log('is error here'); - return collection; + return MarqueeView.getCollection(this._drawing, undefined, true, { left: opts.x, top: opts.y, width: 1, height: 1 }); }; /** @@ -2033,6 +2011,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection !options && ContextMenu.Instance.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' }); const mores = ContextMenu.Instance.findByDescription('More...'); const moreItems = mores?.subitems ?? []; + moreItems.push({ + description: 'recognize all ink', + event: () => { + 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' }); }; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index f1dba58ce..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<SubCollectionViewProps return { left: NumCast(pinDoc._freeform_panX) - panelWidth / 2 / ps, top: NumCast(pinDoc._freeform_panY) - panelHeight / 2 / ps, width: panelWidth / ps, height: panelHeight / ps }; } + // eslint-disable-next-line no-use-before-define static Instance: MarqueeView; constructor(props: SubCollectionViewProps & MarqueeViewProps) { @@ -457,20 +458,19 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps let x_offset = 0; let y_offset = 0; let row_count = 0; + const newColDim = 900; for (const label of labelGroups) { - const newCollection = MarqueeView.getCollection([], undefined, false, { top: 1, left: 1, width: 1, height: 1 }); - newCollection._width = 900; - newCollection._height = 900; - newCollection._x = this.Bounds.left; - newCollection._y = this.Bounds.top; + const newCollection = MarqueeView.getCollection([], undefined, false, this.Bounds); + newCollection._x = this.Bounds.left + x_offset; + newCollection._y = this.Bounds.top + y_offset; + newCollection._width = newColDim; + newCollection._height = newColDim; newCollection._freeform_panX = this.Bounds.left + this.Bounds.width / 2; newCollection._freeform_panY = this.Bounds.top + this.Bounds.height / 2; - newCollection._x = (newCollection._x as number) + x_offset; - newCollection._y = (newCollection._y as number) + y_offset; - x_offset += (newCollection._width as number) + 40; + x_offset += newColDim + 40; row_count += 1; if (row_count == 3) { - y_offset += (newCollection._height as number) + 40; + y_offset += newColDim + 40; x_offset = 0; row_count = 0; } @@ -549,7 +549,6 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps }; touchesLine(r1: { left: number; top: number; width: number; height: number }) { - // eslint-disable-next-line no-restricted-syntax for (const lassoPt of this._lassoPts) { const topLeft = this.Transform.transformPoint(lassoPt[0], lassoPt[1]); if (r1.left < topLeft[0] && topLeft[0] < r1.left + r1.width && r1.top < topLeft[1] && topLeft[1] < r1.top + r1.height) { @@ -570,7 +569,6 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps let hasLeft = false; let hasBottom = false; let hasRight = false; - // eslint-disable-next-line no-restricted-syntax for (const lassoPt of this._lassoPts) { const truePoint = this.Transform.transformPoint(lassoPt[0], lassoPt[1]); hasLeft = hasLeft || (truePoint[0] > tl[0] && truePoint[0] < r1.left && truePoint[1] > r1.top && truePoint[1] < r1.top + r1.height); diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index c08d19a6b..0ab431740 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { Colors } from 'browndash-components'; -import { action, runInAction } from 'mobx'; -import { aggregateBounds } from '../../../Utils'; +import { runInAction } from 'mobx'; import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { InkTool } from '../../../fields/InkField'; @@ -15,10 +14,11 @@ import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SnappingManager } from '../../util/SnappingManager'; import { UndoManager, undoable } from '../../util/UndoManager'; import { GestureOverlay } from '../GestureOverlay'; +import { InkTranscription } from '../InkTranscription'; import { InkingStroke } from '../InkingStroke'; -import { CollectionFreeFormView, MarqueeView } from '../collections/collectionFreeForm'; import { MainView } from '../MainView'; import { PropertiesView } from '../PropertiesView'; +import { CollectionFreeFormView, MarqueeView } from '../collections/collectionFreeForm'; import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView'; import { ActiveEraserWidth, @@ -383,76 +383,11 @@ ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: return undefined; }); -export function createInkGroup(/* inksToGroup?: Doc[], isSubGroup?: boolean */) { - // TODO nda - if document being added to is a inkGrouping then we can just add to that group - if (Doc.ActiveTool === InkTool.Write) { - CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => { - // 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(tool: InkTool | Gestures, keepPrim: boolean, checkResult?: boolean) { - // InkTranscription.Instance?.createInkGroup(); +function setActiveTool(toolIn: InkTool | Gestures, keepPrim: boolean, checkResult?: boolean) { + InkTranscription.Instance?.createInkGroup(); + const tool = toolIn === InkTool.Eraser ? Doc.UserDoc().activeEraserTool : toolIn; if (checkResult) { - return (Doc.ActiveTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool + return ((Doc.ActiveTool === tool || (Doc.UserDoc().activeEraserTool === tool && (tool === toolIn || Doc.ActiveTool === tool))) && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool ? GestureOverlay.Instance?.KeepPrimitiveMode || ![Gestures.Circle, Gestures.Line, Gestures.Rectangle].includes(tool as Gestures) : false; } 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<LinkMenuItemProps> { 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, diff --git a/src/client/views/nodes/DiagramBox.scss b/src/client/views/nodes/DiagramBox.scss index 323638bff..8a7863c14 100644 --- a/src/client/views/nodes/DiagramBox.scss +++ b/src/client/views/nodes/DiagramBox.scss @@ -1,5 +1,3 @@ -$searchbarHeight: 50px; - .DIYNodeBox { width: 100%; height: 100%; @@ -23,7 +21,6 @@ $searchbarHeight: 50px; justify-content: center; align-items: center; width: 100%; - height: $searchbarHeight; padding: 10px; input[type='text'] { @@ -42,7 +39,6 @@ $searchbarHeight: 50px; justify-content: center; align-items: center; width: 100%; - height: calc(100% - $searchbarHeight); .diagramBox { flex: 1; display: flex; diff --git a/src/client/views/nodes/DiagramBox.tsx b/src/client/views/nodes/DiagramBox.tsx index 36deb2d8d..d6c9bb013 100644 --- a/src/client/views/nodes/DiagramBox.tsx +++ b/src/client/views/nodes/DiagramBox.tsx @@ -18,7 +18,9 @@ import { InkingStroke } from '../InkingStroke'; import './DiagramBox.scss'; import { FieldView, FieldViewProps } from './FieldView'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; - +/** + * this is a class for the diagram box doc type that can be found in the tools section of the side bar + */ @observer export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { public static LayoutString(fieldKey: string) { @@ -59,14 +61,21 @@ export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { { fireImmediately: true } ); } + /** + * helper method for renderMermaidAsync + * @param str string containing the mermaid code + * @returns + */ renderMermaid = (str: string) => { try { return mermaid.render('graph' + Date.now(), str); - } catch (error) { + } catch { return { svg: '', bindFunctions: undefined }; } }; - + /** + * will update the div containing the mermaid diagram to render the new mermaidCode + */ renderMermaidAsync = async (mermaidCode: string, dashDiv: HTMLDivElement) => { try { const { svg, bindFunctions } = await this.renderMermaid(mermaidCode); @@ -97,7 +106,9 @@ export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { res ); }, 'set mermaid code'); - + /** + * will generate mermaid code with GPT based on what the user requested + */ generateMermaidCode = action(() => { this._generating = true; const prompt = 'Write this in mermaid code and only give me the mermaid code: ' + this._inputValue; 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<DocumentViewProps>() { 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/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index cb0c4d188..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'; @@ -262,9 +261,10 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() { const tooltip: string = StrCast(this.Document.toolTip); const script = ScriptCast(this.Document.onClick)?.script; - const toggleStatus = script?.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result as boolean; + const toggleStatus = script?.run({ this: this.Document, value: undefined, _readOnly_: true }).result as boolean; // Colors const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string; + const background = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string; const items = DocListCast(this.dataDoc.data); const selectedItems = items.filter(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, value: undefined, _readOnly_: true }).result).map(item => StrCast(item.toolType)); return ( @@ -272,11 +272,11 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() { tooltip={`Toggle ${tooltip}`} type={Type.PRIM} color={color} + background={background === SnappingManager.userBackgroundColor ? undefined : background} multiSelect={true} onPointerDown={e => script && !toggleStatus && setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => script.run({ this: this.Document, value: undefined, _readOnly_: false }))} - isToggle={script ? true : false} + isToggle={false} toggleStatus={toggleStatus} - //background={SnappingManager.userBackgroundColor} label={this.label} items={items.map(item => ({ icon: <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={StrCast(item.icon) as IconProp} color={color} />, diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx index 8faf8ffa5..ddabd61e1 100644 --- a/src/client/views/nodes/IconTagBox.tsx +++ b/src/client/views/nodes/IconTagBox.tsx @@ -67,7 +67,7 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> { render() { const buttons = Doc.MyFilterHotKeys .map(key => ({ key, tag: StrCast(key.toolType) })) - .filter(({ tag }) => this._props.IsEditing || TagItem.docHasTag(this.View.Document, tag) || (DocumentView.Selected.length === 1 && this.View.IsSelected)) + .filter(({ tag }) => this._props.IsEditing || TagItem.docHasTag(this.View.Document, tag) || (DocumentView.Selected().length === 1 && this.View.IsSelected)) .map(({ key, tag }) => ( <Tooltip key={tag} title={<div className="dash-tooltip">Click to add/remove this card from the {tag} group</div>}> <button 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<FieldViewProps>() { }}> <CollectionFreeFormView ref={this._ffref} - // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} setContentViewBox={emptyFunction} NativeWidth={returnZero} diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 42ac51107..7ef431885 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -10,7 +10,7 @@ import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { ComputedField } from '../../../fields/ScriptField'; -import { Cast, FieldValue, ImageCast, NumCast, StrCast, toList } from '../../../fields/Types'; +import { Cast, FieldValue, NumCast, StrCast, toList } from '../../../fields/Types'; import { ImageField, PdfField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction } from '../../../Utils'; @@ -538,7 +538,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { return ComponentTag === CollectionStackingView ? ( <SidebarAnnos ref={this._sidebarRef} - // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} Document={this.Document} layoutDoc={this.layoutDoc} @@ -554,7 +553,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { ) : ( <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this._props.select(false), true)}> <ComponentTag - // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} setContentViewBox={emptyFunction} // override setContentView to do nothing NativeWidth={this.sidebarNativeWidthFunc} @@ -608,7 +606,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { top: 0, }}> <PDFViewer - // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} pdfBox={this} sidebarAddDoc={this.sidebarAddDocument} diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index fd1c791d3..6289470b6 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -149,7 +149,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>() componentDidMount() { this.dataDoc.nativeWidth = this.dataDoc.nativeHeight = 0; - this._props.setContentViewBox?.(this); // this tells the DocumentView that this Box is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. + this._props.setContentViewBox?.(this); // this tells the DocumentView that this ScreenshotBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. // this.layoutDoc.videoWall && reaction(() => ({ width: this._props.PanelWidth(), height: this._props.PanelHeight() }), // ({ width, height }) => { // if (this._camera) { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 84d3fc748..d3bc08bd3 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<FormattedTextB const container = DocCast(this.Document.embedContainer); const docView = DocumentView.getDocumentView?.(container); docView?.ComponentView?._props.addDocument?.(drawing); - drawing.x = NumCast(this.Document.x) + (this.Document.width as number); + drawing.x = NumCast(this.Document.x) + NumCast(this.Document.width); drawing.y = NumCast(this.Document.y); }; @@ -655,7 +655,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB textContent += node.child(i).textContent; i++; } - // eslint-disable-next-line no-cond-assign while (ep && (foundAt = textContent.slice(index).search(regexp)) > -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<FormattedTextB const hr = Math.round(Date.now() / 1000 / 60 / 60); numberRange(10).map(i => 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<FormattedTextB const nodes: Node[] = []; let hadStart = start !== 0; frag.forEach((node, index) => { - // 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<FormattedTextB action(selected => { 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<FormattedTextB const match = RTFCast(this.Document[this.fieldKey])?.Text.match(/^(@[a-zA-Z][a-zA-Z_0-9 -]*[a-zA-Z_0-9-]+)/); if (match) { this.dataDoc.title_custom = true; - // eslint-disable-next-line prefer-destructuring this.dataDoc.title = match[1]; // this triggers the collectionDockingView to publish this Doc this.EditorView?.dispatch(this.EditorView?.state.tr.deleteRange(0, match[1].length + 1)); } @@ -1793,7 +1788,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB const margins = 2 * NumCast(this.layoutDoc._yMargin, this._props.yPadding || 0); const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined; if (children && !SnappingManager.IsDragging) { - // eslint-disable-next-line no-use-before-define const getChildrenHeights = (kids: Element[] | undefined) => 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<FormattedTextB return ComponentTag === CollectionStackingView ? ( <SidebarAnnos ref={this._sidebarRef} - // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} Document={this.Document} layoutDoc={this.layoutDoc} @@ -1900,7 +1893,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB ) : ( <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => DocumentView.SelectView(this.DocumentView?.(), false), true)}> <ComponentTag - // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} ref={this._sidebarTagRef} setContentView={emptyFunction} diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 738f6d699..88e2e4248 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -1,6 +1,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; -import { action, computed, IReactionDisposer, makeObservable, observable } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { lift, toggleMark, wrapIn } from 'prosemirror-commands'; import { Mark, MarkType } from 'prosemirror-model'; @@ -68,10 +68,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { constructor(props: AntimodeMenuProps) { super(props); makeObservable(this); - RichTextMenu._instance.menu = this; - this.updateMenu(undefined, undefined, props, this.layoutDoc); - this._canFade = false; - this.Pinned = true; + runInAction(() => { + RichTextMenu._instance.menu = this; + this.updateMenu(undefined, undefined, props, this.layoutDoc); + this._canFade = false; + this.Pinned = true; + }); } @computed get noAutoLink() { @@ -695,7 +697,6 @@ interface RichTextMenuPluginProps { editorProps: FormattedTextBoxProps; } export class RichTextMenuPlugin extends React.Component<RichTextMenuPluginProps> { - // 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 20719442a..7a86ee802 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -430,7 +430,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { addDrawingAnnotation = (drawing: Doc) => { // drawing[DocData].x = this._props.pdfBox.ScreenToLocalBoxXf().TranslateX // const scaleX = this._mainCont.current.offsetWidth / boundingRect.width; - drawing.y = (drawing.y as number) + (this._props.Document.data_sidebar_panY as number); + drawing.y = NumCast(drawing.y) + NumCast(this._props.Document.layout_scrollTop); this._props.addDocument?.(drawing); }; @@ -504,7 +504,6 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { return ( <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this._props.Document), transform: `scale(${NumCast(this._props.layoutDoc._freeform_scale, 1)})` }} ref={this._annotationLayer}> {inlineAnnos.map(anno => ( - // eslint-disable-next-line react/jsx-props-no-spreading <Annotation {...this._props} fieldKey={this._props.fieldKey + '_annotations'} pointerEvents={this.pointerEvents} containerDataDoc={this._props.dataDoc} annoDoc={anno} key={`${anno[Id]}-annotation`} /> ))} </div> @@ -537,7 +536,6 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { pointerEvents: Doc.ActiveTool !== InkTool.None ? 'all' : undefined, }}> <CollectionFreeFormView - // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} NativeWidth={returnZero} NativeHeight={returnZero} diff --git a/src/client/views/smartdraw/AnnotationPalette.tsx b/src/client/views/smartdraw/AnnotationPalette.tsx index 0c8dbf12d..f1e2e4f41 100644 --- a/src/client/views/smartdraw/AnnotationPalette.tsx +++ b/src/client/views/smartdraw/AnnotationPalette.tsx @@ -10,10 +10,11 @@ import { returnEmptyFilter, returnFalse, returnTrue } from '../../../ClientUtils import { emptyFunction } from '../../../Utils'; import { Doc, DocListCast, returnEmptyDoclist } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; -import { ImageCast } from '../../../fields/Types'; +import { ImageCast, NumCast } from '../../../fields/Types'; +import { ImageField } from '../../../fields/URLField'; import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; -import { makeUserTemplateImage } from '../../util/DropConverter'; +import { makeUserTemplateButtonOrImage } from '../../util/DropConverter'; import { SettingsManager } from '../../util/SettingsManager'; import { Transform } from '../../util/Transform'; import { undoBatch } from '../../util/UndoManager'; @@ -23,8 +24,6 @@ import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView'; import { FieldView } from '../nodes/FieldView'; import './AnnotationPalette.scss'; import { DrawingOptions, SmartDrawHandler } from './SmartDrawHandler'; -import { ImageField } from '../../../fields/URLField'; -import { Copy } from '../../../fields/FieldSymbols'; interface AnnotationPaletteProps { Document: Doc; @@ -123,7 +122,7 @@ export class AnnotationPalette extends ObservableReactComponent<AnnotationPalett const { clone } = await Doc.MakeClone(doc); clone.title = doc.title; const image = ImageCast(doc.icon, ImageCast(clone[Doc.LayoutFieldKey(clone)]))?.url?.href; - Doc.AddDocToList(Doc.MyAnnos, 'data', makeUserTemplateImage(clone, image)); + Doc.AddDocToList(Doc.MyAnnos, 'data', makeUserTemplateButtonOrImage(clone, image)); doc.savedAsAnno = true; } }; @@ -158,7 +157,6 @@ export class AnnotationPalette extends ObservableReactComponent<AnnotationPalett console.log('Error generating drawing', e); } } - // eslint-disable-next-line @typescript-eslint/no-unused-expressions this._opts.text !== '' ? (this._opts.text = `${this._opts.text} ~~~ ${this._userInput}`) : (this._opts.text = this._userInput); this._userInput = ''; this._isLoading = false; @@ -178,7 +176,7 @@ export class AnnotationPalette extends ObservableReactComponent<AnnotationPalett * presses the "save drawing" button. */ saveDrawing = async () => { - const cIndex: number = this._props.Document.carousel_index as number; + const cIndex = NumCast(this._props.Document.carousel_index); const focusedDrawing = DocListCast(this._props.Document.data)[cIndex]; const docData = focusedDrawing[DocData]; docData.title = this._opts.text.match(/^(.*?)~~~.*$/)?.[1] || this._opts.text; diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx index e362c0c89..75ef55060 100644 --- a/src/client/views/smartdraw/SmartDrawHandler.tsx +++ b/src/client/views/smartdraw/SmartDrawHandler.tsx @@ -1,7 +1,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Slider, Switch } from '@mui/material'; import { Button, IconButton } from 'browndash-components'; -import { action, makeObservable, observable } from 'mobx'; +import { action, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import React from 'react'; import { AiOutlineSend } from 'react-icons/ai'; @@ -50,6 +50,7 @@ export interface DrawingOptions { @observer export class SmartDrawHandler extends ObservableReactComponent<object> { + // 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 }; @@ -148,15 +149,17 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { */ @action hideSmartDrawHandler = () => { - this.ShowRegenerate = false; - this._display = false; - this._isLoading = false; - this._showOptions = false; - this._userInput = ''; - this._complexity = 5; - this._size = 350; - this._autoColor = true; - Doc.ActiveTool = InkTool.None; + if (this._display) { + this.ShowRegenerate = false; + this._display = false; + this._isLoading = false; + this._showOptions = false; + this._userInput = ''; + this._complexity = 5; + this._size = 350; + this._autoColor = true; + Doc.ActiveTool = InkTool.None; + } }; /** @@ -192,14 +195,21 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { 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); @@ -210,8 +220,10 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { } } } - this._isLoading = false; - this._canInteract = true; + runInAction(() => { + this._isLoading = false; + this._canInteract = true; + }); }; /** @@ -226,9 +238,8 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { 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; @@ -259,7 +270,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { } 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) { @@ -385,7 +396,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { defaultChecked={true} value={this._autoColor} size="small" - onChange={action(e => this._canInteract && (this._autoColor = !this._autoColor))} + onChange={action(() => this._canInteract && (this._autoColor = !this._autoColor))} /> </div> <div className="complexity"> diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts index 123d32301..17b99b033 100644 --- a/src/fields/InkField.ts +++ b/src/fields/InkField.ts @@ -14,6 +14,7 @@ export enum InkTool { StrokeEraser = 'strokeeraser', SegmentEraser = 'segmenteraser', RadiusEraser = 'radiuseraser', + Eraser = 'eraser', // not a real tool, but a class of tools Stamp = 'stamp', Write = 'write', PresentationPin = 'presentationpin', @@ -103,6 +104,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); 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(); diff --git a/src/pen-gestures/GestureTypes.ts b/src/pen-gestures/GestureTypes.ts index 2e1c9d16f..5a8e9bd97 100644 --- a/src/pen-gestures/GestureTypes.ts +++ b/src/pen-gestures/GestureTypes.ts @@ -1,7 +1,6 @@ export enum Gestures { Line = 'line', Stroke = 'stroke', - Scribble = 'scribble', Text = 'text', Triangle = 'triangle', Circle = 'circle', diff --git a/src/pen-gestures/ndollar.ts b/src/pen-gestures/ndollar.ts index 31a4eb0e8..04262b61f 100644 --- a/src/pen-gestures/ndollar.ts +++ b/src/pen-gestures/ndollar.ts @@ -263,75 +263,7 @@ export class NDollarRecognizer { ]) ) ); - this.Multistrokes.push( - new Multistroke( - Gestures.Scribble, - useBoundedRotationInvariance, - new Array([ - new Point(232.43359374999994, 428.3789062499997), - new Point(232.43359374999994, 382.0359155790783), - new Point(265.26320387389393, 340.59025405258205), - new Point(285.92968749999994, 300.6953124999997), - new Point(285.92968749999994, 300.6953124999997), - new Point(290.29904726504463, 292.260623967478), - new Point(294.85514947688137, 276.5586112000384), - new Point(301.80468749999994, 270.1054687499997), - new Point(301.80468749999994, 270.1054687499997), - new Point(303.30831790356257, 268.7092405181201), - new Point(302.3387333271824, 274.174445118092), - new Point(302.58984374999994, 276.2109374999997), - new Point(302.58984374999994, 276.2109374999997), - new Point(303.5898583815426, 284.3210038050238), - new Point(306.34100306922323, 291.42526150092345), - new Point(308.55078124999994, 299.2578124999997), - new Point(308.55078124999994, 299.2578124999997), - new Point(314.02357444430737, 318.65610875195364), - new Point(312.83820955386796, 338.9193216781303), - new Point(317.41015624999994, 358.4374999999997), - new Point(317.41015624999994, 358.4374999999997), - new Point(324.6448511447705, 389.323263646351), - new Point(341.2272901550544, 419.08257022687917), - new Point(351.55468749999994, 449.3085937499997), - new Point(351.55468749999994, 449.3085937499997), - new Point(354.4485190828321, 457.7782031368645), - new Point(359.85272292551673, 488.59621490807643), - new Point(368.55859374999994, 492.7578124999997), - new Point(368.55859374999994, 492.7578124999997), - new Point(368.9336613544369, 492.9371030581646), - new Point(375.30018285197116, 475.54269522741924), - new Point(385.01171874999994, 460.68749999999966), - new Point(385.01171874999994, 460.68749999999966), - new Point(409.01141338031505, 423.9765046563952), - new Point(465.73402430232653, 338.122252279478), - new Point(470.49609374999994, 336.8945312499997), - new Point(470.49609374999994, 336.8945312499997), - new Point(472.34967396580504, 336.41665510061245), - new Point(470.5375229924171, 340.7226912215484), - new Point(470.56249999999994, 342.6367187499997), - new Point(470.56249999999994, 342.6367187499997), - new Point(470.6277554579174, 347.63734752514455), - new Point(471.4666087447205, 352.597933700578), - new Point(472.79687499999994, 357.4218749999997), - new Point(472.79687499999994, 357.4218749999997), - new Point(478.7003095537336, 378.82948542322754), - new Point(492.1754046938961, 397.52358353298115), - new Point(497.80468749999994, 418.6796874999997), - new Point(497.80468749999994, 418.6796874999997), - new Point(499.2975220287686, 424.29009358262533), - new Point(498.6576561843205, 452.5736061983319), - new Point(502.21874999999994, 455.3749999999997), - new Point(502.21874999999994, 455.3749999999997), - new Point(502.3599404176782, 455.4860697952399), - new Point(526.9859878583989, 412.7902963819643), - new Point(528.28515625, 410.5507812499997), - new Point(528.28515625, 410.5507812499997), - new Point(534.3674131478522, 400.0661462732759), - new Point(586.26953125, 311.2520380124085), - new Point(586.26953125, 306.8515624999997), - ]) - ) - ); - this.Multistrokes.push(new Multistroke(Gestures.RightAngle, useBoundedRotationInvariance, new Array([new Point(200, 0), new Point(0, 0), new Point(0, 100)]))); + this.Multistrokes.push(new Multistroke(Gestures.RightAngle, useBoundedRotationInvariance, new Array([new Point(0, 0), new Point(0, 100), new Point(200, 100)]))); NumMultistrokes = this.Multistrokes.length; // NumMultistrokes flags the end of the non user-defined gstures strokes // // PREDEFINED STROKES |