From 63f9f9573dab9745d2f570e1917b325a474fdd93 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 28 Oct 2024 20:07:51 -0400 Subject: reworked LabelBox's to be fully editable. gor rid of singleLine for label boxes. made a text_fitBox field to render text with a labelBox. fixed styleprovider in freeformview clusters to be untracked() to avoid invalidations. added a default text color to Settings panel. fixed clicking text buttons to not lose focus in labelBox. --- src/client/documents/DocUtils.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/client/documents/DocUtils.ts') diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts index 19f3c89ef..e3cb5e72c 100644 --- a/src/client/documents/DocUtils.ts +++ b/src/client/documents/DocUtils.ts @@ -679,10 +679,13 @@ export namespace DocUtils { ? {} // if the new doc will inherit from a template, don't set any layout fields since that would block the inheritance : { _width: width || 200, - _height: 35, + _height: BoolCast(Doc.UserDoc().fitBox) ? 70 : 35, _layout_centered: BoolCast(Doc.UserDoc()._layout_centered), _layout_fitWidth: true, _layout_autoHeight: true, + text_fitBox: BoolCast(Doc.UserDoc().fitBox), + text_align: StrCast(Doc.UserDoc().textAlign), + text_fontColor: StrCast(Doc.UserDoc().fontColor), }), }); -- cgit v1.2.3-70-g09d2 From 1a444532d026a208817c6997f00022a3e09ead23 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 29 Oct 2024 13:39:16 -0400 Subject: fixes to allow typing and drawing without a mode switch. --- src/client/documents/DocUtils.ts | 4 ++-- src/client/views/GestureOverlay.tsx | 8 +++++++- src/client/views/nodes/formattedText/RichTextMenu.tsx | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) (limited to 'src/client/documents/DocUtils.ts') diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts index e3cb5e72c..dae3e48ad 100644 --- a/src/client/documents/DocUtils.ts +++ b/src/client/documents/DocUtils.ts @@ -678,8 +678,8 @@ export namespace DocUtils { ...(defaultTextTemplate ? {} // if the new doc will inherit from a template, don't set any layout fields since that would block the inheritance : { - _width: width || 200, - _height: BoolCast(Doc.UserDoc().fitBox) ? 70 : 35, + _width: width || BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 * 6 : 200, + _height: BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 : 35, _layout_centered: BoolCast(Doc.UserDoc()._layout_centered), _layout_fitWidth: true, _layout_autoHeight: true, diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index afeecaa63..402a7c20a 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -95,6 +95,8 @@ export class GestureOverlay extends ObservableReactComponent { + DocumentView.DeselectAll(); + (document.activeElement as HTMLElement)?.blur(); if (!(e.target as HTMLElement)?.className?.toString().startsWith('lm_')) { if ([InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) { this._points.push({ X: e.clientX, Y: e.clientY }); @@ -244,8 +246,9 @@ export class GestureOverlay extends ObservableReactComponent { + onPointerUp = (e: PointerEvent) => { const ffView = DocumentView.DownDocView?.ComponentView instanceof CollectionFreeFormView && DocumentView.DownDocView.ComponentView; + const downView = DocumentView.DownDocView; DocumentView.DownDocView = undefined; if (this._points.length > 1) { const B = this.svgBounds; @@ -286,6 +289,9 @@ export class GestureOverlay extends ObservableReactComponent { this.view.focus(); } else { Doc.UserDoc()[fontField] = value; + this.updateMenu(undefined, undefined, undefined, this.dataDoc); } }; -- cgit v1.2.3-70-g09d2 From 4c768162e0436115a05b9c8b0e4d837d626d45ba Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 30 Oct 2024 18:54:52 -0400 Subject: reworked how context menu buttons for ink and text work. added disableMixBlend for making transparent docs not use 'multiply'. --- src/ClientUtils.ts | 11 +- src/client/documents/DocUtils.ts | 16 +- src/client/documents/Documents.ts | 1 - src/client/util/CurrentUserUtils.ts | 78 +++++---- src/client/util/InteractionUtils.tsx | 6 +- src/client/util/SettingsManager.tsx | 4 +- src/client/util/SnappingManager.ts | 7 + src/client/views/FilterPanel.tsx | 2 +- src/client/views/GestureOverlay.tsx | 164 ++++++------------ src/client/views/GlobalKeyHandler.ts | 9 +- src/client/views/InkingStroke.tsx | 3 +- src/client/views/LightboxView.tsx | 4 +- src/client/views/StyleProp.ts | 4 +- src/client/views/StyleProvider.tsx | 5 +- .../views/collections/CollectionDockingView.tsx | 2 +- .../CollectionFreeFormInfoUI.tsx | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 71 ++++---- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/client/views/global/globalScripts.ts | 112 +++++++------ .../views/newlightbox/ButtonMenu/ButtonMenu.tsx | 6 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 5 +- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 72 ++++---- .../views/nodes/FontIconBox/FontIconBox.scss | 3 + src/client/views/nodes/FontIconBox/FontIconBox.tsx | 186 ++++++++++++--------- src/client/views/nodes/ImageBox.tsx | 10 +- src/client/views/nodes/VideoBox.tsx | 2 +- src/client/views/nodes/WebBox.tsx | 13 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 12 +- .../views/nodes/formattedText/RichTextMenu.tsx | 22 +-- src/client/views/pdf/PDFViewer.tsx | 2 +- src/client/views/smartdraw/SmartDrawHandler.tsx | 12 +- src/fields/Doc.ts | 6 +- src/fields/InkField.ts | 27 +-- src/pen-gestures/GestureTypes.ts | 1 - 35 files changed, 444 insertions(+), 446 deletions(-) (limited to 'src/client/documents/DocUtils.ts') diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts index e7aee1c2a..baad9a06c 100644 --- a/src/ClientUtils.ts +++ b/src/ClientUtils.ts @@ -107,12 +107,12 @@ export namespace ClientUtils { default: return type.charAt(0).toUpperCase() + type.substring(1,3); } // prettier-ignore } - export function cleanDocumentType(type: DocumentType, colType: CollectionViewType) { + export function cleanDocumentType(type: DocumentType, colType?: CollectionViewType) { switch (type) { case DocumentType.PDF: return 'PDF'; case DocumentType.IMG: return 'Image'; case DocumentType.AUDIO: return 'Audio'; - case DocumentType.COL: return 'Collection:'+colType; + case DocumentType.COL: return 'Collection:'+ (colType ?? ""); case DocumentType.RTF: return 'Text'; default: return type.charAt(0).toUpperCase() + type.slice(1); } // prettier-ignore @@ -212,7 +212,7 @@ export namespace ClientUtils { return { r: r, g: g, b: b, a: a }; } - const isTransparentFunctionHack = 'isTransparent(__value__)'; + export const isTransparentFunctionHack = 'isTransparent(__value__)'; export const noRecursionHack = '__noRecursion'; // special case filters @@ -223,11 +223,6 @@ export namespace ClientUtils { export function IsRecursiveFilter(val: string) { return !val.includes(noRecursionHack); } - export function HasFunctionFilter(val: string) { - if (val.includes(isTransparentFunctionHack)) return (color: string) => color !== '' && DashColor(color).alpha() !== 1; - // add other function filters here... - return undefined; - } export function toRGBAstr(col: { r: number; g: number; b: number; a?: number }) { return 'rgba(' + col.r + ',' + col.g + ',' + col.b + (col.a !== undefined ? ',' + col.a : '') + ')'; diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts index dae3e48ad..067b9c5e0 100644 --- a/src/client/documents/DocUtils.ts +++ b/src/client/documents/DocUtils.ts @@ -3,7 +3,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { saveAs } from 'file-saver'; import * as JSZip from 'jszip'; import { action, runInAction } from 'mobx'; -import { ClientUtils } from '../../ClientUtils'; +import { ClientUtils, DashColor } from '../../ClientUtils'; import * as JSZipUtils from '../../JSZipUtils'; import { decycle } from '../../decycler/decycler'; import { DateField } from '../../fields/DateField'; @@ -36,11 +36,16 @@ import { DocumentView } from '../views/nodes/DocumentView'; import { CollectionFreeFormView } from '../views/collections/collectionFreeForm'; export namespace DocUtils { + function HasFunctionFilter(val: string) { + if (val.includes(ClientUtils.isTransparentFunctionHack)) return (d: Doc, color: string) => !d.disableMixBlend && color !== '' && DashColor(color).alpha() !== 1; + // add other function filters here... + return undefined; + } function matchFieldValue(doc: Doc, key: string, valueIn: unknown): boolean { let value = valueIn; - const hasFunctionFilter = ClientUtils.HasFunctionFilter(value as string); + const hasFunctionFilter = HasFunctionFilter(value as string); if (hasFunctionFilter) { - return hasFunctionFilter(StrCast(doc[key])); + return hasFunctionFilter(doc, StrCast(doc[key])); } if (key === LinkedTo) { // links are not a field value, so handled here. value is an expression of form ([field=]idToDoc("...")) @@ -683,9 +688,14 @@ export namespace DocUtils { _layout_centered: BoolCast(Doc.UserDoc()._layout_centered), _layout_fitWidth: true, _layout_autoHeight: true, + backgroundColor: StrCast(Doc.UserDoc().textBackgroundColor), text_fitBox: BoolCast(Doc.UserDoc().fitBox), text_align: StrCast(Doc.UserDoc().textAlign), text_fontColor: StrCast(Doc.UserDoc().fontColor), + text_fontFamily: StrCast(Doc.UserDoc().fontFamily), + text_fontWeight: StrCast(Doc.UserDoc().fontWeight), + text_fontStyle: StrCast(Doc.UserDoc().fontStyle), + text_fontDecoration: StrCast(Doc.UserDoc().fontDecoration), }), }); diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index b0e1a7545..efd6ce3c9 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -927,7 +927,6 @@ export namespace Docs { I.text_align = 'center'; I.rotation = 0; I.defaultDoubleClick = 'ignore'; - I.keepZWhenDragged = true; I.author_date = new DateField(); I.acl_Guest = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View; // I.acl_Override = SharingPermissions.Unset; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index b3aa5e145..98a3e8914 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -4,7 +4,7 @@ import * as rp from 'request-promise'; import { ClientUtils, OmitKeys } from "../../ClientUtils"; import { Doc, DocListCast, DocListCastAsync, FieldType, Opt } from "../../fields/Doc"; import { DocData } from "../../fields/DocSymbols"; -import { InkTool } from "../../fields/InkField"; +import { InkEraserTool, InkInkTool, InkTool } from "../../fields/InkField"; import { List } from "../../fields/List"; import { PrefetchProxy } from "../../fields/Proxy"; import { RichTextField } from "../../fields/RichTextField"; @@ -39,6 +39,7 @@ import { SelectionManager } from "./SelectionManager"; import { ColorScheme } from "./SettingsManager"; import { SnappingManager } from "./SnappingManager"; import { UndoManager } from "./UndoManager"; +import { DocumentView } from "../views/nodes/DocumentView"; export interface Button { // DocumentOptions fields a button can set @@ -731,11 +732,11 @@ pie title Minerals in my tap water return [ { title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, toolType:"font", ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'}, btnList: new List(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) }, - { title: "Font Size",toolTip: "Font size (%size)", btnType: ButtonType.NumberDropdownButton, toolType:"fontSize", ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 9 }, + { title: " Size", toolTip: "Font size (%size)", btnType: ButtonType.NumberDropdownButton, toolType:"fontSize", ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 9 }, { title: "Color", toolTip: "Font color (%color)", btnType: ButtonType.ColorButton, icon: "font", toolType:"fontColor",ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'} }, { title: "Highlight",toolTip: "Font highlight", btnType: ButtonType.ColorButton, icon: "highlighter", toolType:"highlight",ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'} }, { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", toolType:"bold", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} }, - { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", toolType:"italics", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} }, + { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", toolType:"italic", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} }, { title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", toolType:"underline",ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} }, { 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_);}'} }, @@ -758,24 +759,27 @@ 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, toolType: InkTool.Eraser, scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }, + { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle", toolType: Gestures.Circle, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} }, + { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square", toolType: Gestures.Rectangle, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} }, + { title: "Line", toolTip: "Line (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "minus", toolType: Gestures.Line, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} }, + { title: "Ink", toolTip: "Ink", btnType: ButtonType.MultiToggleButton, toolType: InkTool.Ink, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }, + subMenu: [ + { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", toolType: InkInkTool.Pen, ignoreClick: true, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }}, + { title: "Highlight",toolTip: "Highlight (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", toolType: InkInkTool.Highlight, ignoreClick: true, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }}, + { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", toolType: InkInkTool.Write, ignoreClick: true, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }}, + ]}, + { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberSliderButton, toolType: "strokeWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"!activeInkTool()"}, numBtnMin: 1, linearBtnWidth:40}, + { title: "Color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", toolType: "strokeColor", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"!activeInkTool()"}}, + { 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: "Radius", toolTip: "Radius Erase", btnType: ButtonType.ToggleButton, icon: "circle-xmark",toolType:InkTool.RadiusEraser, ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'} }, + { title: "Stroke", toolTip: "Eraser complete strokes",btnType: ButtonType.ToggleButton, icon: "eraser", toolType:InkEraserTool.Stroke, ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'}}, + { title: "Segment", toolTip: "Erase segments up to intersections",btnType: ButtonType.ToggleButton,icon: "xmark",toolType:InkEraserTool.Segment,ignoreClick:true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'}}, + { title: "Area", toolTip: "Erase like a pencil", btnType: ButtonType.ToggleButton, icon: "circle-xmark",toolType:InkEraserTool.Radius, 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()"}}, - { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle", toolType: Gestures.Circle, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} }, - { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square", toolType: Gestures.Rectangle, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} }, - { title: "Line", toolTip: "Line (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "minus", toolType: Gestures.Line, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} }, - { title: "Mask", toolTip: "Mask", btnType: ButtonType.ToggleButton, icon: "user-circle",toolType: "inkMask", scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"IsNoviceMode()" } }, - { title: "Labels", toolTip: "Labels", btnType: ButtonType.ToggleButton, icon: "text-width", toolType: "labels", scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, }, - { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberSliderButton, toolType: "strokeWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, numBtnMin: 1}, - { title: "Ink", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", toolType: "strokeColor", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'} }, - { title: "Smart Draw", toolTip: "Draw with GPT", btnType: ButtonType.ToggleButton, icon: "user-pen", toolType: "smartdraw", scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}'}, funcs: {hidden: "IsNoviceMode()"}}, + { title: " Size", toolTip: "Size of area pencil eraser", btnType: ButtonType.NumberSliderButton, toolType: "eraserWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"NotRadiusEraser()"}, numBtnMin: 1, linearBtnWidth:40}, + { title: "Mask", toolTip: "Make Stroke a Stencil Mask", btnType: ButtonType.ToggleButton, icon: "user-circle",toolType: "inkMask", scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"IsNoviceMode()" } }, + { title: "Labels", toolTip: "Show Labels Inside Shapes", btnType: ButtonType.ToggleButton, icon: "text-width", toolType: "labels", scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}}, + { title: "Smart Draw", toolTip: "Draw with GPT", btnType: ButtonType.ToggleButton, icon: "user-pen", toolType: "smartdraw", scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}'}, funcs: {hidden: "IsNoviceMode()"}}, ]; } @@ -814,11 +818,11 @@ pie title Minerals in my tap water { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, expertMode: false, width: 30, scripts: { onClick: 'pinWithView(altKey)'}, funcs: {hidden: "IsNoneSelected()"}}, { title: "Header", icon: "heading", toolTip: "Doc Titlebar Color", btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'} }, { title: "Template",icon: "scroll", toolTip: "Default Note Template",btnType: ButtonType.ToggleButton, expertMode: false, toolType:DocumentType.RTF, scripts: { onClick: '{ return setDefaultTemplate(_readOnly_); }'} }, - { title: "Fill", icon: "fill-drip", toolTip: "Fill/Background Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 30, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}}, // Only when a document is selected - { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode, true)'}, scripts: { onClick: '{ return toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform - { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, width: 30, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}}, + { title: "Fill", icon: "fill-drip", toolTip: "Fill/Background Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 30, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'} }, // Only when a document is selected + { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode, true)'}, scripts: { onClick: '{ return toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform + { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, width: 30, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}}, { 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: "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 @@ -863,7 +867,7 @@ pie title Minerals in my tap water } // linear view const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, undoIgnoreFields: new List(['width', "linearView_IsOpen"]), - childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: !params.scripts?.onClick, + childDontRegisterViews: true, flexGap: 0, _height: 30, _width: 30, ignoreClick: !params.scripts?.onClick, linearView_SubMenu: true, linearView_Expandable: true, embedContainer: menuDoc}; const items = (menutBtn?:Doc) => !menutBtn ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menutBtn) ); @@ -987,15 +991,23 @@ pie title Minerals in my tap water Doc.noviceMode ?? (Doc.noviceMode = true); doc._showLabel ?? (doc._showLabel = true); doc.textAlign ?? (doc.textAlign = "left"); + doc.textBackgroundColor ?? (doc.textBackgroundColor = Colors.LIGHT_GRAY); doc.activeTool = InkTool.None; doc.openInkInLightbox ?? (doc.openInkInLightbox = false); - doc.activeInkHideTextLabels ?? (doc.activeInkHideTextLabels = false); - doc.activeInkColor ?? (doc.activeInkColor = "rgb(0, 0, 0)"); - doc.activeInkWidth ?? (doc.activeInkWidth = 1); - doc.activeInkBezier ?? (doc.activeInkBezier = "0"); - doc.activeFillColor ?? (doc.activeFillColor = ""); - doc.activeArrowStart ?? (doc.activeArrowStart = ""); - doc.activeArrowEnd ?? (doc.activeArrowEnd = ""); + doc.activeHideTextLabels ?? (doc.activeHideTextLabels = false); + doc[`active${InkInkTool.Pen}Color`] ?? (doc[`active${InkInkTool.Pen}Color`] = "rgb(0, 0, 0)"); + doc[`active${InkInkTool.Pen}Width`] ?? (doc[`active${InkInkTool.Pen}Width`] = 2); + doc[`active${InkInkTool.Pen}Bezier`] ?? (doc[`active${InkInkTool.Pen}Bezier`] = "0"); + doc[`active${InkInkTool.Write}Color`] ?? (doc[`active${InkInkTool.Write}Color`] = "rgb(255, 0, 0)"); + doc[`active${InkInkTool.Write}Width`] ?? (doc[`active${InkInkTool.Write}Width`] = 1); + doc[`active${InkInkTool.Write}Bezier`] ?? (doc[`active${InkInkTool.Write}Bezier`] = "0"); + doc[`active${InkInkTool.Highlight}Color`] ?? (doc[`active${InkInkTool.Highlight}Color`] = 'transparent'); + doc[`active${InkInkTool.Highlight}Width`] ?? (doc[`active${InkInkTool.Highlight}Width`] = 20); + doc[`active${InkInkTool.Highlight}Bezier`] ?? (doc[`active${InkInkTool.Highlight}Bezier`] = "0"); + doc[`active${InkInkTool.Highlight}Fill`] ?? (doc[`active${InkInkTool.Highlight}Fill`] = "rgba(0, 255, 255, 0.4)"); + doc.activeInkTool ?? (doc.activeInkTool = InkInkTool.Pen); + doc.activeEraserTool ?? (doc.activeEraserTool = InkEraserTool.Stroke); + doc.activeEraserWidth ?? (doc.activeEraserWidth = 20); doc.activeDash ?? (doc.activeDash === "0"); doc.fontSize ?? (doc.fontSize = "12px"); doc.fontFamily ?? (doc.fontFamily = "Arial"); @@ -1130,7 +1142,9 @@ pie title Minerals in my tap water } // eslint-disable-next-line prefer-arrow-callback -ScriptingGlobals.add(function NotRadiusEraser() { return Doc.ActiveTool !== InkTool.RadiusEraser; }, "is the active tool anything but the radius eraser"); +ScriptingGlobals.add(function activeInkTool() { return Doc.ActiveTool=== InkTool.Ink || DocumentView.Selected().some(dv => dv.layoutDoc.layout_isSvg); }, "is a pen tool or an ink stroke active"); +// eslint-disable-next-line prefer-arrow-callback +ScriptingGlobals.add(function NotRadiusEraser() { return Doc.ActiveTool !== InkTool.Eraser || Doc.ActiveEraser !== InkEraserTool.Radius; }, "is the active tool anything but the radius eraser"); // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function MySharedDocs() { return Doc.MySharedDocs; }, "document containing all shared Docs"); // eslint-disable-next-line prefer-arrow-callback diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 4231c2ca8..ca1cb8014 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -109,7 +109,7 @@ export namespace InteractionUtils { dash: string | undefined, scalexIn: number, scaleyIn: number, - shape: Gestures, + shape: Gestures | undefined, pevents: Property.PointerEvents, opacity: number, nodefs: boolean, @@ -136,7 +136,7 @@ export namespace InteractionUtils { const arrowLengthFactor = 5 * (markerScale || 0.5); const arrowNotchFactor = 2 * (markerScale || 0.5); return ( - + {' '} {/* setting the svg fill sets the arrowStart fill */} {nodefs ? null : ( @@ -186,7 +186,7 @@ export namespace InteractionUtils { opacity: 1.0, // opacity: strokeWidth !== width ? 0.5 : undefined, pointerEvents: pevents === 'all' ? 'visiblePainted' : pevents, - stroke: color ?? 'rgb(0, 0, 0)', + stroke: (color ?? 'rgb(0, 0, 0)') || 'transparent', strokeWidth, strokeLinecap: strokeLineCap, strokeDasharray: dashArray, diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index 33d7d90a8..628097ca8 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -268,9 +268,9 @@ export class SettingsManager extends React.Component { formLabelPlacement="right" toggleType={ToggleType.SWITCH} onClick={() => { - Doc.UserDoc().activeInkHideTextLabels = !Doc.UserDoc().activeInkHideTextLabels; + Doc.UserDoc().activeHideTextLabels = !Doc.UserDoc().activeHideTextLabels; }} - toggleStatus={BoolCast(Doc.UserDoc().activeInkHideTextLabels)} + toggleStatus={BoolCast(Doc.UserDoc().activeHideTextLabels)} size={Size.XSMALL} color={SettingsManager.userColor} /> diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts index 5f6c7d9ac..2a150dc5a 100644 --- a/src/client/util/SnappingManager.ts +++ b/src/client/util/SnappingManager.ts @@ -1,4 +1,5 @@ import { observable, action, runInAction, makeObservable } from 'mobx'; +import { Gestures } from '../../pen-gestures/GestureTypes'; export enum freeformScrollMode { Pan = 'pan', @@ -29,6 +30,8 @@ export class SnappingManager { @observable _propertyWid: number = 0; @observable _printToConsole: boolean = false; @observable _hideDecorations: boolean = false; + @observable _keepGestureMode: boolean = false; // for whether primitive selection enters a one-shot or persistent mode + @observable _inkShape: Gestures | undefined = undefined; private constructor() { SnappingManager._manager = this; @@ -61,6 +64,8 @@ export class SnappingManager { public static get PropertiesWidth(){ return this.Instance._propertyWid; } // prettier-ignore public static get PrintToConsole() { return this.Instance._printToConsole; } // prettier-ignore public static get HideDecorations(){ return this.Instance._hideDecorations; } // prettier-ignore + public static get KeepGestureMode(){ return this.Instance._keepGestureMode; } // prettier-ignore + public static get InkShape() { return this.Instance._inkShape; } // prettier-ignore public static SetLongPress = (press: boolean) => runInAction(() => {this.Instance._longPress = press}); // prettier-ignore public static SetShiftKey = (down: boolean) => runInAction(() => {this.Instance._shiftKey = down}); // prettier-ignore @@ -78,6 +83,8 @@ export class SnappingManager { public static SetPropertiesWidth= (wid:number) =>runInAction(() => {this.Instance._propertyWid = wid}); // prettier-ignore public static SetPrintToConsole = (state:boolean) =>runInAction(() => {this.Instance._printToConsole = state}); // prettier-ignore public static SetHideDecorations= (state:boolean) =>runInAction(() => {this.Instance._hideDecorations = state}); // prettier-ignore + public static SetKeepGestureMode= (state:boolean) =>runInAction(() => {this.Instance._keepGestureMode = state}); // prettier-ignore + public static SetInkShape = (shape?:Gestures)=>runInAction(() => {this.Instance._inkShape = shape}); // prettier-ignore public static userColor: string | undefined; public static userVariantColor: string | undefined; diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index 11425e477..99738052d 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -102,7 +102,7 @@ const HotKeyIconButton: React.FC = observer(({ hotKey /*, sel return (
{ e.stopPropagation(); state.startEditing(); diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 59d37df91..2d6cd03e0 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -2,9 +2,9 @@ import * as fitCurve from 'fit-curve'; 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 { setupMoveUpEvents } from '../../ClientUtils'; import { emptyFunction, intersectRect } from '../../Utils'; -import { Doc, Opt, returnEmptyDoclist } from '../../fields/Doc'; +import { Doc } from '../../fields/Doc'; import { InkData, InkField, InkTool } from '../../fields/InkField'; import { NumCast } from '../../fields/Types'; import { Gestures } from '../../pen-gestures/GestureTypes'; @@ -14,27 +14,25 @@ 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 { SnappingManager } from '../util/SnappingManager'; 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, + ActiveInkArrowEnd, + ActiveInkArrowScale, + ActiveInkArrowStart, ActiveInkColor, + ActiveInkDash, + ActiveInkFillColor, ActiveInkWidth, DocumentView, - SetActiveArrowStart, - SetActiveDash, - SetActiveFillColor, + SetActiveInkArrowStart, SetActiveInkColor, + SetActiveInkDash, + SetActiveInkFillColor, SetActiveInkWidth, } from './nodes/DocumentView'; export enum ToolglassTools { @@ -57,18 +55,14 @@ export class GestureOverlay extends ObservableReactComponent = undefined; @observable public SavedColor?: string = undefined; @observable public SavedWidth?: number = undefined; @observable public Tool: ToolglassTools = ToolglassTools.None; - @observable public KeepPrimitiveMode = false; // for whether primitive selection enters a one-shot or persistent mode @observable private _thumbX?: number = undefined; @observable private _thumbY?: number = undefined; @observable private _pointerY?: number = undefined; @observable private _points: { X: number; Y: number }[] = []; - @observable private _strokes: InkData[] = []; - @observable private _palette?: JSX.Element = undefined; @observable private _clipboardDoc?: JSX.Element = undefined; @computed private get height(): number { @@ -97,7 +91,7 @@ export class GestureOverlay extends ObservableReactComponent { (document.activeElement as HTMLElement)?.blur(); if (!(e.target as HTMLElement)?.className?.toString().startsWith('lm_')) { - if ([InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) { + if (Doc.ActiveTool === InkTool.Ink) { this._points.push({ X: e.clientX, Y: e.clientY }); setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction); } @@ -123,13 +117,9 @@ export class GestureOverlay extends ObservableReactComponent ({ X: p.X - B.left, Y: p.Y - B.top })); const { Name, Score } = - (this.InkShape - ? new Result(this.InkShape, 1, Date.now) + (SnappingManager.InkShape + ? new Result(SnappingManager.InkShape, 1, Date.now) : Doc.UserDoc().recognizeGestures && points.length > 2 ? GestureUtils.GestureRecognizer.Recognize([points]) : undefined) ?? @@ -292,6 +282,7 @@ export class GestureOverlay extends ObservableReactComponent { - const b = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; // this.getBounds(l, true); - return ( - - {InteractionUtils.CreatePolyline( - l, - b.left, - b.top, - strokeColor, - width, - width, - 'miter', - 'round', - ActiveInkBezierApprox(), - 'none' /* ActiveFillColor() */, - ActiveArrowStart(), - ActiveArrowEnd(), - ActiveArrowScale(), - ActiveDash(), - 1, - 1, - this.InkShape as Gestures, - 'none', - 1.0, - false - )} - - ); - }), - this._points.length <= 1 ? null : ( - - {InteractionUtils.CreatePolyline( - this._points.map(p => ({ X: p.X - (rect?.x || 0), Y: p.Y - (rect?.y || 0) })), - B.left, - B.top, - ActiveInkColor(), - width, - width, - 'miter', - 'round', - '', - 'none' /* ActiveFillColor() */, - ActiveArrowStart(), - ActiveArrowEnd(), - ActiveArrowScale(), - ActiveDash(), - 1, - 1, - this.InkShape as Gestures, - 'none', - 1.0, - false - )} - - ), - ], + this._points.length <= 1 ? null : ( + + {InteractionUtils.CreatePolyline( + this._points.map(p => ({ X: p.X - (rect?.x || 0), Y: p.Y - (rect?.y || 0) })), + B.left, + B.top, + strokeColor, + width, + width, + 'miter', + 'round', + '', + 'none' /* ActiveFillColor() */, + ActiveInkArrowStart(), + ActiveInkArrowEnd(), + ActiveInkArrowScale(), + ActiveInkDash(), + 1, + 1, + SnappingManager.InkShape, + 'none', + 1.0, + false + )} + + ), ]; } - screenToLocalTransform = () => new Transform(-(this._thumbX ?? 0), -(this._thumbY ?? 0) + this.height, 1); - return300 = () => 300; - @action - public openFloatingDoc = (doc: Doc) => { - this._clipboardDoc = ( - - ); - }; @action public closeFloatingDoc = () => { @@ -694,10 +626,10 @@ ScriptingGlobals.add(function setPen(width: string, color: string, fill: string, SetActiveInkColor(color); GestureOverlay.Instance.SavedWidth = ActiveInkWidth(); SetActiveInkWidth(width); - SetActiveFillColor(fill); - SetActiveArrowStart(arrowStart); - SetActiveArrowStart(arrowEnd); - SetActiveDash(dash); + SetActiveInkFillColor(fill); + SetActiveInkArrowStart(arrowStart); + SetActiveInkArrowStart(arrowEnd); + SetActiveInkDash(dash); }); }); // eslint-disable-next-line prefer-arrow-callback diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index d7d8e9506..f16338361 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -2,7 +2,7 @@ import { random } from 'lodash'; import { action } from 'mobx'; import { Doc, DocListCast } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; -import { InkTool } from '../../fields/InkField'; +import { InkInkTool, InkTool } from '../../fields/InkField'; import { ScriptField } from '../../fields/ScriptField'; import { Cast, PromiseValue } from '../../fields/Types'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; @@ -280,10 +280,10 @@ export class KeyManager { } break; case 'e': - Doc.ActiveTool = [InkTool.StrokeEraser, InkTool.SegmentEraser, InkTool.RadiusEraser].includes(Doc.ActiveTool) ? InkTool.None : InkTool.StrokeEraser; + Doc.ActiveTool = Doc.ActiveTool === InkTool.Eraser ? InkTool.None : InkTool.Eraser; break; case 'p': - Doc.ActiveTool = Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen; + Doc.ActiveTool = Doc.ActiveTool === InkTool.Ink ? InkTool.None : InkTool.Ink; break; case 'r': preventDefault = false; @@ -378,7 +378,8 @@ export class KeyManager { UndoManager.Redo(); break; case 'p': - Doc.ActiveTool = InkTool.Write; + Doc.ActiveInk = InkInkTool.Write; + Doc.ActiveTool = InkTool.Ink; break; default: } diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 270266a94..5199eb02b 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -470,14 +470,13 @@ export class InkingStroke extends ViewBoxAnnotatableComponent() className="inkStroke" style={{ transform: isInkMask ? `rotate(-${NumCast(this._props.LocalRotation?.() ?? 0)}deg) translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined, - // mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset', cursor: this._props.isSelected() ? 'default' : undefined, }} {...interactions}> {clickableLine(this.onPointerDown, isInkMask)} {isInkMask ? null : inkLine} - {!closed || this.dataDoc[this.fieldKey + '_showLabel'] === false || (!RTFCast(this.dataDoc.text)?.Text && !this.dataDoc[this.fieldKey + '_showLabel'] && (!this._props.isSelected() || Doc.UserDoc().activeInkHideTextLabels)) ? null : ( + {!closed || this.dataDoc[this.fieldKey + '_showLabel'] === false || (!RTFCast(this.dataDoc.text)?.Text && !this.dataDoc[this.fieldKey + '_showLabel'] && (!this._props.isSelected() || Doc.UserDoc().activeHideTextLabels)) ? null : (
{ // if (this._showPalette === false) AnnotationPalette.Instance.resetPalette(true); }; togglePen = () => { - Doc.ActiveTool = Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen; + Doc.ActiveTool = Doc.ActiveTool === InkTool.Ink ? InkTool.None : InkTool.Ink; }; toggleExplore = () => SnappingManager.SetExploreMode(!SnappingManager.ExploreMode); @@ -332,7 +332,7 @@ export class LightboxView extends ObservableReactComponent { {toggleBtn('lightboxView-navBtn', 'toggle reading view', BoolCast(this._doc?._layout_fitWidth), 'book-open', 'book', this.toggleFitWidth)} {toggleBtn('lightboxView-tabBtn', 'open document in a tab', false, 'file-export', '', this.downloadDoc)} {toggleBtn('lightboxView-paletteBtn', 'toggle annotation palette', this._showPalette === true, 'palette', '', this.togglePalette)} - {toggleBtn('lightboxView-penBtn', 'toggle pen annotation', Doc.ActiveTool === InkTool.Pen, 'pen', '', this.togglePen)} + {toggleBtn('lightboxView-penBtn', 'toggle pen annotation', Doc.ActiveTool === InkTool.Ink, 'pen', '', this.togglePen)} {toggleBtn('lightboxView-exploreBtn', 'toggle navigate only mode', SnappingManager.ExploreMode, 'globe-americas', '', this.toggleExplore)}
); diff --git a/src/client/views/StyleProp.ts b/src/client/views/StyleProp.ts index 44d3bf757..56367e70b 100644 --- a/src/client/views/StyleProp.ts +++ b/src/client/views/StyleProp.ts @@ -19,7 +19,9 @@ export enum StyleProp { FontColor = 'fontColor', // color o tet FontSize = 'fontSize', // size of text font FontFamily = 'fontFamily', // font family of text - FontWeight = 'fontWeight', // font weight of text + FontWeight = 'fontWeight', // font weight of text (eg bold) + FontStyle = 'fontStyle', // font style of text (eg italic) + FontDecoration = 'fontDecoration', // text decoration of text (eg underline) Highlighting = 'highlighting', // border highlighting ContextMenuItems = 'contextMenuItems', // menu items to add to context menu AnchorMenuItems = 'anchorMenuItems', diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 12c032964..3a5f57908 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -25,6 +25,7 @@ import { styleProviderQuiz } from './StyleProviderQuiz'; import { StyleProp } from './StyleProp'; import './StyleProvider.scss'; import { TagsView } from './TagsView'; +import { InkInkTool } from '../../fields/InkField'; function toggleLockedPosition(doc: Doc) { UndoManager.RunInBatch(() => Doc.toggleLockedPosition(doc), 'toggleBackground'); @@ -171,7 +172,9 @@ export function DefaultStyleProvider(doc: Opt, props: Opt activeTool() === InkTool.Pen, () => penMode], + activePen: [() => activeTool() === InkTool.Ink, () => penMode], }, 'documentation.png', () => TopBar.Instance.FlipDocumentationIcon() @@ -187,7 +187,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent activeTool() === InkTool.Eraser, () => eraserMode], - activePen: [() => activeTool() !== InkTool.Pen, () => viewedLink], + activePen: [() => activeTool() !== InkTool.Ink, () => viewedLink], }); // prettier-ignore // const eraserMode = InfoState('You\'re in eraser mode. Say goodbye to your first masterpiece.', { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index ff711314b..f2a5780c5 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -10,7 +10,7 @@ import { DateField } from '../../../../fields/DateField'; import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../../fields/Doc'; import { DocData, Height, Width } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; -import { InkData, InkField, InkTool, Segment } from '../../../../fields/InkField'; +import { InkData, InkEraserTool, InkField, InkInkTool, InkTool, Segment } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; import { RichTextField } from '../../../../fields/RichTextField'; import { listSpec } from '../../../../fields/Schema'; @@ -36,7 +36,20 @@ import { ContextMenu } from '../../ContextMenu'; import { InkingStroke } from '../../InkingStroke'; import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView'; import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp'; -import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveEraserWidth, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, DocumentView, SetActiveInkColor, SetActiveInkWidth } from '../../nodes/DocumentView'; +import { + ActiveInkArrowEnd, + ActiveInkArrowStart, + ActiveInkDash, + ActiveEraserWidth, + ActiveInkFillColor, + ActiveInkBezierApprox, + ActiveInkColor, + ActiveInkWidth, + ActiveIsInkMask, + DocumentView, + SetActiveInkColor, + SetActiveInkWidth, +} from '../../nodes/DocumentView'; import { FieldViewProps } from '../../nodes/FieldView'; import { FocusViewOptions } from '../../nodes/FocusViewOptions'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; @@ -497,13 +510,9 @@ export class CollectionFreeFormView extends CollectionSubView = this.getRadiusEraserIntersections({ X: currPoint.X - delta[0], Y: currPoint.Y - delta[1] }, currPoint); strokeMap.forEach((intersects, stroke) => { if (!this._deleteList.includes(stroke)) { @@ -614,13 +624,16 @@ export class CollectionFreeFormView extends CollectionSubView - this.forceStrokeGesture( - e, - Gestures.Stroke, - segment.reduce((data, curve) => [...data, ...curve.points.map(p => stroke.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })], [] as PointData[]) - ) - ); + segments?.forEach(segment => { + const points = segment.reduce((data, curve) => [...data, ...curve.points.map(p => stroke.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })], [] as PointData[]); + const bounds = InkField.getBounds(points); + const B = this.screenToFreeformContentsXf.transformBounds(bounds.left, bounds.top, bounds.width, bounds.height); + const inkDoc = this.createInkDoc(points, B); + ['color', 'fillColor', 'stroke_width', 'stroke_dash', 'stroke_bezier'].forEach(field => { + inkDoc[DocData][field] = stroke.dataDoc[field]; + }); + this.addDocument(inkDoc); + }); } stroke.layoutDoc.opacity = 0; stroke.layoutDoc.dontIntersect = true; @@ -632,7 +645,7 @@ export class CollectionFreeFormView extends CollectionSubView { @@ -1195,14 +1208,14 @@ export class CollectionFreeFormView extends CollectionSubView - {Doc.ActiveTool === InkTool.RadiusEraser && this._showEraserCircle && ( + {Doc.ActiveTool === InkTool.Eraser && Doc.ActiveEraser === InkEraserTool.Radius && this._showEraserCircle && (
e.preventDefault()} onScroll={e => { diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 013b18a97..40144c4ce 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -3,7 +3,7 @@ import { Colors } from 'browndash-components'; import { runInAction } from 'mobx'; import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; -import { InkTool } from '../../../fields/InkField'; +import { InkEraserTool, InkInkTool, InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; import { WebField } from '../../../fields/URLField'; @@ -16,21 +16,20 @@ import { SnappingManager } from '../../util/SnappingManager'; import { UndoManager, undoable } from '../../util/UndoManager'; import { GestureOverlay } from '../GestureOverlay'; import { InkTranscription } from '../InkTranscription'; -import { InkingStroke } from '../InkingStroke'; import { PropertiesView } from '../PropertiesView'; import { CollectionFreeFormView } from '../collections/collectionFreeForm'; import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView'; import { ActiveEraserWidth, - ActiveFillColor, + ActiveInkFillColor, ActiveInkColor, - ActiveInkHideTextLabels, + ActiveHideTextLabels, ActiveInkWidth, ActiveIsInkMask, DocumentView, - SetActiveFillColor, + SetActiveInkFillColor, SetActiveInkColor, - SetActiveInkHideTextLabels, + SetactiveHideTextLabels, SetActiveInkWidth, SetActiveIsInkMask, SetEraserWidth, @@ -60,20 +59,21 @@ ScriptingGlobals.add(function setView(view: string, getSelected: boolean) { // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: boolean) { const selectedViews = DocumentView.Selected(); + const selectedDoc = selectedViews.lastElement()?.Document; + const defaultFill = selectedDoc?._layout_isSvg ? () => StrCast(selectedDoc[DocData].fillColor) : !Doc.ActiveTool || Doc.ActiveTool === InkTool.None ? () => StrCast(Doc.UserDoc().textBackgroundColor, 'transparent') : () => ActiveInkFillColor(); + const setDefaultFill = !Doc.ActiveTool || Doc.ActiveTool === InkTool.None ? (c: string) => { Doc.UserDoc().textBackgroundColor = c; }: SetActiveInkFillColor; // prettier-ignore if (Doc.ActiveTool !== InkTool.None && !selectedViews.lastElement()?.Document._layout_isSvg) { - if (checkResult) { - return ActiveFillColor(); - } - SetActiveFillColor(color ?? 'transparent'); + if (checkResult) return defaultFill(); + setDefaultFill(color ?? 'transparent'); } else if (selectedViews.length) { if (checkResult) { const selView = selectedViews.lastElement(); const fieldKey = selView.Document._layout_isSvg ? 'fillColor' : 'backgroundColor'; const layoutFrameNumber = Cast(selView.containerViewPath?.().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values const contentFrameNumber = Cast(selView.Document?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed - return CollectionFreeFormDocumentView.getStringValues(selView?.Document, contentFrameNumber)[fieldKey] ?? 'transparent'; + return CollectionFreeFormDocumentView.getStringValues(selView?.Document, contentFrameNumber)[fieldKey] || defaultFill(); } - selectedViews.some(dv => dv.ComponentView instanceof InkingStroke) && SetActiveFillColor(color ?? 'transparent'); + setDefaultFill(color ?? 'transparent'); selectedViews.forEach(dv => { const fieldKey = dv.Document._layout_isSvg ? 'fillColor' : 'backgroundColor'; const layoutFrameNumber = Cast(dv.containerViewPath?.().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values @@ -92,10 +92,13 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b } else { const selected = DocumentView.SelectedDocs().length ? DocumentView.SelectedDocs() : LinkManager.Instance.currentLink ? [LinkManager.Instance.currentLink] : []; if (checkResult) { - return selected.lastElement()?._backgroundColor ?? 'transparent'; + return selected.lastElement()?._backgroundColor ?? defaultFill(); } - SetActiveFillColor(color ?? 'transparent'); - selected.forEach(doc => { doc[DocData].backgroundColor = color; }); // prettier-ignore + if (!selected.length) setDefaultFill(color ?? 'transparent'); + else + selected.forEach(doc => { + doc[DocData][doc._layout_isSvg ? 'fillColor' : 'backgroundColor'] = color; + }); } return ''; }); @@ -330,7 +333,7 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh return undefined; }); -type attrname = 'noAutoLink' | 'dictation' | 'fitBox' | 'bold' | 'italics' | 'elide' | 'underline' | 'left' | 'center' | 'right' | 'vcent' | 'bullet' | 'decimal'; +type attrname = 'noAutoLink' | 'dictation' | 'fitBox' | 'bold' | 'italic' | 'elide' | 'underline' | 'left' | 'center' | 'right' | 'vcent' | 'bullet' | 'decimal'; type attrfuncs = [attrname, { checkResult: () => boolean; toggle?: () => unknown }]; // eslint-disable-next-line prefer-arrow-callback @@ -360,10 +363,10 @@ ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: toggle: () => editorView && RichTextMenu.Instance?.toggleNoAutoLinkAnchor()}], ['bold', { checkResult: () => (editorView ? RichTextMenu.Instance?.bold??false : (Doc.UserDoc().fontWeight === 'bold')), toggle: editorView ? RichTextMenu.Instance?.toggleBold : () => { Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold'; }}], - ['italics', { checkResult: () => (editorView ? RichTextMenu.Instance?.italics ?? false : (Doc.UserDoc().fontStyle === 'italics')), - toggle: editorView ? RichTextMenu.Instance?.toggleItalics : () => { Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics'; }}], - ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance?.underline ?? false: (Doc.UserDoc().textDecoration === 'underline')), - toggle: editorView ? RichTextMenu.Instance?.toggleUnderline : () => { Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline'; } }]] + ['italic', { checkResult: () => (editorView ? RichTextMenu.Instance?.italic ?? false : (Doc.UserDoc().fontStyle === 'italic')), + toggle: editorView ? RichTextMenu.Instance?.toggleItalic : () => { Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italic' ? undefined : 'italic'; }}], + ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance?.underline ?? false: (Doc.UserDoc().fontDecoration === 'underline')), + toggle: editorView ? RichTextMenu.Instance?.toggleUnderline : () => { Doc.UserDoc().fontDecoration = Doc.UserDoc().fontDecoration === 'underline' ? undefined : 'underline'; } }]] const map = new Map(attrs.concat(alignments).concat(listings)); if (checkResult) { @@ -373,43 +376,46 @@ ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: return undefined; }); -function setActiveTool(toolIn: InkTool | Gestures, keepPrim: boolean, checkResult?: boolean) { +function setActiveTool(tool: InkTool | InkEraserTool | InkInkTool | Gestures, keepPrim: boolean, checkResult?: boolean) { InkTranscription.Instance?.createInkGroup(); - const tool = toolIn === InkTool.Eraser ? Doc.UserDoc().activeEraserTool : toolIn; if (checkResult) { - 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) + return Doc.ActiveTool === tool || Doc.ActiveEraser === tool || Doc.ActiveInk === tool || SnappingManager.InkShape === tool + ? true //SnappingManager.KeepGestureMode || ![Gestures.Circle, Gestures.Line, Gestures.Rectangle].includes(tool as Gestures) : false; } runInAction(() => { + const eraserTool = tool === InkTool.Eraser ? Doc.ActiveEraser : [InkEraserTool.Stroke, InkEraserTool.Radius, InkEraserTool.Segment].includes(tool as InkEraserTool) ? (tool as InkEraserTool) : undefined; + const inkTool = tool === InkTool.Ink ? Doc.ActiveInk : [InkInkTool.Pen, InkInkTool.Write, InkInkTool.Highlight].includes(tool as InkInkTool) ? (tool as InkInkTool) : undefined; if (GestureOverlay.Instance) { - GestureOverlay.Instance.KeepPrimitiveMode = keepPrim; + SnappingManager.SetKeepGestureMode(keepPrim); } if (Object.values(Gestures).includes(tool as Gestures)) { - if (GestureOverlay.Instance.InkShape === tool && !keepPrim) { + if (SnappingManager.InkShape === tool && !keepPrim) { Doc.ActiveTool = InkTool.None; - GestureOverlay.Instance.InkShape = undefined; + SnappingManager.SetInkShape(undefined); } else { - Doc.ActiveTool = InkTool.Pen; - GestureOverlay.Instance.InkShape = tool as Gestures; + Doc.ActiveTool = InkTool.Ink; + SnappingManager.SetInkShape(tool as Gestures); } - } else if (tool) { - if (Doc.UserDoc().ActiveTool === tool) { + } else if (eraserTool) { + if (Doc.ActiveTool === InkTool.Eraser && Doc.ActiveTool === tool) { Doc.ActiveTool = InkTool.None; } else { - if ([InkTool.StrokeEraser, InkTool.RadiusEraser, InkTool.SegmentEraser].includes(tool as InkTool)) { - Doc.UserDoc().activeEraserTool = tool; - } - // pen or eraser - if (Doc.ActiveTool === tool && !GestureOverlay.Instance.InkShape && !keepPrim) { - Doc.ActiveTool = InkTool.None; - } else { - Doc.ActiveTool = tool as InkTool; - GestureOverlay.Instance.InkShape = undefined; - } + Doc.ActiveEraser = eraserTool; + Doc.ActiveTool = InkTool.Eraser; + SnappingManager.SetInkShape(undefined); + } + } else if (inkTool) { + if (Doc.ActiveTool === InkTool.Ink && Doc.ActiveTool === tool) { + Doc.ActiveTool = InkTool.None; + } else { + Doc.ActiveInk = inkTool; + Doc.ActiveTool = InkTool.Ink; + SnappingManager.SetInkShape(undefined); } } else { - Doc.ActiveTool = InkTool.None; + if ((Doc.ActiveTool === tool || !tool) && !keepPrim) Doc.ActiveTool = InkTool.None; + else Doc.ActiveTool = tool as InkTool; } }); return undefined; @@ -419,44 +425,44 @@ ScriptingGlobals.add(setActiveTool, 'sets the active ink tool mode'); // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function activeEraserTool() { - return StrCast(Doc.UserDoc().activeEraserTool, InkTool.StrokeEraser); + return StrCast(Doc.UserDoc().activeEraserTool, InkEraserTool.Stroke); }, 'returns the current eraser tool'); // toggle: Set overlay status of selected document // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'labels' | 'fillColor' | 'strokeWidth' | 'strokeColor' | 'eraserWidth', value: string | number, checkResult?: boolean) { - const selected = DocumentView.SelectedDocs().lastElement() ?? Doc.UserDoc(); + const selected = DocumentView.SelectedDocs().lastElement(); // prettier-ignore const map: Map<'inkMask' | 'labels' | 'fillColor' | 'strokeWidth' | 'strokeColor' | 'eraserWidth', { checkResult: () => number|boolean|string|undefined; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([ ['inkMask', { checkResult: () => ((selected?._layout_isSvg ? BoolCast(selected[DocData].stroke_isInkMask) : ActiveIsInkMask())), setInk: (doc: Doc) => { doc[DocData].stroke_isInkMask = !doc.stroke_isInkMask; }, - setMode: () => selected?.type !== DocumentType.INK && SetActiveIsInkMask(!ActiveIsInkMask()), + setMode: () => SetActiveIsInkMask(value ? true : false) }], ['labels', { - checkResult: () => ((selected?._stroke_showLabel ? BoolCast(selected[DocData].stroke_showLabel) : ActiveInkHideTextLabels())), - setInk: (doc: Doc) => { doc[DocData].stroke_showLabel = !doc.stroke_showLabel; }, - setMode: () => selected?.type !== DocumentType.INK && SetActiveInkHideTextLabels(!ActiveInkHideTextLabels()), + checkResult: () => ((selected?._layout_isSvg ? BoolCast(selected[DocData].stroke_showLabel) : !ActiveHideTextLabels())), + setInk: (doc: Doc) => { doc[DocData].stroke_showLabel = value; }, + setMode: () => SetactiveHideTextLabels(value? false : true), }], ['fillColor', { - checkResult: () => (selected?._layout_isSvg ? StrCast(selected[DocData].fillColor) : ActiveFillColor() ?? "transparent"), + checkResult: () => (selected?._layout_isSvg ? StrCast(selected[DocData].fillColor) : ActiveInkFillColor() ?? "transparent"), setInk: (doc: Doc) => { doc[DocData].fillColor = StrCast(value); }, - setMode: () => SetActiveFillColor(StrCast(value)), + setMode: () => SetActiveInkFillColor(StrCast(value)), }], [ 'strokeWidth', { checkResult: () => (selected?._layout_isSvg ? NumCast(selected[DocData].stroke_width) : ActiveInkWidth()), setInk: (doc: Doc) => { doc[DocData].stroke_width = NumCast(value); }, - setMode: () => { SetActiveInkWidth(value.toString()); selected?.type === DocumentType.INK && setActiveTool( GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);}, + setMode: () => SetActiveInkWidth(value.toString()), }], ['strokeColor', { checkResult: () => (selected?._layout_isSvg? StrCast(selected[DocData].color) : ActiveInkColor()), setInk: (doc: Doc) => { doc[DocData].color = String(value); }, - setMode: () => { SetActiveInkColor(StrCast(value)); selected?.type === DocumentType.INK && setActiveTool(GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);}, + setMode: () => SetActiveInkColor(StrCast(value)) }], [ 'eraserWidth', { checkResult: () => ActiveEraserWidth() === 0 ? 1 : ActiveEraserWidth(), setInk: (doc: Doc) => { }, - setMode: () => { SetEraserWidth(+value);}, + setMode: () => SetEraserWidth(+value), }] ]); diff --git a/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx index 3eb99f47a..8115bafbf 100644 --- a/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx +++ b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx @@ -1,5 +1,3 @@ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ import { action } from 'mobx'; import * as React from 'react'; import { Doc } from '../../../../fields/Doc'; @@ -35,10 +33,10 @@ export function ButtonMenu() {
{ e.stopPropagation(); - Doc.ActiveTool = Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen; + Doc.ActiveTool = Doc.ActiveTool === InkTool.Ink ? InkTool.None : InkTool.Ink; }} />
{this.RenderCutoffProvider(this.Document) ? (
) : ( val.lower)).omit} // prettier-ignore Document={this._props.Document} renderDepth={this._props.renderDepth} diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index d79a37181..7925410e2 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -73,7 +73,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { return
; } marqueeDown = (e: React.PointerEvent) => { - if (!e.altKey && e.button === 0 && NumCast(this.Document._freeform_scale, 1) <= NumCast(this.Document.freeform_scaleMin, 1) && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) { + if (!e.altKey && e.button === 0 && NumCast(this.Document._freeform_scale, 1) <= NumCast(this.Document.freeform_scaleMin, 1) && this._props.isContentActive() && Doc.ActiveTool !== InkTool.Ink) { setupMoveUpEvents( this, e, @@ -453,7 +453,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { @action onPointerDown = (e: React.PointerEvent): void => { if ((this.Document._freeform_scale || 1) !== 1) return; - if (!e.altKey && e.button === 0 && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) { + if (!e.altKey && e.button === 0 && this._props.isContentActive() && Doc.ActiveTool !== InkTool.Ink) { this._props.select(false); MarqueeAnnotator.clearAnnotations(this._savedAnnotations); this._marqueeing = [e.clientX, e.clientY]; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index eb7f333b9..30f9e6363 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -380,7 +380,7 @@ export class DocumentViewInternal extends DocComponent { - if (e.buttons !== 1 || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) return; + if (e.buttons !== 1 || Doc.ActiveTool === InkTool.Ink) return; if (!ClientUtils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) { this.cleanupPointerEvents(); @@ -1560,55 +1560,45 @@ export class DocumentView extends DocComponent() { } else func(); } } - -export function ActiveFillColor(): string { - const dv = DocumentView.Selected().lastElement() ?.Document._layout_isSvg ? DocumentView.Selected().lastElement() : undefined; - return StrCast(dv?.Document.fillColor, StrCast(ActiveInkPen()?.activeFillColor, "")); -} // prettier-ignore -export function ActiveInkPen(): Doc { return Doc.UserDoc(); } // prettier-ignore -export function ActiveInkColor(): string { return StrCast(ActiveInkPen()?.activeInkColor, 'black'); } // prettier-ignore -export function ActiveIsInkMask(): boolean { return BoolCast(ActiveInkPen()?.activeIsInkMask, false); } // prettier-ignore -export function ActiveInkHideTextLabels(): boolean { return BoolCast(ActiveInkPen().activeInkHideTextLabels, false); } // prettier-ignore -export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, ''); } // prettier-ignore -export function ActiveArrowEnd(): string { return StrCast(ActiveInkPen()?.activeArrowEnd, ''); } // prettier-ignore -export function ActiveArrowScale(): number { return NumCast(ActiveInkPen()?.activeArrowScale, 1); } // prettier-ignore -export function ActiveDash(): string { return StrCast(ActiveInkPen()?.activeDash, '0'); } // prettier-ignore -export function ActiveInkWidth(): number { return Number(ActiveInkPen()?.activeInkWidth); } // prettier-ignore -export function ActiveInkBezierApprox(): string { return StrCast(ActiveInkPen()?.activeInkBezier); } // prettier-ignore -export function ActiveEraserWidth(): number { return Number(ActiveInkPen()?.eraserWidth ?? 25); } // prettier-ignore - +export function ActiveHideTextLabels(): boolean { return BoolCast(Doc.UserDoc().activeHideTextLabels, false); } // prettier-ignore +export function ActiveIsInkMask(): boolean { return BoolCast(Doc.UserDoc()?.activeIsInkMask, false); } // prettier-ignore +export function ActiveEraserWidth(): number { return Number(Doc.UserDoc()?.activeEraserWidth ?? 25); } // prettier-ignore + +export function ActiveInkFillColor(): string { return StrCast(Doc.UserDoc()?.[`active${Doc.ActiveInk}Fill`]); } // prettier-ignore +export function ActiveInkColor(): string { return StrCast(Doc.UserDoc()?.[`active${Doc.ActiveInk}Color`], 'black'); } // prettier-ignore +export function ActiveInkArrowStart(): string { return StrCast(Doc.UserDoc()?.[`active${Doc.ActiveInk}ArrowStart`], ''); } // prettier-ignore +export function ActiveInkArrowEnd(): string { return StrCast(Doc.UserDoc()?.[`active${Doc.ActiveInk}ArrowEnd`], ''); } // prettier-ignore +export function ActiveInkArrowScale(): number { return NumCast(Doc.UserDoc()?.[`active${Doc.ActiveInk}ArrowScale`], 1); } // prettier-ignore +export function ActiveInkDash(): string { return StrCast(Doc.UserDoc()?.[`active${Doc.ActiveInk}Dash`], '0'); } // prettier-ignore +export function ActiveInkWidth(): number { return Number(Doc.UserDoc()?.[`active${Doc.ActiveInk}Width`]); } // prettier-ignore +export function ActiveInkBezierApprox(): string { return StrCast(Doc.UserDoc()[`active${Doc.ActiveInk}Bezier`]); } // prettier-ignore + +export function SetActiveIsInkMask(value: boolean) { Doc.UserDoc() && (Doc.UserDoc().activeIsInkMask = value); } // prettier-ignore +export function SetactiveHideTextLabels(value: boolean) { Doc.UserDoc() && (Doc.UserDoc().activeHideTextLabels = value); } // prettier-ignore +export function SetEraserWidth(width: number): void { Doc.UserDoc() && (Doc.UserDoc().activeEraserWidth = width); } // prettier-ignore export function SetActiveInkWidth(width: string): void { - !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width); + !isNaN(parseInt(width)) && Doc.UserDoc() && (Doc.UserDoc()[`active${Doc.ActiveInk}Width`] = width); } -export function SetActiveBezierApprox(bezier: string): void { - ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? '' : bezier); +export function SetActiveInkBezierApprox(bezier: string): void { + Doc.UserDoc() && (Doc.UserDoc()[`active${Doc.ActiveInk}Bezier`] = isNaN(parseInt(bezier)) ? '' : bezier); } export function SetActiveInkColor(value: string) { - ActiveInkPen() && (ActiveInkPen().activeInkColor = value); -} -export function SetActiveIsInkMask(value: boolean) { - ActiveInkPen() && (ActiveInkPen().activeIsInkMask = value); -} -export function SetActiveInkHideTextLabels(value: boolean) { - ActiveInkPen() && (ActiveInkPen().activeInkHideTextLabels = value); -} -export function SetActiveFillColor(value: string) { - ActiveInkPen() && (ActiveInkPen().activeFillColor = value); + Doc.UserDoc() && (Doc.UserDoc()[`active${Doc.ActiveInk}Color`] = value); } -export function SetActiveArrowStart(value: string) { - ActiveInkPen() && (ActiveInkPen().activeArrowStart = value); +export function SetActiveInkFillColor(value: string) { + Doc.UserDoc() && (Doc.UserDoc()[`active${Doc.ActiveInk}Fill`] = value); } -export function SetActiveArrowEnd(value: string) { - ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value); +export function SetActiveInkArrowStart(value: string) { + Doc.UserDoc() && (Doc.UserDoc()[`active${Doc.ActiveInk}ArrowStart`] = value); } -export function SetActiveArrowScale(value: number) { - ActiveInkPen() && (ActiveInkPen().activeArrowScale = value); +export function SetActiveInkArrowEnd(value: string) { + Doc.UserDoc() && (Doc.UserDoc()[`active${Doc.ActiveInk}ArrowEnd`] = value); } -export function SetActiveDash(dash: string): void { - !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash); +export function SetActiveInkArrowScale(value: number) { + Doc.UserDoc() && (Doc.UserDoc()[`active${Doc.ActiveInk}ArrowScale`] = value); } -export function SetEraserWidth(width: number): void { - ActiveInkPen() && (ActiveInkPen().eraserWidth = width); +export function SetActiveInkDash(dash: string): void { + !isNaN(parseInt(dash)) && Doc.UserDoc() && (Doc.UserDoc()[`active${Doc.ActiveInk}`] = dash); } // eslint-disable-next-line prefer-arrow-callback diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.scss b/src/client/views/nodes/FontIconBox/FontIconBox.scss index ab03a2318..2405889cf 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.scss +++ b/src/client/views/nodes/FontIconBox/FontIconBox.scss @@ -13,6 +13,9 @@ .fonticonbox { margin: auto; width: 100%; + .formLabel { + height: 5px; + } } .menuButton { height: 100%; diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index b45774a75..8c138c2ee 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -4,7 +4,7 @@ import { Button, ColorPicker, Dropdown, DropdownType, IconButton, IListItemProps import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { ClientUtils, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils'; +import { ClientUtils, DashColor, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils'; import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc'; import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; import { emptyFunction } from '../../../../Utils'; @@ -21,6 +21,8 @@ import { FieldView, FieldViewProps } from '../FieldView'; import { OpenWhere } from '../OpenWhere'; import './FontIconBox.scss'; import TrailsIcon from './TrailsIcon'; +import { InkTool } from '../../../../fields/InkField'; +import { ScriptField } from '../../../../fields/ScriptField'; export enum ButtonType { TextButton = 'textBtn', @@ -126,7 +128,8 @@ export class FontIconBox extends ViewBoxBaseComponent() { background={SnappingManager.userBackgroundColor} numberDropdownType={type} showPlusMinus={false} - tooltip={this.label} + formLabel={(StrCast(this.Document.title).startsWith(' ') ? '\u00A0' : '') + StrCast(this.Document.title)} + tooltip={StrCast(this.Document.toolTip, this.label)} type={Type.PRIM} min={NumCast(this.dataDoc.numBtnMin, 0)} max={NumCast(this.dataDoc.numBtnMax, 100)} @@ -148,65 +151,80 @@ export class FontIconBox extends ViewBoxBaseComponent() { return false; }; + /** + * Displays custom dropdown menu for fonts -- this is a HACK -- fix for generality, don't copy + */ + handleFontDropdown = (script: () => string, buttonList: string[]) => { + // text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily); + return { + buttonList, + jsx: undefined, + selectedVal: script(), + getStyle: (val: string) => ({ fontFamily: val }), + }; + }; + /** + * Displays custom dropdown menu for view selection -- this is a HACK -- fix for generality, don't copy + */ + handleViewDropdown = (script: ScriptField, buttonList: string[]) => { + const selected = Array.from(script?.script.run({ _readOnly_: true }).result as Doc[]); + const noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Card, CollectionViewType.Carousel3D, CollectionViewType.Carousel, CollectionViewType.Stacking, CollectionViewType.NoteTaking]; + return selected.length === 1 && selected[0].type === DocumentType.COL + ? { + buttonList: buttonList.filter(value => !Doc.noviceMode || !noviceList.length || noviceList.includes(value as CollectionViewType)), + getStyle: undefined, + selectedVal: StrCast(selected[0]._type_collection), + } + : { + jsx: selected.length ? ( + 1 ? 'caret-down' : (Doc.toIcon(selected.lastElement()) as IconProp)} />} + text={selected.length === 1 ? ClientUtils.cleanDocumentType(StrCast(selected[0].type) as DocumentType) : selected.length + ' selected'} + type={Type.TERT} + color={SnappingManager.userColor} + background={SnappingManager.userVariantColor} + popup={} + fillWidth + /> + ) : ( +
@@ -1042,7 +1041,6 @@ export class WebBox extends ViewBoxAnnotatableComponent() { } renderAnnotations = (childFilters: () => string[]) => ( () {
{ @observable private collapsed: boolean = false; @observable private _noLinkActive: boolean = false; @observable private _boldActive: boolean = false; - @observable private _italicsActive: boolean = false; + @observable private _italicActive: boolean = false; @observable private _underlineActive: boolean = false; @observable private _strikethroughActive: boolean = false; @observable private _subscriptActive: boolean = false; @@ -89,8 +89,8 @@ export class RichTextMenu extends AntimodeMenu { @computed get underline() { return this._underlineActive; } - @computed get italics() { - return this._italicsActive; + @computed get italic() { + return this._italicActive; } @computed get strikeThrough() { return this._strikethroughActive; @@ -280,7 +280,7 @@ export class RichTextMenu extends AntimodeMenu { this._noLinkActive = false; this._boldActive = false; - this._italicsActive = false; + this._italicActive = false; this._underlineActive = false; this._strikethroughActive = false; this._subscriptActive = false; @@ -290,7 +290,7 @@ export class RichTextMenu extends AntimodeMenu { switch (mark.name) { case 'noAutoLinkAnchor': this._noLinkActive = true; break; case 'strong': this._boldActive = true; break; - case 'em': this._italicsActive = true; break; + case 'em': this._italicActive = true; break; case 'underline': this._underlineActive = true; break; case 'strikethrough': this._strikethroughActive = true; break; case 'subscript': this._subscriptActive = true; break; @@ -352,7 +352,7 @@ export class RichTextMenu extends AntimodeMenu { } }; - toggleItalics = () => { + toggleItalic = () => { if (this.view) { const mark = this.view.state.schema.mark(this.view.state.schema.marks.em); this.setMark(mark, this.view.state, this.view.dispatch, false); @@ -361,13 +361,10 @@ export class RichTextMenu extends AntimodeMenu { }; setFontField = (value: string, fontField: 'fitBox' | 'fontSize' | 'fontFamily' | 'fontColor' | 'fontHighlight') => { - if (this.dataDoc) { - this.dataDoc[`text_${fontField}`] = value; - this.updateMenu(undefined, undefined, undefined, this.dataDoc); - } else if (this.TextView && this.view) { + if (this.TextView && this.view && fontField !== 'fitBox') { const { text, paragraph } = this.view.state.schema.nodes; const selNode = this.view.state.selection.$anchor.node(); - if ((fontField === 'fontSize' && value === '0px') || (this.view.state.selection.from === 1 && this.view.state.selection.empty && [undefined, text, paragraph].includes(selNode?.type))) { + if (this.view.state.selection.from === 1 && this.view.state.selection.empty && [undefined, text, paragraph].includes(selNode?.type)) { this.TextView.dataDoc[this.TextView.fieldKey + `_${fontField}`] = value; this.view.focus(); } @@ -376,6 +373,9 @@ export class RichTextMenu extends AntimodeMenu { const fmark = this.view?.state.schema.marks['pF' + fontField.substring(1)].create(attrs); this.setMark(fmark, this.view.state, (tx: Transaction) => this.view!.dispatch(tx.addStoredMark(fmark)), true); this.view.focus(); + } else if (this.dataDoc) { + this.dataDoc[`text_${fontField}`] = value; + this.updateMenu(undefined, undefined, undefined, this.dataDoc); } else { Doc.UserDoc()[fontField] = value; this.updateMenu(undefined, undefined, undefined, this.dataDoc); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 358557ad7..920c9ea8b 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -377,7 +377,7 @@ export class PDFViewer extends ObservableReactComponent { if ((e.button !== 0 || e.altKey) && this._props.isContentActive()) { this._setPreviewCursor?.(e.clientX, e.clientY, true, false, this._props.Document); } - if (!e.altKey && e.button === 0 && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) { + if (!e.altKey && e.button === 0 && this._props.isContentActive() && Doc.ActiveTool !== InkTool.Ink) { this._props.select(false); MarqueeAnnotator.clearAnnotations(this._savedAnnotations); this.isAnnotating = true; diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx index b4635673c..d0f6566a5 100644 --- a/src/client/views/smartdraw/SmartDrawHandler.tsx +++ b/src/client/views/smartdraw/SmartDrawHandler.tsx @@ -21,7 +21,7 @@ import { SVGToBezier, SVGType } from '../../util/bezierFit'; import { InkingStroke } from '../InkingStroke'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { MarqueeView } from '../collections/collectionFreeForm'; -import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, DocumentView } from '../nodes/DocumentView'; +import { ActiveInkArrowEnd, ActiveInkArrowStart, ActiveInkDash, ActiveInkFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, DocumentView } from '../nodes/DocumentView'; import './SmartDrawHandler.scss'; export interface DrawingOptions { @@ -105,14 +105,14 @@ export class SmartDrawHandler extends ObservableReactComponent { y: bounds.top - inkWidth / 2, _width: bounds.width + inkWidth, _height: bounds.height + inkWidth, - stroke_showLabel: BoolCast(Doc.UserDoc().activeInkHideTextLabels)}, // prettier-ignore + stroke_showLabel: !BoolCast(Doc.UserDoc().activeHideTextLabels)}, // prettier-ignore inkWidth, opts.autoColor ? stroke[1] : ActiveInkColor(), ActiveInkBezierApprox(), - stroke[2] === 'none' ? ActiveFillColor() : stroke[2], - ActiveArrowStart(), - ActiveArrowEnd(), - ActiveDash(), + stroke[2] === 'none' ? ActiveInkFillColor() : stroke[2], + ActiveInkArrowStart(), + ActiveInkArrowEnd(), + ActiveInkDash(), ActiveIsInkMask() ); drawing.push(inkDoc); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 6ec195910..aef7bf330 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -14,7 +14,7 @@ import { Initializing, Self, SelfProxy, TransitionTimer, UpdatingFromServer, Width } from './DocSymbols'; // prettier-ignore import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols'; -import { InkTool } from './InkField'; +import { InkEraserTool, InkInkTool, InkTool } from './InkField'; import { List } from './List'; import { ObjectField, serverOpType } from './ObjectField'; import { PrefetchProxy, ProxyField } from './Proxy'; @@ -253,6 +253,10 @@ export class Doc extends RefField { public static set ActivePage(val) { Doc.UserDoc().activePage = val; } // prettier-ignore public static get ActiveTool(): InkTool { return StrCast(Doc.UserDoc().activeTool, InkTool.None) as InkTool; } // prettier-ignore public static set ActiveTool(tool:InkTool){ Doc.UserDoc().activeTool = tool; } // prettier-ignore + public static get ActiveInk(): InkInkTool { return StrCast(Doc.UserDoc().activeInkTool, InkTool.None) as InkInkTool; } // prettier-ignore + public static set ActiveInk(tool:InkInkTool){ Doc.UserDoc().activeInkTool = tool; } // prettier-ignore + public static get ActiveEraser(): InkEraserTool { return StrCast(Doc.UserDoc().activeEraserTool, InkTool.None) as InkEraserTool; } // prettier-ignore + public static set ActiveEraser(tool:InkEraserTool){ Doc.UserDoc().activeEraserTool = tool; } // prettier-ignore public static get ActivePresentation() { return DocCast(Doc.ActiveDashboard?.activePresentation) as Opt; } // prettier-ignore public static set ActivePresentation(val) { Doc.ActiveDashboard && (Doc.ActiveDashboard.activePresentation = val) } // prettier-ignore public static get ActiveDashboard() { return DocCast(Doc.UserDoc().activeDashboard); } // prettier-ignore diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts index 17b99b033..f3ff5f11f 100644 --- a/src/fields/InkField.ts +++ b/src/fields/InkField.ts @@ -8,17 +8,22 @@ import { ObjectField } from './ObjectField'; // Helps keep track of the current ink tool in use. export enum InkTool { - None = 'none', - Pen = 'pen', - Highlighter = 'highlighter', - StrokeEraser = 'strokeeraser', - SegmentEraser = 'segmenteraser', - RadiusEraser = 'radiuseraser', - Eraser = 'eraser', // not a real tool, but a class of tools - Stamp = 'stamp', - Write = 'write', - PresentationPin = 'presentationpin', - SmartDraw = 'smartdraw', + None = 'None', + Ink = 'Ink', + Eraser = 'Eraser', // not a real tool, but a class of tools + SmartDraw = 'Smartdraw', +} + +export enum InkInkTool { + Pen = 'Pen', + Highlight = 'Highlight', + Write = 'Write', +} + +export enum InkEraserTool { + Stroke = 'Stroke', + Segment = 'Segment', + Radius = 'Radius', } export type Segment = Array; diff --git a/src/pen-gestures/GestureTypes.ts b/src/pen-gestures/GestureTypes.ts index 5a8e9bd97..10f9ba6d0 100644 --- a/src/pen-gestures/GestureTypes.ts +++ b/src/pen-gestures/GestureTypes.ts @@ -8,7 +8,6 @@ export enum Gestures { Arrow = 'arrow', RightAngle = 'rightangle', } - // Defines a point in an ink as a pair of x- and y-coordinates. export interface PointData { X: number; -- cgit v1.2.3-70-g09d2 From 8680d1c31d4f835663c070f5b8cef57254e75e28 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 12 Nov 2024 16:42:35 -0500 Subject: made equation background same as text. fixed dflt stroke/link width to both be 1. made function plot axis ranges get saved to Doc. marked equation->function links as being svgs. fixed initial size of equation boxes.. --- src/client/documents/DocUtils.ts | 3 +- src/client/util/CurrentUserUtils.ts | 2 +- src/client/views/PropertiesView.tsx | 2 +- src/client/views/StyleProvider.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 7 +--- src/client/views/global/globalScripts.ts | 2 +- src/client/views/linking/LinkMenuItem.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 3 +- src/client/views/nodes/EquationBox.scss | 2 - src/client/views/nodes/EquationBox.tsx | 2 +- src/client/views/nodes/FunctionPlotBox.tsx | 43 +++++++++++++++++++--- src/client/views/nodes/LinkBox.tsx | 2 +- 12 files changed, 50 insertions(+), 22 deletions(-) (limited to 'src/client/documents/DocUtils.ts') diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts index 067b9c5e0..741d3a10e 100644 --- a/src/client/documents/DocUtils.ts +++ b/src/client/documents/DocUtils.ts @@ -168,7 +168,7 @@ export namespace DocUtils { return rangeFilteredDocs; } - export function MakeLink(source: Doc, target: Doc, linkSettings: { link_relationship?: string; link_description?: string }, id?: string, showPopup?: number[]) { + export function MakeLink(source: Doc, target: Doc, linkSettings: { layout_isSvg?: boolean; link_relationship?: string; link_description?: string }, id?: string, showPopup?: number[]) { if (!linkSettings.link_relationship) linkSettings.link_relationship = target.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link'; if (target.doc === Doc.UserDoc()) return undefined; @@ -220,6 +220,7 @@ export namespace DocUtils { link_anchor_2_useSmallAnchor: target.useSmallAnchor ? true : undefined, link_relationship: linkSettings.link_relationship, link_description: linkSettings.link_description, + layout_isSvg: linkSettings.layout_isSvg, x: ComputedField.MakeFunction(`((this.${a}?.x||0)+(this.${b}?.x||0))/2`) as unknown as number, // x can accept functions even though type says it can't y: ComputedField.MakeFunction(`((this.${a}?.y||0)+(this.${b}?.y||0))/2`) as unknown as number, // y can accept functions even though type says it can't link_autoMoveAnchors: true, diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index fe23fe062..7b2a7ae0f 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1142,7 +1142,7 @@ pie title Minerals in my tap water } // eslint-disable-next-line prefer-arrow-callback -ScriptingGlobals.add(function activeInkTool() { return Doc.ActiveTool=== InkTool.Ink || DocumentView.Selected().some(dv => dv.layoutDoc.layout_isSvg); }, "is a pen tool or an ink stroke active"); +ScriptingGlobals.add(function activeInkTool() { return Doc.ActiveTool=== InkTool.Ink || DocumentView.Selected().some(dv => dv.layoutDoc.layout_isSvg); }, "is a pen tool or an ink stroke active"); // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function NotRadiusEraser() { return Doc.ActiveTool !== InkTool.Eraser || Doc.ActiveEraser !== InkEraserTool.Radius; }, "is the active tool anything but the radius eraser"); // eslint-disable-next-line prefer-arrow-callback diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 10c2a9898..2bd8bc98a 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -830,7 +830,7 @@ export class PropertiesView extends ObservableReactComponent { doc[DocData].stroke_width = Math.round(value * 100) / 100; }); } // prettier-ignore @computed get hgtInput() { diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 3a5f57908..0a9da1237 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -257,7 +257,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt SetactiveHideTextLabels(value? false : true), }], [ InkProperty.StrokeWidth, { - checkResult: () => (selected?._layout_isSvg ? NumCast(selected[DocData].stroke_width) : ActiveInkWidth()), + checkResult: () => (selected?._layout_isSvg ? NumCast(selected[DocData].stroke_width, 1) : ActiveInkWidth()), setInk: (doc: Doc) => { doc[DocData].stroke_width = NumCast(value); }, setMode: () => SetActiveInkWidth(value.toString()), }], diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index b24fca8e2..1c92c3b0d 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -90,7 +90,7 @@ export class LinkMenuItem extends ObservableReactComponent { moveEv => { const dragData = new DragManager.DocumentDragData([this._props.linkDoc], dropActionType.embed); dragData.dropPropertiesToRemove = ['hidden']; - DragManager.StartDocumentDrag([this._editRef.current!], dragData, moveEv.x, moveEv.y, undefined, e => (this._props.linkDoc._layout_isSvg = true)); + DragManager.StartDocumentDrag([this._editRef.current!], dragData, moveEv.x, moveEv.y, undefined, () => (this._props.linkDoc._layout_isSvg = true)); return true; }, emptyFunction, diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 30f9e6363..92e98db87 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -459,10 +459,9 @@ export class DocumentViewInternal extends DocComponent span { width: fit-content; } diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx index 472fa56a0..34b46381d 100644 --- a/src/client/views/nodes/EquationBox.tsx +++ b/src/client/views/nodes/EquationBox.tsx @@ -81,7 +81,7 @@ export class EquationBox extends ViewBoxBaseComponent() { _height: 300, backgroundColor: 'white', }); - const link = DocUtils.MakeLink(this.Document, graph, { link_relationship: 'function', link_description: 'input' }); + const link = DocUtils.MakeLink(this.Document, graph, { layout_isSvg: true, link_relationship: 'function', link_description: 'input' }); this._props.addDocument?.(graph); link && this._props.addDocument?.(link); e.stopPropagation(); diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx index 6b439cd64..91c351895 100644 --- a/src/client/views/nodes/FunctionPlotBox.tsx +++ b/src/client/views/nodes/FunctionPlotBox.tsx @@ -1,5 +1,5 @@ import functionPlot, { Chart } from 'function-plot'; -import { computed, makeObservable, reaction } from 'mobx'; +import { action, computed, makeObservable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast } from '../../../fields/Doc'; @@ -15,6 +15,8 @@ import { undoBatch } from '../../util/UndoManager'; import { ViewBoxAnnotatableComponent } from '../DocComponent'; import { PinDocView, PinProps } from '../PinFuncs'; import { FieldView, FieldViewProps } from './FieldView'; +import { returnFalse, setupMoveUpEvents } from '../../../ClientUtils'; +import { emptyFunction } from '../../../Utils'; @observer export class FunctionPlotBox extends ViewBoxAnnotatableComponent() { @@ -65,18 +67,24 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent ); return funcs; } + computeYScale = (width: number, height: number, xScale: number[]) => { + const xDiff = xScale[1] - xScale[0]; + const yDiff = (height * xDiff) / width; + return [-yDiff / 2, yDiff / 2]; + }; createGraph = (ele?: HTMLDivElement) => { this._plotEle = ele || this._plotEle; const width = this._props.PanelWidth(); const height = this._props.PanelHeight(); + const xrange = Cast(this.layoutDoc.xRange, listSpec('number'), [-10, 10]); try { this._plotEle?.children.length && this._plotEle.removeChild(this._plotEle.children[0]); this._plot = functionPlot({ target: '#' + this._plotEle?.id, width, height, - xAxis: { domain: Cast(this.layoutDoc.xRange, listSpec('number'), [-10, 10]) }, - yAxis: { domain: Cast(this.layoutDoc.yRange, listSpec('number'), [-1, 9]) }, + xAxis: { domain: xrange }, + yAxis: { domain: this.computeYScale(width, height, xrange) }, // Cast(this.layoutDoc.yRange, listSpec('number'), [-1, 9]) }, grid: true, data: this.graphFuncs.map(fn => ({ fn, @@ -94,7 +102,7 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent const added = de.complete.docDragData.droppedDocuments.reduce((res, doc) => { // const ret = res && Doc.AddDocToList(this.dataDoc, this._props.fieldKey, doc); if (res) { - const link = DocUtils.MakeLink(doc, this.Document, { link_relationship: 'function', link_description: 'input' }); + const link = DocUtils.MakeLink(doc, this.Document, { layout_isSvg: true, link_relationship: 'function', link_description: 'input' }); link && this._props.addDocument?.(link); } return res; @@ -115,7 +123,32 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent // if (this.layout_autoHeight) this.tryUpdateScrollHeight(); }; @computed get theGraph() { - return
r && this.createGraph(r)} style={{ position: 'absolute', width: '100%', height: '100%' }} onPointerDown={e => e.stopPropagation()} />; + return ( +
r && this.createGraph(r)} + style={{ position: 'absolute', width: '100%', height: '100%' }} + onPointerDown={e => { + e.stopPropagation(); + setupMoveUpEvents( + this, + e, + returnFalse, + action(() => { + if (this._plot?.options.xAxis?.domain) { + this.Document.xRange = new List(this._plot.options.xAxis.domain); + } + if (this._plot?.options.yAxis?.domain) { + this.Document.yRange = new List(this._plot.options.yAxis.domain); + } + }), + emptyFunction, + false, + false + ); + }} + /> + ); } render() { TraceMobx(); diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 4d9d2460e..c62a8afb1 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -160,7 +160,7 @@ export class LinkBox extends ViewBoxBaseComponent() { // eslint-disable-next-line camelcase const { stroke_markerScale: strokeMarkerScale, stroke_width: strokeRawWidth, stroke_startMarker: strokeStartMarker, stroke_endMarker: strokeEndMarker, stroke_dash: strokeDash } = this.Document; - const strokeWidth = NumCast(strokeRawWidth, 4); + const strokeWidth = NumCast(strokeRawWidth, 1); const linkDesc = StrCast(this.dataDoc.link_description) || ' '; const labelText = linkDesc.substring(0, 50) + (linkDesc.length > 50 ? '...' : ''); return ( -- cgit v1.2.3-70-g09d2 From 6a6215a447c3104f5fea8a813270b8c1fc39ad75 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 12 Nov 2024 18:03:12 -0500 Subject: fixed resizing height to 0 to autoResize. fixed setting background color default for equations (and everything else) to textBackgroundColor from user doc. Added a math pseudo-font to trigger entering equations instead of rich text. --- src/client/documents/DocUtils.ts | 70 +++++++++++++++++++++----------- src/client/util/CurrentUserUtils.ts | 4 +- src/client/views/DocumentDecorations.tsx | 6 +-- src/client/views/nodes/EquationBox.tsx | 14 +++++-- 4 files changed, 63 insertions(+), 31 deletions(-) (limited to 'src/client/documents/DocUtils.ts') diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts index 741d3a10e..c466344b3 100644 --- a/src/client/documents/DocUtils.ts +++ b/src/client/documents/DocUtils.ts @@ -358,6 +358,16 @@ export namespace DocUtils { return ctor ? ctor(path, overwriteDoc ? { ...options, title: StrCast(overwriteDoc.title, path) } : options, overwriteDoc) : undefined; } + /** + * Adds items to the doc creator (':') context menu for creating each document type + * @param docTextAdder + * @param docAdder + * @param x + * @param y + * @param simpleMenu + * @param pivotField + * @param pivotValue + */ export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number, simpleMenu: boolean = false, pivotField?: string, pivotValue?: string | number | boolean): void { const documentList: ContextMenuProps[] = DocListCast(DocListCast(Doc.MyTools?.data)[0]?.data) .filter(btnDoc => !btnDoc.hidden) @@ -371,6 +381,7 @@ export namespace DocUtils { newDoc.author = ClientUtils.CurrentUserEmail(); newDoc.x = x; newDoc.y = y; + newDoc[DocData].backgroundColor = Doc.UserDoc().textBackgroundColor; Doc.SetSelectOnLoad(newDoc); if (pivotField) { newDoc[pivotField] = pivotValue; @@ -675,30 +686,43 @@ export namespace DocUtils { export function GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, annotationOn?: Doc, backgroundColor?: string) { const defaultTextTemplate = DocCast(Doc.UserDoc().defaultTextLayout); - const tbox = Docs.Create.TextDocument('', { - annotationOn, - backgroundColor, - x, - y, - title, - ...(defaultTextTemplate - ? {} // if the new doc will inherit from a template, don't set any layout fields since that would block the inheritance - : { - _width: width || BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 * 6 : 200, - _height: BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 : 35, - _layout_centered: BoolCast(Doc.UserDoc()._layout_centered), - _layout_fitWidth: true, - _layout_autoHeight: true, - backgroundColor: StrCast(Doc.UserDoc().textBackgroundColor), - text_fitBox: BoolCast(Doc.UserDoc().fitBox), - text_align: StrCast(Doc.UserDoc().textAlign), + const tbox = + StrCast(Doc.UserDoc().fontFamily) === 'Math' + ? Docs.Create.EquationDocument('', { + // + annotationOn, + backgroundColor: backgroundColor ?? StrCast(Doc.UserDoc().textBackgroundColor), + x, + y, + title, text_fontColor: StrCast(Doc.UserDoc().fontColor), - text_fontFamily: StrCast(Doc.UserDoc().fontFamily), - text_fontWeight: StrCast(Doc.UserDoc().fontWeight), - text_fontStyle: StrCast(Doc.UserDoc().fontStyle), - text_fontDecoration: StrCast(Doc.UserDoc().fontDecoration), - }), - }); + _width: 35, + _height: 35, + }) + : Docs.Create.TextDocument('', { + annotationOn, + backgroundColor, + x, + y, + title, + ...(defaultTextTemplate + ? {} // if the new doc will inherit from a template, don't set any layout fields since that would block the inheritance + : { + _width: width || BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 * 6 : 200, + _height: BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 : 35, + _layout_centered: BoolCast(Doc.UserDoc()._layout_centered), + _layout_fitWidth: true, + _layout_autoHeight: true, + backgroundColor: StrCast(Doc.UserDoc().textBackgroundColor), + text_fitBox: BoolCast(Doc.UserDoc().fitBox), + text_align: StrCast(Doc.UserDoc().textAlign), + text_fontColor: StrCast(Doc.UserDoc().fontColor), + text_fontFamily: StrCast(Doc.UserDoc().fontFamily), + text_fontWeight: StrCast(Doc.UserDoc().fontWeight), + text_fontStyle: StrCast(Doc.UserDoc().fontStyle), + text_fontDecoration: StrCast(Doc.UserDoc().fontDecoration), + }), + }); if (defaultTextTemplate) { tbox.layout_fieldKey = 'layout_' + StrCast(defaultTextTemplate.title); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 7b2a7ae0f..c01f57a59 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -372,7 +372,7 @@ pie title Minerals in my tap water {key: "Note", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true }}, {key: "Flashcard", creator: opts => Docs.Create.FlashcardDocument("", undefined, undefined, opts),opts: { _width: 300, _height: 300}}, {key: "Image", creator: opts => Docs.Create.ImageDocument("", opts), opts: { _width: 400, _height:400 }}, - {key: "Equation", creator: opts => Docs.Create.EquationDocument("",opts), opts: { _width: 300, _height: 35, }}, + {key: "Equation", creator: opts => Docs.Create.EquationDocument("",opts), opts: { _width: 35, _height: 35, }}, {key: "Noteboard", creator: opts => Docs.Create.NoteTakingDocument([], opts), opts: { _width: 250, _height: 200, _layout_fitWidth: true}}, {key: "Simulation", creator: opts => Docs.Create.SimulationDocument(opts), opts: { _width: 300, _height: 300, }}, {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 150, _height: 100, _layout_fitWidth: true }}, @@ -731,7 +731,7 @@ pie title Minerals in my tap water static textTools():Button[] { return [ { title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, toolType:"font", ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'}, - btnList: new List(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) }, + btnList: new List(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text", "Math"]) }, { title: " Size", toolTip: "Font size (%size)", btnType: ButtonType.NumberDropdownButton, toolType:"fontSize", ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 9 }, { title: "Color", toolTip: "Font color (%color)", btnType: ButtonType.ColorButton, icon: "font", toolType:"fontColor",ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'} }, { title: "Highlight",toolTip: "Font highlight", btnType: ButtonType.ColorButton, icon: "highlighter", toolType:"highlight",ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'} }, diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 492c2bda1..07e362672 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -506,7 +506,7 @@ export class DocumentDecorations extends ObservableReactComponent { - const [w, h] = [this.Bounds.r - this.Bounds.x, this.Bounds.b - this.Bounds.y]; + const [w, h] = [Math.max(1, this.Bounds.r - this.Bounds.x), Math.max(1, this.Bounds.b - this.Bounds.y)]; const [moveX, moveY] = [thisPt.x - this._snapPt.x, thisPt.y - this._snapPt.y]; switch (dragHdl) { case 'topLeft': return { scale: { x: 1 - moveX / w, y: 1 -moveY / h }, refPt: [this.Bounds.r, this.Bounds.b] }; @@ -566,8 +566,8 @@ export class DocumentDecorations extends ObservableReactComponent() { @@ -56,6 +57,8 @@ export class EquationBox extends ViewBoxBaseComponent() { { fireImmediately: true } ); } + @computed get fontSize() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize) as string; } // prettier-ignore + @computed get fontColor() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontColor) as string; } // prettier-ignore @action keyPressed = (e: KeyboardEvent) => { @@ -68,6 +71,9 @@ export class EquationBox extends ViewBoxBaseComponent() { _height: 25, x: NumCast(this.layoutDoc.x), y: NumCast(this.layoutDoc.y) + _height + 10, + backgroundColor: StrCast(this.Document.backgroundColor), + color: StrCast(this.Document.color), + fontSize: this.fontSize, }); Doc.SetSelectOnLoad(nextEq); this._props.addDocument?.(nextEq); @@ -118,10 +124,12 @@ export class EquationBox extends ViewBoxBaseComponent() { className="equationBox-cont" onPointerDown={e => !e.ctrlKey && e.stopPropagation()} style={{ + minWidth: `${100 / scale}%`, transform: `scale(${scale})`, height: `${100 / scale}%`, pointerEvents: !this._props.isSelected() ? 'none' : undefined, - fontSize: StrCast(this.Document._text_fontSize), + fontSize: this.fontSize, + color: this.fontColor, }} onKeyDown={e => e.stopPropagation()}> @@ -132,5 +140,5 @@ export class EquationBox extends ViewBoxBaseComponent() { Docs.Prototypes.TemplateMap.set(DocumentType.EQUATION, { layout: { view: EquationBox, dataField: 'text' }, - options: { acl: '', fontSize: '14px', _layout_reflowHorizontal: true, _layout_reflowVertical: true, _layout_nativeDimEditable: true, layout_hideDecorationTitle: true, systemIcon: 'BsCalculatorFill' }, // systemIcon: 'BsSuperscript' + BsSubscript + options: { acl: '', fontSize: '14px', _nativeWidth: 35, _nativeHeight: 35, _layout_reflowHorizontal: false, _layout_reflowVertical: false, _layout_nativeDimEditable: false, layout_hideDecorationTitle: true, systemIcon: 'BsCalculatorFill' }, // systemIcon: 'BsSuperscript' + BsSubscript }); -- cgit v1.2.3-70-g09d2 From d57fdc07fb88282903157b414c4a0886ddaf8bc6 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 12 Nov 2024 21:06:42 -0500 Subject: updated equationBox to support margins/padding like text. fixed initial undo of newly typed equation. --- src/client/documents/DocUtils.ts | 8 +++++-- src/client/documents/Documents.ts | 6 +++-- src/client/util/CurrentUserUtils.ts | 2 +- src/client/views/PropertiesView.tsx | 2 +- src/client/views/nodes/EquationBox.tsx | 42 ++++++++++++++++++++++++++-------- 5 files changed, 44 insertions(+), 16 deletions(-) (limited to 'src/client/documents/DocUtils.ts') diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts index c466344b3..d11a3e235 100644 --- a/src/client/documents/DocUtils.ts +++ b/src/client/documents/DocUtils.ts @@ -696,8 +696,12 @@ export namespace DocUtils { y, title, text_fontColor: StrCast(Doc.UserDoc().fontColor), - _width: 35, - _height: 35, + _width: 50, + _height: 50, + _yMargin: 10, + _xMargin: 10, + nativeWidth: 40, + nativeHeight: 40, }) : Docs.Create.TextDocument('', { annotationOn, diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index d898fe0c5..6828a1929 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -197,8 +197,10 @@ export class DocumentOptions { data_nativeWidth?: NUMt = new NumInfo('native width of data field contents (e.g., the pixel width of an image)', false); data_nativeHeight?: NUMt = new NumInfo('native height of data field contents (e.g., the pixel height of an image)', false); linearBtnWidth?: NUMt = new NumInfo('unexpanded width of a linear menu button (button "width" changes when it expands)', false); - _nativeWidth?: NUMt = new NumInfo('native width of document contents (e.g., the pixel width of an image)', false); - _nativeHeight?: NUMt = new NumInfo('native height of document contents (e.g., the pixel height of an image)', false); + _nativeWidth?: NUMt = new NumInfo('Deprecated: use nativeWidth. native width of document contents (e.g., the pixel width of an image)', false); + _nativeHeight?: NUMt = new NumInfo('Deprecated: use nativeHeight. native height of document contents (e.g., the pixel height of an image)', false); + nativeWidth?: NUMt = new NumInfo('native width of document contents (e.g., the pixel width of an image)', false); + nativeHeight?: NUMt = new NumInfo('native height of document contents (e.g., the pixel height of an image)', false); acl?: STRt = new StrInfo('unused except as a display category in KeyValueBox'); acl_Guest?: STRt = new StrInfo("permissions granted to users logged in as 'guest' (either view, or private)"); // public permissions diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index c01f57a59..49a4a981a 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -372,7 +372,7 @@ pie title Minerals in my tap water {key: "Note", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true }}, {key: "Flashcard", creator: opts => Docs.Create.FlashcardDocument("", undefined, undefined, opts),opts: { _width: 300, _height: 300}}, {key: "Image", creator: opts => Docs.Create.ImageDocument("", opts), opts: { _width: 400, _height:400 }}, - {key: "Equation", creator: opts => Docs.Create.EquationDocument("",opts), opts: { _width: 35, _height: 35, }}, + {key: "Equation", creator: opts => Docs.Create.EquationDocument("",opts), opts: { _width: 50, _height: 50, nativeWidth: 40, nativeHeight: 40, _xMargin: 10, _yMargin: 10}}, {key: "Noteboard", creator: opts => Docs.Create.NoteTakingDocument([], opts), opts: { _width: 250, _height: 200, _layout_fitWidth: true}}, {key: "Simulation", creator: opts => Docs.Create.SimulationDocument(opts), opts: { _width: 300, _height: 300, }}, {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 150, _height: 100, _layout_fitWidth: true }}, diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 2bd8bc98a..1158d93e9 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -141,7 +141,7 @@ export class PropertiesView extends ObservableReactComponent() { @@ -68,7 +69,7 @@ export class EquationBox extends ViewBoxBaseComponent() { const nextEq = Docs.Create.EquationDocument(e.shiftKey ? StrCast(this.dataDoc.text) : 'x', { title: '# math', _width, - _height: 25, + _height: 20, x: NumCast(this.layoutDoc.x), y: NumCast(this.layoutDoc.y) + _height + 10, backgroundColor: StrCast(this.Document.backgroundColor), @@ -102,27 +103,32 @@ export class EquationBox extends ViewBoxBaseComponent() { updateSize = () => { const style = this._ref.current?.element.current && getComputedStyle(this._ref.current.element.current); if (style?.width.endsWith('px') && style?.height.endsWith('px')) { + const mathWidth = Math.max(35, NumCast(this.layoutDoc.xMargin) * 2 + Number(style.width.replace('px', ''))); + const mathHeight = Math.max(20, NumCast(this.layoutDoc.yMargin) * 2 + Number(style.height.replace('px', ''))); if (this.layoutDoc._nativeWidth) { // if equation has been scaled then editing the expression must also edit the native dimensions to keep the aspect ratio const prevNwidth = NumCast(this.layoutDoc._nativeWidth); - const newNwidth = (this.layoutDoc._nativeWidth = Math.max(35, Number(style.width.replace('px', '')))); - const newNheight = (this.layoutDoc._nativeHeight = Math.max(25, Number(style.height.replace('px', '')))); - this.layoutDoc._width = (NumCast(this.layoutDoc._width) * NumCast(this.layoutDoc._nativeWidth)) / prevNwidth; - this.layoutDoc._height = (NumCast(this.layoutDoc._width) * newNheight) / newNwidth; + this.layoutDoc._nativeWidth = mathWidth; + this.layoutDoc._nativeHeight = mathHeight; + this.layoutDoc._width = mathWidth * (NumCast(this.layoutDoc._width) / prevNwidth); + this.layoutDoc._height = mathHeight * (NumCast(this.layoutDoc._width) / mathWidth); } else { - this.layoutDoc._width = Math.max(35, Number(style.width.replace('px', ''))); - this.layoutDoc._height = Math.max(25, Number(style.height.replace('px', ''))); + this.layoutDoc._width = mathWidth; + this.layoutDoc._height = mathHeight; } } }; render() { TraceMobx(); - const scale = (this._props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1); + const scale = this._props.NativeDimScaling?.() || 1; return (
this.updateSize()} className="equationBox-cont" onPointerDown={e => !e.ctrlKey && e.stopPropagation()} + onBlur={() => { + FormattedTextBox.LiveTextUndo?.end(); + }} style={{ minWidth: `${100 / scale}%`, transform: `scale(${scale})`, @@ -130,6 +136,10 @@ export class EquationBox extends ViewBoxBaseComponent() { pointerEvents: !this._props.isSelected() ? 'none' : undefined, fontSize: this.fontSize, color: this.fontColor, + paddingLeft: NumCast(this.layoutDoc.xMargin), + paddingRight: NumCast(this.layoutDoc.xMargin), + paddingTop: NumCast(this.layoutDoc.yMargin), + paddingBottom: NumCast(this.layoutDoc.yMargin), }} onKeyDown={e => e.stopPropagation()}> @@ -140,5 +150,17 @@ export class EquationBox extends ViewBoxBaseComponent() { Docs.Prototypes.TemplateMap.set(DocumentType.EQUATION, { layout: { view: EquationBox, dataField: 'text' }, - options: { acl: '', fontSize: '14px', _nativeWidth: 35, _nativeHeight: 35, _layout_reflowHorizontal: false, _layout_reflowVertical: false, _layout_nativeDimEditable: false, layout_hideDecorationTitle: true, systemIcon: 'BsCalculatorFill' }, // systemIcon: 'BsSuperscript' + BsSubscript + options: { + acl: '', + _xMargin: 10, + _yMargin: 10, + fontSize: '14px', + _nativeWidth: 40, + _nativeHeight: 40, + _layout_reflowHorizontal: false, + _layout_reflowVertical: false, + _layout_nativeDimEditable: false, + layout_hideDecorationTitle: true, + systemIcon: 'BsCalculatorFill', + }, // systemIcon: 'BsSuperscript' + BsSubscript }); -- cgit v1.2.3-70-g09d2 From 1e673454f8cabf894e8dfec36734d2cb41caa7b1 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 3 Feb 2025 10:23:56 -0500 Subject: changed filter 'check' to be exact match and 'match' to be substring. fixed GPTpopup filtering to use a #chat tag instead of a chatFilter field. --- src/client/apis/gpt/GPT.ts | 2 +- src/client/documents/DocUtils.ts | 4 +- src/client/util/CurrentUserUtils.ts | 3 +- src/client/views/TagsView.scss | 4 +- src/client/views/TagsView.tsx | 2 +- .../views/collections/CollectionCardDeckView.tsx | 11 +++-- src/client/views/global/globalScripts.ts | 4 +- src/client/views/nodes/ComparisonBox.tsx | 2 +- src/client/views/nodes/IconTagBox.scss | 2 - src/client/views/pdf/GPTPopup/GPTPopup.tsx | 57 ++++++++++------------ 10 files changed, 44 insertions(+), 47 deletions(-) (limited to 'src/client/documents/DocUtils.ts') diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index 269a4284a..1894bb4df 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -42,7 +42,7 @@ const callTypeMap: { [type: string]: GPTCallOpts } = { model: 'gpt-4o', maxTokens: 2048, temp: 0.7, - prompt: 'Create a stack of flashcards out of this text with each question and answer labeled as question and answer. Create a title that represents the question in just a few words and label it "title". For some questions, ask "what is this image of" but tailored to stacks theme and the image and write a keyword that represents the image and label it "keyword". Otherwise, write none. Do not label each flashcard and do not include asterisks.', + prompt: 'Create a stack of at least 10 flashcards out of this text with each question and answer labeled as question and answer. Each flashcard should have a title that represents the question in just a few words and label it "title". For some questions, ask "what is this image of" but tailored to stacks theme and the image and write a keyword that represents the image and label it "keyword". Otherwise, write none. Do not label each flashcard and do not include asterisks.', }, completion: { model: 'gpt-4-turbo', maxTokens: 256, temp: 0.5, prompt: "You are a helpful assistant. Answer the user's prompt." }, diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts index d11a3e235..23032b62e 100644 --- a/src/client/documents/DocUtils.ts +++ b/src/client/documents/DocUtils.ts @@ -72,9 +72,9 @@ export namespace DocUtils { } const vals = StrListCast(fieldVal); // list typing is very imperfect. casting to a string list doesn't mean that the entries will actually be strings if (vals.length) { - return vals.some(v => typeof v === 'string' && v.includes(value as string)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring + return vals.some(v => typeof v === 'string' && v === (value as string)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring } - return Field.toString(fieldVal as FieldType).includes(value as string); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring + return Field.toString(fieldVal as FieldType) === (value as string); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring } /** * @param docs diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index b75961575..0fd09b479 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -728,7 +728,8 @@ pie title Minerals in my tap water this.filterBtnDesc("Star", "star"), this.filterBtnDesc("Like", "heart"), this.filterBtnDesc("Todo", "bolt"), - this.filterBtnDesc("Idea", "cloud") + this.filterBtnDesc("Idea", "cloud"), + this.filterBtnDesc("Chat", "robot") ]; return [ { title:"Options",isSystem: true,icon: "gear", toolTip:"Click to customize list of filter buttons", btnType: ButtonType.ClickButton, expertMode: false, toolType:"-opts-",funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, false,_readOnly_);}'}}, diff --git a/src/client/views/TagsView.scss b/src/client/views/TagsView.scss index 303a08e1e..b21d303fb 100644 --- a/src/client/views/TagsView.scss +++ b/src/client/views/TagsView.scss @@ -12,7 +12,7 @@ .tagsView-list { display: flex; flex-wrap: wrap; - height: inherit; + height: 1; .iconButton-container { min-height: unset !important; } @@ -58,7 +58,7 @@ } .tagsView-editing-box { - margin-top: 8px; + margin-top: 20px; } .tagsView-input-box { diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx index 9e936ea54..b70e21918 100644 --- a/src/client/views/TagsView.tsx +++ b/src/client/views/TagsView.tsx @@ -146,7 +146,7 @@ export class TagItem extends ObservableReactComponent { } } } - doc[DocData].tags = new List((doc[DocData].tags as List).filter(label => label !== tag)); + doc[DocData].tags = new List(StrListCast(doc[DocData].tags).filter(label => label !== tag)); }; private _ref: React.RefObject; diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index f24673c39..43464e50c 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -85,6 +85,10 @@ export class CollectionCardView extends CollectionSubView() { }); componentDidMount() { + this._disposers.chatVis = reaction( + () => GPTPopup.Instance.Visible, + vis => !vis && this.onGptHide() + ); GPTPopup.Instance.setRegenerateCallback(this.Document, this.childPairStringListAndUpdateSortDesc); this._props.setContentViewBox?.(this); // if card deck moves, then the child doc views are hidden so their screen to local transforms will return empty rectangles @@ -106,6 +110,7 @@ export class CollectionCardView extends CollectionSubView() { ); } + onGptHide = () => Doc.setDocFilter(this.Document, 'tags', '#chat', 'remove'); componentWillUnmount() { GPTPopup.Instance.setSortDesc(''); GPTPopup.Instance.onSortComplete = undefined; @@ -445,7 +450,7 @@ export class CollectionCardView extends CollectionSubView() { if (questionType === '2' || questionType === '4') { this.childDocs.forEach(d => { - d.chatFilter = false; + TagItem.removeTagFromDoc(d, '#chat'); }); } @@ -471,8 +476,8 @@ export class CollectionCardView extends CollectionSubView() { break; case '2': case '4': - doc.chatFilter = true; - Doc.setDocFilter(DocCast(this.Document), 'chatFilter', true, 'check'); + TagItem.addTagToDoc(doc, '#chat'); + Doc.setDocFilter(this.Document, 'tags', '#chat', 'check'); break; } } else { diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index b02eabab0..b44292164 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -223,9 +223,9 @@ ScriptingGlobals.add(function showFreeform( setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort_desc"] = true; }, }], ['toggle-chat', { - checkResult: (doc: Doc) => GPTPopup.Instance.visible, + checkResult: (doc: Doc) => GPTPopup.Instance.Visible, setDoc: (doc: Doc, dv: DocumentView) => { - if (GPTPopup.Instance.visible){ + if (GPTPopup.Instance.Visible){ doc[Doc.LayoutFieldKey(doc)+"_sort"] = ''; GPTPopup.Instance.setVisible(false); diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index f5291a4c1..53fbd11c5 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -510,7 +510,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() * Calls GPT for each flashcard type. */ askGPT = async (callType: GPTCallType) => { - const questionText = 'Question: ' + this.frontText; + const questionText = this.frontText; const queryText = questionText + (callType == GPTCallType.QUIZ ? ' UserAnswer: ' + this._inputValue + '. ' + ' Rubric: ' + this.backText : ''); this.loading = true; diff --git a/src/client/views/nodes/IconTagBox.scss b/src/client/views/nodes/IconTagBox.scss index 90cc06092..c79d662f4 100644 --- a/src/client/views/nodes/IconTagBox.scss +++ b/src/client/views/nodes/IconTagBox.scss @@ -10,8 +10,6 @@ gap: 5px; padding-left: 5px; padding-right: 5px; - padding-top: 2px; - padding-bottom: 2px; button { pointer-events: auto; diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index da0cbea7a..f5a9f9e6a 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -46,9 +46,9 @@ export class GPTPopup extends ObservableReactComponent { @observable private chatMode: boolean = false; private correlatedColumns: string[] = []; - @observable public visible: boolean = false; + @observable public Visible: boolean = false; @action public setVisible = (vis: boolean) => { - this.visible = vis; + this.Visible = vis; }; @observable public loading: boolean = false; @action public setLoading = (loading: boolean) => { @@ -114,8 +114,8 @@ export class GPTPopup extends ObservableReactComponent { this.sortDesc = t; }; - @observable onSortComplete?: (sortResult: string, questionType: string, tag?: string) => void; - @observable onQuizRandom?: () => void; + onSortComplete?: (sortResult: string, questionType: string, tag?: string) => void; + onQuizRandom?: () => void; @observable cardsDoneLoading = false; @observable collectionDoc: Doc | undefined = undefined; @@ -154,30 +154,27 @@ export class GPTPopup extends ObservableReactComponent { generateQuiz = async () => { this.setLoading(true); - const selected = DocumentView.SelectedDocs().lastElement(); + await this.regenerateCallback?.(); - const questionText = 'Question: ' + StrCast(selected.gptInputText); - const rubricText = 'Rubric: ' + StrCast(selected.gptRubric); - const queryText = questionText + ' UserAnswer: ' + this.quizAnswer + '. ' + rubricText; + const selected = DocumentView.SelectedDocs().lastElement(); + if (!StrCast(selected.gptRubric)) { + await this.generateRubric(StrCast(selected.gptInputText), selected); + } try { - const res = await gptAPICall(queryText, GPTCallType.QUIZ); - if (!res) { - console.error('GPT call failed'); - return; - } - console.log(res); - this.setQuizResp(res); - this.conversationArray.push(res); + const res = await gptAPICall('Question: ' + StrCast(selected.gptInputText) + ' UserAnswer: ' + this.quizAnswer + '. Rubric: ' + StrCast(selected.gptRubric), GPTCallType.QUIZ); + if (res) { + this.setQuizResp(res); + this.conversationArray.push(res); - this.setLoading(false); + this.setLoading(false); + this.onQuizRandom?.(); + } else { + console.error('GPT provided no response'); + } } catch (err) { console.error('GPT call failed', err); } - - if (this.onQuizRandom) { - this.onQuizRandom(); - } }; /** @@ -223,9 +220,7 @@ export class GPTPopup extends ObservableReactComponent { generateCard = async () => { this.setLoading(true); - if (this.regenerateCallback) { - await this.regenerateCallback(); - } + await this.regenerateCallback?.(); try { const questionType = await gptAPICall(this.chatSortPrompt, GPTCallType.TYPE); @@ -442,9 +437,7 @@ export class GPTPopup extends ObservableReactComponent { onClick={() => { this.conversationArray = ['Define the selected card!']; this.setMode(GPTPopupMode.QUIZ); - if (this.onQuizRandom) { - this.onQuizRandom(); - } + this.onQuizRandom?.(); }} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} @@ -694,13 +687,13 @@ export class GPTPopup extends ObservableReactComponent { } onClick={() => (this.mode = GPTPopupMode.CARD)} style={{ right: '50px', position: 'absolute' }} /> )} this.collectionDoc && (this.collectionDoc.childFilters = undefined)} + onClick={() => this.collectionDoc && Doc.setDocFilter(this.collectionDoc, 'tags', '#chat', 'remove')} /> { } return ( -
+
{content}
-- cgit v1.2.3-70-g09d2