aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--eslint.config.mjs2
-rw-r--r--package-lock.json128
-rw-r--r--package.json6
-rw-r--r--src/Utils.ts3
-rw-r--r--src/client/documents/Documents.ts4
-rw-r--r--src/client/util/CurrentUserUtils.ts26
-rw-r--r--src/client/util/DropConverter.ts39
-rw-r--r--src/client/util/Scripting.ts9
-rw-r--r--src/client/views/ContextMenu.tsx2
-rw-r--r--src/client/views/DocumentButtonBar.tsx1
-rw-r--r--src/client/views/DocumentDecorations.tsx25
-rw-r--r--src/client/views/GestureOverlay.tsx319
-rw-r--r--src/client/views/InkTranscription.scss8
-rw-r--r--src/client/views/InkTranscription.tsx115
-rw-r--r--src/client/views/InkingStroke.tsx26
-rw-r--r--src/client/views/LightboxView.tsx2
-rw-r--r--src/client/views/Main.tsx1
-rw-r--r--src/client/views/MainView.tsx20
-rw-r--r--src/client/views/PropertiesView.tsx44
-rw-r--r--src/client/views/collections/CollectionCardDeckView.tsx85
-rw-r--r--src/client/views/collections/CollectionCarousel3DView.tsx37
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx16
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx48
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx20
-rw-r--r--src/client/views/global/globalScripts.ts79
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx2
-rw-r--r--src/client/views/nodes/DiagramBox.scss4
-rw-r--r--src/client/views/nodes/DiagramBox.tsx19
-rw-r--r--src/client/views/nodes/DocumentView.tsx4
-rw-r--r--src/client/views/nodes/FontIconBox/FontIconBox.tsx8
-rw-r--r--src/client/views/nodes/IconTagBox.tsx2
-rw-r--r--src/client/views/nodes/ImageBox.tsx1
-rw-r--r--src/client/views/nodes/PDFBox.tsx5
-rw-r--r--src/client/views/nodes/ScreenshotBox.tsx2
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx10
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx13
-rw-r--r--src/client/views/pdf/PDFViewer.tsx4
-rw-r--r--src/client/views/smartdraw/AnnotationPalette.tsx12
-rw-r--r--src/client/views/smartdraw/SmartDrawHandler.tsx51
-rw-r--r--src/fields/InkField.ts21
-rw-r--r--src/fields/SchemaHeaderField.ts1
-rw-r--r--src/pen-gestures/GestureTypes.ts1
-rw-r--r--src/pen-gestures/ndollar.ts70
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