From 29b83f023442c313ca5cf95f70f6430f101060e6 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 14 Oct 2024 19:55:32 -0400 Subject: reorganized comparisonBox related components -- moved stuff down into Docs.Crete and CurrentUserUtils. changed Doc.Copy to copy Doc's in fields tagged with cloneOnCopy. Changed ComparisonBox to support hover for slide or flip views. Fixed pointerEfvents for hover in comparisonBox --- src/client/documents/DocUtils.ts | 2 -- 1 file changed, 2 deletions(-) (limited to 'src/client/documents/DocUtils.ts') diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts index 5f54f9d0a..19f3c89ef 100644 --- a/src/client/documents/DocUtils.ts +++ b/src/client/documents/DocUtils.ts @@ -103,7 +103,6 @@ export namespace DocUtils { return false; } const facetKeys = Object.keys(filterFacets).filter(fkey => fkey !== 'cookies' && fkey !== ClientUtils.noDragDocsFilter.split(Doc.FilterSep)[0]); - // eslint-disable-next-line no-restricted-syntax for (const facetKey of facetKeys) { const facet = filterFacets[facetKey]; @@ -288,7 +287,6 @@ export namespace DocUtils { return doc; } export function AssignDocField(doc: Doc, field: string, creator: (reqdOpts: DocumentOptions, items?: Doc[]) => Doc, reqdOpts: DocumentOptions, items?: Doc[], scripts?: { [key: string]: string }, funcs?: { [key: string]: string }) { - // eslint-disable-next-line no-return-assign return DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(doc[field]), reqdOpts, items) ?? (doc[field] = creator(reqdOpts, items)), scripts, funcs); } -- cgit v1.2.3-70-g09d2 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. --- package-lock.json | 8 +- package.json | 2 +- src/client/documents/DocUtils.ts | 5 +- src/client/documents/Documents.ts | 14 +- src/client/util/CurrentUserUtils.ts | 9 +- src/client/util/SettingsManager.tsx | 24 +- src/client/views/MarqueeAnnotator.tsx | 3 +- src/client/views/PinFuncs.ts | 4 +- src/client/views/SidebarAnnos.tsx | 4 +- src/client/views/StyleProvider.tsx | 4 +- src/client/views/StyleProviderQuiz.tsx | 2 +- .../views/collections/CollectionCardDeckView.tsx | 1 - .../CollectionFreeFormClusters.ts | 6 +- .../collections/collectionSchema/SchemaRowBox.tsx | 2 +- src/client/views/global/globalScripts.ts | 24 +- .../views/nodes/DataVizBox/DocCreatorMenu.tsx | 35 +-- src/client/views/nodes/DocumentView.tsx | 12 +- src/client/views/nodes/EquationBox.tsx | 2 +- .../views/nodes/FontIconBox/FontIconBox.scss | 4 + src/client/views/nodes/FontIconBox/FontIconBox.tsx | 4 +- src/client/views/nodes/LabelBox.scss | 1 - src/client/views/nodes/LabelBox.tsx | 168 +++++++++---- .../views/nodes/formattedText/FormattedTextBox.tsx | 259 +++++++++++---------- .../formattedText/ProsemirrorExampleTransfer.ts | 4 +- .../views/nodes/formattedText/RichTextMenu.tsx | 71 +++--- .../views/nodes/formattedText/RichTextRules.ts | 2 +- src/client/views/nodes/trails/PresBox.tsx | 14 +- src/client/views/pdf/GPTPopup/GPTPopup.tsx | 23 +- src/fields/documentSchemas.ts | 7 +- 29 files changed, 426 insertions(+), 292 deletions(-) (limited to 'src/client/documents/DocUtils.ts') diff --git a/package-lock.json b/package-lock.json index 4e95fcee0..43dcb1c3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -72,7 +72,7 @@ "body-parser": "^1.20.2", "bootstrap": "^5.3.2", "brotli": "^1.3.3", - "browndash-components": "^0.1.50", + "browndash-components": "0.1.53", "browser-assert": "^1.2.1", "bson": "^6.2.0", "canvas": "^2.11.2", @@ -13575,9 +13575,9 @@ } }, "node_modules/browndash-components": { - "version": "0.1.50", - "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.1.50.tgz", - "integrity": "sha512-8EgU82os8/Tg3gayh+nYXCQZ8P7GGQWnibz0U2RM0sXTbc3BWTSYiC1Sj1VpL7HPkZ1KMGHzWynCo9QKIOWZBg==", + "version": "0.1.53", + "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.1.53.tgz", + "integrity": "sha512-lBZlXe2u/6d9NlrR+fH+9ncCFGRUxGqJRejpfhbhVmQ4Q4uvP9LlmTuaVQElqys5wGUyK0SuqrLYPGhJthdQLQ==", "dependencies": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", diff --git a/package.json b/package.json index 06179fd7d..63d5e387d 100644 --- a/package.json +++ b/package.json @@ -155,7 +155,7 @@ "body-parser": "^1.20.2", "bootstrap": "^5.3.2", "brotli": "^1.3.3", - "browndash-components": "^0.1.50", + "browndash-components": "0.1.53", "browser-assert": "^1.2.1", "bson": "^6.2.0", "canvas": "^2.11.2", 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), }), }); diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 98e8bc932..b0e1a7545 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -294,7 +294,6 @@ export class DocumentOptions { _yMargin?: NUMt = new NumInfo('gap between top edge of dcoument and start of masonry/stacking layouts', false); _xPadding?: NUMt = new NumInfo('x padding', false); _yPadding?: NUMt = new NumInfo('y padding', false); - _singleLine?: boolean; // whether label box is restricted to one line of text _createDocOnCR?: boolean; // whether carriage returns and tabs create new text documents _columnWidth?: NUMt = new NumInfo('width of table column', false); _columnsHideIfEmpty?: BOOLt = new BoolInfo('whether stacking view column headings should be hidden'); @@ -302,10 +301,14 @@ export class DocumentOptions { _caption_yMargin?: NUMt = new NumInfo('y margin of caption inside of a carousel collection', false, true); icon_nativeWidth?: NUMt = new NumInfo('native width of icon view', false, true); icon_nativeHeight?: NUMt = new NumInfo('native height of icon view', false, true); - _text_fontSize?: string; - _text_fontFamily?: string; - _text_fontWeight?: string; - text_align?: STRt = new StrInfo('horizontal text alignment default'); + text_fontSize?: string; + text_fontFamily?: string; + text_fontWeight?: string; + text_fitBox?: BOOLt = new BoolInfo("whether text box should be scaled to fit it's containing render box"); + text_align?: STRt = new StrInfo('horizontal text alignment default', undefined, undefined, ['left', 'center', 'right']); + title_align?: STRt = new StrInfo('horizontal title alignment in label box', undefined, undefined, ['left', 'center', 'right']); + title_transform?: STRt = new StrInfo('transformation to apply to title in label box (eg., uppercase)', undefined, undefined, ['uppercase', 'lowercase', 'capitalize']); + text_transform?: STRt = new StrInfo('transformation to apply to text in text box (eg., uppercase)', undefined, undefined, ['uppercase', 'lowercase', 'capitalize']); text_placeholder?: BOOLt = new BoolInfo('makes the text act like a placeholder and automatically select when the text box is selected'); fontSize?: string; _pivotField?: string; // field key used to determine headings for sections in stacking, masonry, pivot views @@ -496,7 +499,6 @@ export class DocumentOptions { sidebar_type_collection?: string; // collection type of text sidebar data_dashboards?: List; // list of dashboards used in shareddocs; - textTransform?: string; letterSpacing?: string; iconTemplate?: string; // name of icon template style icon_fieldKey?: string; // specifies the icon template to use (e.g., icon_fieldKey='george', then the icon template's name is icon_george; otherwise, the template's name would be icon_ where type is the Doc's type(pdf,rich text, etc)) diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 30c75c659..b3aa5e145 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -186,7 +186,7 @@ export class CurrentUserUtils { const templateIconsDoc = DocUtils.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts)); const labelBox = (opts: DocumentOptions, fieldKey:string) => Docs.Create.LabelDocument({ - layout: LabelBox.LayoutString(fieldKey), textTransform: "unset", letterSpacing: "unset", _singleLine: false, _label_minFontSize: 14, _label_maxFontSize: 14, layout_borderRounding: "5px", _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts + layout: LabelBox.LayoutString(fieldKey), letterSpacing: "unset", _label_minFontSize: 14, _label_maxFontSize: 14, layout_borderRounding: "5px", _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts }); const imageBox = (opts: DocumentOptions, fieldKey:string) => Docs.Create.ImageDocument( "http://www.cs.brown.edu/~bcz/noImage.png", { layout:ImageBox.LayoutString(fieldKey), "icon_nativeWidth": 360 / 4, "icon_nativeHeight": 270 / 4, iconTemplate:DocumentType.IMG, _width: 360 / 4, _height: 270 / 4, _layout_showTitle: "title", ...opts }); const fontBox = (opts:DocumentOptions, fieldKey:string) => Docs.Create.FontIconDocument({ layout:FontIconBox.LayoutString(fieldKey), _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, ...opts }); @@ -268,7 +268,7 @@ export class CurrentUserUtils { MakeTemplate(Docs.Create.MultirowDocument( [ Docs.Create.MulticolumnDocument([], { title: "hero", _height: 200, isSystem: true }), - Docs.Create.TextDocument("", { title: "text", _layout_fitWidth:true, _height: 100, isSystem: true, _text_fontFamily: StrCast(Doc.UserDoc().fontFamily), _text_fontSize: StrCast(Doc.UserDoc().fontSize) }) + Docs.Create.TextDocument("", { title: "text", _layout_fitWidth:true, _height: 100, isSystem: true, text_fontFamily: StrCast(Doc.UserDoc().fontFamily), text_fontSize: StrCast(Doc.UserDoc().fontSize) }) ], {...opts, title: "Slide View Template"})); const plotlyApi = () => { let plotly = Doc.MyPublishedDocs.find(fdoc => fdoc.title === "@plotly"); @@ -392,7 +392,7 @@ pie title Minerals in my tap water {key: "Trail", creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, _layout_dontCenter:'xy', dropAction: dropActionType.embed, treeView_HideTitle: true, _layout_fitWidth:true, layout_boxShadow: "0 0" }}, {key: "Tab", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 500, _height: 800, _layout_fitWidth: true, _freeform_backgroundGrid: true, }}, {key: "Slide", creator: opts => Docs.Create.TreeDocument([], opts), opts: { _width: 300, _height: 200, _type_collection: CollectionViewType.Tree, - treeView_HasOverlay: true, _text_fontSize: "20px", _layout_autoHeight: true, + treeView_HasOverlay: true, text_fontSize: "20px", _layout_autoHeight: true, dropAction:dropActionType.move, treeView_Type: TreeViewType.outline, backgroundColor: "white", _xMargin: 0, _yMargin: 0, _createDocOnCR: true }, funcs: {title: 'this.text?.Text'}}, @@ -731,7 +731,7 @@ 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: 6 }, + { 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: "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_);}'} }, @@ -746,6 +746,7 @@ pie title Minerals in my tap water { title: "Center", toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center",ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} }, { title: "Right", toolTip: "Right align (Cmd-])", btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} }, ]}, + { title: "Fit Box", toolTip: "Fit text to box", btnType: ButtonType.ToggleButton, icon: "object-group",toolType:"fitBox", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} }, { title: "Elide", toolTip: "Elide selection", btnType: ButtonType.ToggleButton, icon: "eye", toolType:"elide", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} }, { title: "Dictate", toolTip: "Dictate", btnType: ButtonType.ToggleButton, icon: "microphone", toolType:"dictation", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} }, { title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", toolType:"noAutoLink", expertMode:true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'}, funcs: {hidden: 'IsNoviceMode()'}}, diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index 9200d68db..33d7d90a8 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Button, ColorPicker, Dropdown, DropdownType, EditableText, Group, NumberDropdown, Size, Toggle, ToggleType, Type } from 'browndash-components'; +import { Button, ColorPicker, Colors, Dropdown, DropdownType, EditableText, Group, NumberDropdown, Size, Toggle, ToggleType, Type } from 'browndash-components'; import { action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -45,7 +45,6 @@ export class SettingsManager extends React.Component { public closeMgr = action(() => { this._isOpen = false; }); - // eslint-disable-next-line react/no-unused-class-component-methods public openMgr = action(() => { this._isOpen = true; }); @@ -354,6 +353,27 @@ export class SettingsManager extends React.Component { Doc.UserDoc().fontSize = val + 'px'; }} /> + } + tooltip="default text background color" + label="background" + setSelectedColor={value => { + Doc.UserDoc().textBackgroundColor = value; + // if (!this.colorBatch) this.colorBatch = UndoManager.StartBatch(`Set ${tooltip} color`); + // this.colorScript?.script.run({ this: this.Document, value: value, _readOnly_: false }); + }} + setFinalColor={value => { + Doc.UserDoc().textBackgroundColor = value; + // this.colorScript?.script.run({ this: this.Document, value: value, _readOnly_: false }); + // this.colorBatch?.end(); + // this.colorBatch = undefined; + }} + /> ({ text: val, diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index 7266875c5..fa1123a2d 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -34,7 +34,7 @@ export interface MarqueeAnnotatorProps { getPageFromScroll?: (top: number) => number; finishMarquee: (x?: number, y?: number) => void; anchorMenuClick?: () => undefined | ((anchor: Doc) => void); - anchorMenuFlashcard?: () => Promise; + anchorMenuFlashcard?: () => Promise; anchorMenuCrop?: (anchor: Doc | undefined, addCrop: boolean) => Doc | undefined; highlightDragSrcColor?: string; } @@ -220,7 +220,6 @@ export class MarqueeAnnotator extends ObservableReactComponent (cropRegion = this.highlight('', true, undefined, true)); // hyperlink color const targetCreator = (/* annotationOn: Doc | undefined */) => this.props.anchorMenuCrop!(cropRegion, false)!; DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView(), sourceAnchorCreator, targetCreator), e.pageX, e.pageY, { diff --git a/src/client/views/PinFuncs.ts b/src/client/views/PinFuncs.ts index bed473eec..ab02c2d07 100644 --- a/src/client/views/PinFuncs.ts +++ b/src/client/views/PinFuncs.ts @@ -1,4 +1,4 @@ -import { Doc, DocListCast } from '../../fields/Doc'; +import { Doc, DocListCast, Field } from '../../fields/Doc'; import { DocData } from '../../fields/DocSymbols'; import { Copy, Id } from '../../fields/FieldSymbols'; import { List } from '../../fields/List'; @@ -74,7 +74,7 @@ export function PinDocView(pinDocIn: Doc, pinProps: PinProps, targetDoc: Doc) { const fkey = Doc.LayoutFieldKey(targetDoc); if (pinProps.pinData.dataview) { pinDoc.config_usePath = targetDoc[fkey + '_usePath']; - pinDoc.config_data = targetDoc[fkey] instanceof ObjectField ? (targetDoc[fkey] as ObjectField)[Copy]() : targetDoc.data; + pinDoc.config_data = Field.Copy(targetDoc[fkey]); } if (pinProps.pinData.dataannos) { const fieldKey = Doc.LayoutFieldKey(targetDoc); diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 1f3ad8444..87076bf65 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -78,8 +78,8 @@ export class SidebarAnnos extends ObservableReactComponent, props: Opt, props: Opt, props: Opt, property: string) => { - if (doc && this.childDocs?.includes(doc)) + // without untracked, every inquired style property for any Doc will be invalidated if a change is made to the collection's childDocs. + // this prevents that by assuming that a Doc is generally always (or never) a member of childDocs - if it's removed or added, then all of its properties get updated anyway. + if (doc && untracked(() => this.childDocs)?.includes(doc)) switch (property.split(':')[0]) { case StyleProp.BackgroundColor: { diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx index 6ffb0865a..7f519b065 100644 --- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx +++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx @@ -188,7 +188,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent() { selectedCells={this.selectedCells} selectedCol={this.selectedCol} setColumnValues={this.setColumnValues} - oneLine={BoolCast(this.schemaDoc?._singleLine)} + oneLine={BoolCast(this.schemaDoc?._schema_singleLine)} menuTarget={this.schemaView.MenuTarget} transform={() => { const ind = index === this.schemaView.columnKeys.length - 1 ? this.schemaView.columnKeys.length - 3 : index; diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 954c79f7d..013b18a97 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -295,10 +295,10 @@ ScriptingGlobals.add(function setTagFilter(tag: string, added: boolean, checkRes }, ''); // eslint-disable-next-line prefer-arrow-callback -ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highlight' | 'fontSize' | 'alignment', value: string | number, checkResult?: boolean) { +ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highlight' | 'fontSize', value: string | number, checkResult?: boolean) { const editorView = RichTextMenu.Instance?.TextView?.EditorView; // prettier-ignore - const map: Map<'font'|'fontColor'|'highlight'|'fontSize'|'alignment', { checkResult: () => string | undefined; setDoc: () => void;}> = new Map([ + const map: Map<'font'|'fontColor'|'highlight'|'fontSize', { checkResult: () => string | undefined; setDoc: () => void;}> = new Map([ ['font', { checkResult: () => RichTextMenu.Instance?.fontFamily, setDoc: () => value && RichTextMenu.Instance?.setFontField(value.toString(), 'fontFamily'), @@ -311,10 +311,6 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh checkResult: () => RichTextMenu.Instance?.fontColor, setDoc: () => value && RichTextMenu.Instance?.setFontField(value.toString(), 'fontColor'), }], - ['alignment', { - checkResult: () => RichTextMenu.Instance?.textAlign, - setDoc: () => { value && editorView?.state ? RichTextMenu.Instance?.align(editorView, editorView.dispatch, value.toString() as "center"|"left"|"right"):(Doc.UserDoc().textAlign = value); }, - }], ['fontSize', { checkResult: () => RichTextMenu.Instance?.fontSize.replace('px', ''), setDoc: () => { @@ -334,7 +330,7 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh return undefined; }); -type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'elide' | 'underline' | 'left' | 'center' | 'right' | 'vcent' | 'bullet' | 'decimal'; +type attrname = 'noAutoLink' | 'dictation' | 'fitBox' | 'bold' | 'italics' | 'elide' | 'underline' | 'left' | 'center' | 'right' | 'vcent' | 'bullet' | 'decimal'; type attrfuncs = [attrname, { checkResult: () => boolean; toggle?: () => unknown }]; // eslint-disable-next-line prefer-arrow-callback @@ -343,14 +339,10 @@ ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: const editorView = textView?.EditorView; // prettier-ignore const alignments:attrfuncs[] = (['left','right','center','vcent'] as ("left"|"center"|"right"|"vcent")[]).map((where) => - [ where, { checkResult: () => editorView ? (where === 'vcent' ? RichTextMenu.Instance?.textVcenter ?? false: - (RichTextMenu.Instance?.textAlign === where)): - where === 'vcent' ? BoolCast(Doc.UserDoc()._layout_centered): - (Doc.UserDoc().textAlign === where), - toggle: () => { editorView?.state ? (where === 'vcent' ? RichTextMenu.Instance?.vcenterToggle(): - RichTextMenu.Instance?.align(editorView, editorView.dispatch, where)): - where === 'vcent' ? Doc.UserDoc()._layout_centered = !Doc.UserDoc()._layout_centered: - (Doc.UserDoc().textAlign = where); } + [ where, { checkResult: () => (where === 'vcent' ? RichTextMenu.Instance?.textVcenter ?? false: + (RichTextMenu.Instance?.textAlign === where)), + toggle: () => { (where === 'vcent' ? RichTextMenu.Instance?.vcenterToggle(): + RichTextMenu.Instance?.align(editorView, editorView?.dispatch, where)); } }]); // prettier-ignore // prettier-ignore const listings:attrfuncs[] = (['bullet','decimal'] as attrname[]).map(list => @@ -360,6 +352,8 @@ ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: const attrs:attrfuncs[] = [ ['dictation', { checkResult: () => !!textView?._recordingDictation, toggle: () => textView && runInAction(() => { textView._recordingDictation = !textView._recordingDictation;} ) }], + ['fitBox', { checkResult: () => RichTextMenu.Instance?.fitBox ?? false, + toggle: () => RichTextMenu.Instance?.toggleFitBox()}], ['elide', { checkResult: () => false, toggle: () => editorView ? RichTextMenu.Instance?.elideSelection(): 0}], ['noAutoLink',{ checkResult: () => ((editorView && RichTextMenu.Instance?.noAutoLink) ?? false), diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx index 16c016d6c..7c601185e 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx @@ -245,7 +245,7 @@ export class DocCreatorMenu extends ObservableReactComponent { } updateIcons = (docs: Doc[]) => { - console.log('called') + console.log('called'); docs.map(this.getIcon); }; @@ -416,7 +416,7 @@ export class DocCreatorMenu extends ObservableReactComponent { const height = bottom - top; const width = right - left; const doc = !field.title.includes('$$') - ? Docs.Create.TextDocument('', { _height: height, _width: width, title: field.title, x: left, y: top, _text_fontSize: `${height / 2}` }) + ? Docs.Create.TextDocument('', { _height: height, _width: width, title: field.title, x: left, y: top, text_fontSize: `${height / 2}` }) : Docs.Create.ImageDocument('', { _height: height, _width: width, title: field.title.replace(/\$\$/g, ''), x: left, y: top }); return doc; }); @@ -919,17 +919,17 @@ export class DocCreatorMenu extends ObservableReactComponent { @action setExpandedView = (info: { icon: ImageField; doc: Doc } | undefined) => { if (info) { const doc = info.doc; - const wrapper: Doc = Docs.Create.FreeformDocument([info.doc], { _height: NumListCast(doc._height)[0], _width: NumListCast(doc._width)[0], title: ''}); - const newInfo = {icon: new ImageField(''), doc: wrapper} + const wrapper: Doc = Docs.Create.FreeformDocument([info.doc], { _height: NumListCast(doc._height)[0], _width: NumListCast(doc._width)[0], title: '' }); + const newInfo = { icon: new ImageField(''), doc: wrapper }; this._expandedPreview = newInfo; } else { this._expandedPreview = info; } }; - get editingWindow(){ + get editingWindow() { const doc = this._expandedPreview?.doc ?? new Doc(); - const rendered = + const rendered = (
{ removeDocument={returnFalse} PanelWidth={() => this._menuDimensions.width - 10} PanelHeight={() => this._menuDimensions.height - 60} - ScreenToLocalTransform={() => new Transform(-this._pageX,-this._pageY, 1)} + ScreenToLocalTransform={() => new Transform(-this._pageX, -this._pageY, 1)} renderDepth={5} whenChildContentsActiveChanged={emptyFunction} focus={emptyFunction} @@ -961,14 +961,21 @@ export class DocCreatorMenu extends ObservableReactComponent { yPadding={0} />
- + ); return (
-
+
{rendered}
-
- ); } @@ -2260,11 +2266,11 @@ export class FieldUtils { title: title, x: coord.x, y: coord.y, - _text_fontSize: `${FieldUtils.calculateFontSize(width, height, content, true)}`, + text_fontSize: `${FieldUtils.calculateFontSize(width, height, content, true)}`, backgroundColor: opts.backgroundColor ?? '', text_fontColor: opts.color, contentBold: opts.fontBold, - textTransform: opts.fontTransform, + text_transform: opts.fontTransform, color: opts.color, _layout_borderRounding: `${opts.cornerRounding ?? 0}px`, borderColor: opts.borderColor, @@ -2306,7 +2312,7 @@ export class FieldUtils { public static CarouselField = (coords: { tl: [number, number]; br: [number, number] }, parentWidth: number, parentHeight: number, title: string, fields: Doc[]) => { const { width, height, coord } = FieldUtils.getDimensions(coords, parentWidth, parentHeight); - const doc = Docs.Create.Carousel3DDocument(fields, { _height: height, _width: width, title: title, x: coord.x, y: coord.y, _text_fontSize: `${height / 2}` }); + const doc = Docs.Create.Carousel3DDocument(fields, { _height: height, _width: width, title: title, x: coord.x, y: coord.y, text_fontSize: `${height / 2}` }); return doc; }; @@ -2359,4 +2365,3 @@ export class FieldUtils { // }] // }; // } - diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index a343b9a39..eb7f333b9 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -880,7 +880,7 @@ export class DocumentViewInternal extends DocComponent{renderDoc} - case PresEffect.Flip: return {renderDoc} - case PresEffect.Rotate: return {renderDoc} - case PresEffect.Bounce: return {renderDoc} - case PresEffect.Roll: return {renderDoc} + const presEffect = StrCast(presEffectDoc?.presentation_effect, StrCast(presEffectDoc?.followLinkAnimEffect)); + switch (presEffect) { + case PresEffect.Expand: case PresEffect.Flip: case PresEffect.Rotate: case PresEffect.Bounce: + case PresEffect.Roll: return {renderDoc} // case PresEffect.Fade: return {renderDoc} case PresEffect.Fade: return {renderDoc} // keep as preset, doesn't really make sense with spring config diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx index fefe25764..290c90d6e 100644 --- a/src/client/views/nodes/EquationBox.tsx +++ b/src/client/views/nodes/EquationBox.tsx @@ -122,7 +122,7 @@ export class EquationBox extends ViewBoxBaseComponent() { width: 'fit-content', // `${100 / scale}%`, height: `${100 / scale}%`, pointerEvents: !this._props.isSelected() ? 'none' : undefined, - fontSize: StrCast(this.layoutDoc._text_fontSize), + fontSize: StrCast(this.Document._text_fontSize), }} onKeyDown={e => e.stopPropagation()}> diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.scss b/src/client/views/nodes/FontIconBox/FontIconBox.scss index 2db285910..ab03a2318 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.scss +++ b/src/client/views/nodes/FontIconBox/FontIconBox.scss @@ -10,6 +10,10 @@ height: 3px !important; } } +.fonticonbox { + margin: auto; + width: 100%; +} .menuButton { height: 100%; display: flex; diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index d4898eb3c..b45774a75 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -262,11 +262,13 @@ export class FontIconBox extends ViewBoxBaseComponent() { const script = ScriptCast(this.Document.onClick)?.script; const toggleStatus = script?.run({ this: this.Document, value: undefined, _readOnly_: true }).result as boolean; + // Colors const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string; const background = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string; const items = DocListCast(this.dataDoc.data); const selectedItems = items.filter(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, value: undefined, _readOnly_: true }).result).map(item => StrCast(item.toolType)); + return ( () { render() { return ( -
+
{this.renderButton()}
); diff --git a/src/client/views/nodes/LabelBox.scss b/src/client/views/nodes/LabelBox.scss index ca4b3d467..889cdc0ca 100644 --- a/src/client/views/nodes/LabelBox.scss +++ b/src/client/views/nodes/LabelBox.scss @@ -13,7 +13,6 @@ height: 100%; border-radius: inherit; //letter-spacing: 2px; // bcz: doesn't work with LabelBigText - text-transform: uppercase; overflow: hidden; display: inline-block; margin: auto; diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index 94a9541f2..dcf9e1fed 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -1,19 +1,22 @@ import { Property } from 'csstype'; -import { action, computed, makeObservable } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import * as textfit from 'textfit'; -import { Field, FieldType } from '../../../fields/Doc'; -import { BoolCast, NumCast, StrCast } from '../../../fields/Types'; +import { Doc, Field } from '../../../fields/Doc'; +import { NumCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; +import { undoable } from '../../util/UndoManager'; import { ViewBoxBaseComponent } from '../DocComponent'; import { PinDocView, PinProps } from '../PinFuncs'; import { StyleProp } from '../StyleProp'; import { FieldView, FieldViewProps } from './FieldView'; import './LabelBox.scss'; +import { FormattedTextBox } from './formattedText/FormattedTextBox'; +import { RichTextMenu } from './formattedText/RichTextMenu'; @observer export class LabelBox extends ViewBoxBaseComponent() { @@ -22,7 +25,8 @@ export class LabelBox extends ViewBoxBaseComponent() { } private dropDisposer?: DragManager.DragDropDisposer; private _timeout: NodeJS.Timeout | undefined; - _divRef: HTMLDivElement | null = null; + private _divRef: HTMLDivElement | null = null; + private _reaction: IReactionDisposer | undefined; constructor(props: FieldViewProps) { super(props); @@ -36,21 +40,29 @@ export class LabelBox extends ViewBoxBaseComponent() { } }; - @computed get Title() { - return Field.toString(this.dataDoc[this.fieldKey] as FieldType) || StrCast(this.Document.title); - } - - @computed get backgroundColor() { - return this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor) as string; - } - componentDidMount() { this._props.setContentViewBox?.(this); + this._reaction = reaction( + () => this.Title, + () => document.activeElement !== this._divRef && this._forceRerender++ + ); } componentWillUnMount() { this._timeout && clearTimeout(this._timeout); + this.setText(this._divRef?.innerText ?? ''); + this._reaction?.(); } + @observable _forceRerender = 0; + + @computed get Title() { return Field.toString(this.dataDoc[this.fieldKey]); } // prettier-ignore + @computed get backgroundColor() { return this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor) as string; } // prettier-ignore + @computed get boxShadow() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BoxShadow) as string; } // prettier-ignore + + setText = undoable((text: string) => { + this.dataDoc[this.fieldKey] = text; + }, 'set label text'); + drop = (/* e: Event, de: DragManager.DropEvent */) => { return false; }; @@ -82,10 +94,11 @@ export class LabelBox extends ViewBoxBaseComponent() { const textfitParams = { minFontSize: NumCast(this.layoutDoc._label_minFontSize, 1), maxFontSize: NumCast(this.layoutDoc._label_maxFontSize, 100), - multiLine: BoolCast(this.layoutDoc._singleLine, true) ? false : true, - alignHoriz: true, + multiLine: r?.textContent?.includes('\n') ? true : false, + // hack because tetFit doesn't support align 'right', but we need mobx to invalidate, so treat null as false and set to right inline + alignHoriz: StrCast(this.layoutDoc[this.fieldKey + '_align']) === 'center' ? true : StrCast(this.layoutDoc[this.fieldKey + '_align']) === 'right' ? (null as unknown as boolean) : false, alignVert: true, - detectMultiLine: true, + detectMultiLine: false, }; if (r) { if (!r.offsetHeight || !r.offsetWidth) { @@ -94,65 +107,140 @@ export class LabelBox extends ViewBoxBaseComponent() { this._timeout = setTimeout(() => this.fitTextToBox(r)); return textfitParams; } + r.style.whiteSpace = ''; // textfit sets to nowrap if not multiline, but doesn't reeset if it becomes multiline + r.style.textAlign = StrCast(this.layoutDoc[this.fieldKey + '_align']); // textfit doesn't reset textAlign if it has been set to center, so we just set it to what we want + r.firstChild instanceof HTMLElement && (r.firstChild.style.textAlign = StrCast(this.layoutDoc[this.fieldKey + '_align'])); textfit(r, textfitParams); } return textfitParams; }; + resetCursor = (cranchor?: number) => { + if (this._divRef && (cranchor || this._divRef === document.activeElement)) { + const range = document.createRange(); + const anchor = cranchor ?? this._divRef.childNodes.length; + const container = cranchor === undefined ? this._divRef : (this._divRef.firstChild?.firstChild ?? this._divRef); + range.setStart(container, anchor); + range.setEnd(container, anchor); + const sel = window.getSelection(); + sel?.removeAllRanges(); + sel?.addRange(range); + } + }; + + beforeInput = action((event: InputEvent) => { + const spanChild = this._divRef?.firstChild?.firstChild; + if (spanChild?.nodeName === '#text' && ['insertLineBreak', 'insertParagraph'].includes(event.inputType)) { + event.preventDefault(); + event.stopPropagation(); + + const selection = document.getSelection(); + if (selection && document.activeElement === event.target) { + const text = spanChild.textContent ?? ''; + const cranchor = selection.anchorNode === this._divRef ? (selection.anchorOffset ? text.length : 0) : selection.anchorOffset; + const addReturnHack = text.length <= cranchor && text[text.length - 1] !== '\n' ? '\n\n' : '\n'; // not sure why, but need to add a second carriage return if typing enter at the end of the text + const splitText = text.substring(0, cranchor) + addReturnHack + text.substring(cranchor); + spanChild.textContent = splitText; + this.resetCursor(cranchor + addReturnHack.length); + } + // const span = document.createElement('span'); + // span.innerHTML = '​'; + // this._divRef!.append(span); + } + }); + // .labelBox-mainButton > div > span:nth-child(2) { + + /** + * When an IconButton is clicked, it will receive focus. However, we don't want that since we want or need that since we really want + * to maintain focus in the label's editing div (and cursor position). so this relies on IconButton's having a tabindex set to -1 so that + * we can march up the tree from the 'relatedTarget' to determine if the loss of focus was caused by a fonticonbox. If it is, we then + * restore focus + * @param e focusout event on the editing div + */ + keepFocus = (e: FocusEvent) => { + if (e.relatedTarget instanceof HTMLElement && e.relatedTarget.tabIndex === -1) { + for (let ele: HTMLElement | null = e.relatedTarget; ele; ele = (ele as HTMLElement)?.parentElement) { + if ((ele as HTMLElement)?.className === 'fonticonbox') { + setTimeout(() => this._divRef?.focus()); + break; + } + } + } + }; + render() { TraceMobx(); const boxParams = this.fitTextToBox(undefined); // this causes mobx to trigger re-render when data changes - const label = this.Title.startsWith('#') ? null : this.Title; return ( -
+
{ + onKeyDown={e => { e.stopPropagation(); - })} + }} onKeyUp={action(e => { e.stopPropagation(); - this.dataDoc[this.fieldKey] = this._divRef?.innerText ?? ''; - setTimeout(() => this._props.select(false)); + const text = this._divRef?.firstChild; + if (text && (text as HTMLElement)?.nodeType === 3) { + this._divRef?.removeChild(text); + this._divRef?.firstChild?.appendChild(text); + this.resetCursor(); + } + this.fitTextToBox(this._divRef); })} + onFocus={() => { + RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, this.dataDoc); + this._divRef?.removeEventListener('focusout', this.keepFocus); + this._divRef?.addEventListener('focusout', this.keepFocus); + }} onBlur={() => { - this.dataDoc[this.fieldKey] = this._divRef?.innerText ?? ''; + this._divRef?.removeEventListener('focusout', this.keepFocus); + this.setText(this._divRef?.innerText ?? ''); + RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined); + FormattedTextBox.LiveTextUndo?.end(); + FormattedTextBox.LiveTextUndo = undefined; + }} + dangerouslySetInnerHTML={{ + __html: `${this.Title.startsWith('#') ? null : (this.Title ?? '')}`, }} contentEditable={this._props.onClickScript?.() ? undefined : true} ref={r => { + this._divRef?.removeEventListener('beforeinput', this.beforeInput); this._divRef = r; - this.fitTextToBox(r); - if (this._props.isSelected() && this._divRef) { - const range = document.createRange(); - range.setStart(this._divRef, this._divRef.childNodes.length); - range.setEnd(this._divRef, this._divRef.childNodes.length); - const sel = window.getSelection(); - sel?.removeAllRanges(); - sel?.addRange(range); + if (this._divRef) { + this._divRef.addEventListener('beforeinput', this.beforeInput); + + if (Doc.SelectOnLoad === this.Document) { + Doc.SelectOnLoad = undefined; + this._divRef.focus(); + } + this.fitTextToBox(this._divRef); + if (this.Title) { + this.resetCursor(); + } } - }}> - {label} -
+ }} + />
); @@ -161,9 +249,9 @@ export class LabelBox extends ViewBoxBaseComponent() { Docs.Prototypes.TemplateMap.set(DocumentType.LABEL, { layout: { view: LabelBox, dataField: 'title' }, - options: { acl: '', _singleLine: true, _layout_nativeDimEditable: true, _layout_reflowHorizontal: true, _layout_reflowVertical: true }, + options: { acl: '', _layout_nativeDimEditable: true, _layout_reflowHorizontal: true, _layout_reflowVertical: true, title_align: 'center', title_transform: 'uppercase' }, }); Docs.Prototypes.TemplateMap.set(DocumentType.BUTTON, { layout: { view: LabelBox, dataField: 'title' }, - options: { acl: '', _layout_nativeDimEditable: true, _layout_reflowHorizontal: true, _layout_reflowVertical: true }, + options: { acl: '', _layout_nativeDimEditable: true, _layout_reflowHorizontal: true, _layout_reflowVertical: true, title_align: 'center', title_transform: 'uppercase' }, }); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 2aee7b6b3..659cda9dd 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -65,6 +65,7 @@ import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu'; import { RichTextRules } from './RichTextRules'; import { schema } from './schema_rts'; import { Property } from 'csstype'; +import { LabelBox } from '../LabelBox'; // import * as applyDevTools from 'prosemirror-dev-tools'; export interface FormattedTextBoxProps extends FieldViewProps { @@ -158,6 +159,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent void = unimplementedFunction; public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined; @@ -175,10 +177,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { const rootDoc: Doc = Doc.isTemplateDoc(this._props.docViewPath().lastElement()?.Document) ? this.Document : DocCast(this.Document.rootDocument, this.Document); - if (!pinProps && this._editorView?.state.selection.empty) return rootDoc; + if (!pinProps && this.EditorView?.state.selection.empty) return rootDoc; const anchor = Docs.Create.ConfigDocument({ title: StrCast(rootDoc.title), annotationOn: rootDoc }); this.addDocument(anchor); this._finishingLink = true; @@ -263,7 +265,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent this._editorView?.state && RichTextMenu.Instance?.setFontField(color, 'fontHighlight'), 'highlght text'); + AnchorMenu.Instance.Highlight = undoable((color: string) => this.EditorView?.state && RichTextMenu.Instance?.setFontField(color, 'fontHighlight'), 'highlght text'); AnchorMenu.Instance.onMakeAnchor = () => this.getAnchor(true); AnchorMenu.Instance.StartCropDrag = unimplementedFunction; /** @@ -292,7 +294,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent; try { @@ -309,7 +311,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - if (node.type === this._editorView?.state.schema.nodes.dashField) { + if (node.type === this.EditorView?.state.schema.nodes.dashField) { const refDoc = !node.attrs.docId ? DocCast(this.Document.rootDocument, this.Document) : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc); const fieldKey = StrCast(node.attrs.fieldKey); return ( @@ -320,16 +322,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - if (this._editorView && !this._editorView.isDestroyed) { - const state = this._editorView.state.apply(tx); - this._editorView.updateState(state); + if (this.EditorView && !this.EditorView.isDestroyed) { + const state = this.EditorView.state.apply(tx); + this.EditorView.updateState(state); this.tryUpdateDoc(false); } }; tryUpdateDoc = (force: boolean) => { - if (this._editorView) { - const { state } = this._editorView; + if (this.EditorView) { + const { state } = this.EditorView; const { dataDoc } = this; const newText = state.doc.textBetween(0, state.doc.content.size, ' \n', this.leafText); const newJson = JSON.stringify(state.toJSON()); @@ -372,7 +374,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent term.title).forEach(term => { tr = this.hyperlinkTerm(tr, term, newAutoLinks); }); tr = tr.setSelection(new TextSelection(tr.doc.resolve(from), tr.doc.resolve(to))); - this._editorView?.dispatch(tr); + this.EditorView?.dispatch(tr); } oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.Document).forEach(doc => Doc.DeleteLink?.(doc)); }; @@ -450,11 +452,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent) => { let tr = trIn; - const editorView = this._editorView; + const editorView = this.EditorView; if (editorView && !Doc.AreProtosEqual(target, this.Document)) { const autoLinkTerm = Field.toString(target.title as FieldType).replace(/^@/, ''); let alink: Doc | undefined; @@ -553,18 +555,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - if (this._editorView) { - const { state } = this._editorView; + if (this.EditorView) { + const { state } = this.EditorView; if (state) { const mark = state.schema.mark(state.schema.marks.search_highlight); const activeMark = state.schema.mark(state.schema.marks.search_highlight, { selected: true }); const end = state.doc.nodeSize - 2; - this._editorView.dispatch(state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark)); + this.EditorView.dispatch(state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark)); } } }; adoptAnnotation = (start: number, end: number, mark: Mark) => { - const view = this._editorView!; + const view = this.EditorView!; const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: ClientUtils.CurrentUserEmail() }); view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark)); }; @@ -616,7 +618,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent node that wraps the hyerlink while (target && (!(target instanceof HTMLElement) || !target.dataset?.targethrefs)) target = target.parentElement; - const editor = this._editorView; + const editor = this.EditorView; if (editor && target && !(e.nativeEvent instanceof simMouseEvent ? e.nativeEvent.dash : false)) { const hrefs = (target.dataset?.targethrefs as string) ?.trim() @@ -996,8 +998,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { if (resIndex < newText.length) { - const marks = this._editorView?.state.storedMarks ?? []; - this._editorView?.dispatch(this._editorView?.state.tr.insertText(newText[resIndex]).setStoredMarks(marks)); + const marks = this.EditorView?.state.storedMarks ?? []; + this.EditorView?.dispatch(this.EditorView?.state.tr.insertText(newText[resIndex]).setStoredMarks(marks)); setTimeout(() => this.animateRes(resIndex + 1, newText), 20); } }; @@ -1035,8 +1035,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - if (this._editorView && this._recordingDictation) { + if (this.EditorView && this._recordingDictation) { this.stopDictation(/* true */); this._break = true; - const { state } = this._editorView; + const { state } = this.EditorView; const { to } = state.selection; const updated = TextSelection.create(state.doc, to, to); - this._editorView.dispatch(state.tr.setSelection(updated).insert(to, state.schema.nodes.paragraph.create({}))); + this.EditorView.dispatch(state.tr.setSelection(updated).insert(to, state.schema.nodes.paragraph.create({}))); if (this._recordingDictation) { this.recordDictation(); } @@ -1082,7 +1082,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent DictationManager.Controls.stop(/* !abort */); setDictationContent = (value: string) => { - if (this._editorView && this._recordingStart) { + if (this.EditorView && this._recordingStart) { if (this._break) { const textanchorFunc = () => { const tanch = Docs.Create.ConfigDocument({ title: 'dictation anchor' }); @@ -1095,22 +1095,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { const examinedNode = findAnchorNode(node, editor); - if (examinedNode?.node && (examinedNode.node.textContent || examinedNode.node.type === this._editorView?.state.schema.nodes.dashDoc || examinedNode.node.type === this._editorView?.state.schema.nodes.audiotag)) { + if (examinedNode?.node && (examinedNode.node.textContent || examinedNode.node.type === this.EditorView?.state.schema.nodes.dashDoc || examinedNode.node.type === this.EditorView?.state.schema.nodes.audiotag)) { nodes.push(examinedNode.node); !hadStart && (start = index + examinedNode.start); hadStart = true; @@ -1186,13 +1186,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - if (node.type === this._editorView?.state.schema.nodes.audiotag) { + if (node.type === this.EditorView?.state.schema.nodes.audiotag) { if (node.attrs.textId === textAnchorId) { return { node, start: 0 }; } return undefined; } - if (node.type === this._editorView?.state.schema.nodes.dashDoc) { + if (node.type === this.EditorView?.state.schema.nodes.dashDoc) { if (node.attrs.docId === textAnchorId) { return { node, start: 0 }; } @@ -1208,9 +1208,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent= 0 && (ret.frag.size || (firstChild && [state.schema.nodes.dashDoc, state.schema.nodes.audioTag].includes(firstChild.type)))) { @@ -1219,7 +1219,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent= '0' && textAnchorId[0] <= '9' ? `\\3${textAnchorId[0]} ${textAnchorId.substr(1)}` : textAnchorId; addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: 'yellow', transform: 'scale(3)', 'transform-origin': 'left bottom' }); setTimeout(() => { @@ -1307,15 +1307,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - if (this._editorView && this._applyingChange !== this.fieldKey) { + if (this.EditorView && this._applyingChange !== this.fieldKey) { if (incomingValue?.data) { const updatedState = JSON.parse(incomingValue.data.Data); - if (JSON.stringify(this._editorView.state.toJSON()) !== JSON.stringify(updatedState)) { - this._editorView.updateState(EditorState.fromJSON(this.config, updatedState)); + if (JSON.stringify(this.EditorView.state.toJSON()) !== JSON.stringify(updatedState)) { + this.EditorView.updateState(EditorState.fromJSON(this.config, updatedState)); this.tryUpdateScrollHeight(); } - } else if (this._editorView.state.doc.textContent !== incomingValue?.str) { - selectAll(this._editorView.state, tx => this._editorView?.dispatch(tx.insertText(incomingValue?.str ?? ''))); + } else if (this.EditorView.state.doc.textContent !== (incomingValue?.str ?? '')) { + selectAll(this.EditorView.state, tx => this.EditorView?.dispatch(tx.insertText(incomingValue?.str ?? ''))); } } }, @@ -1333,9 +1333,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { if (selected && this.dataDoc[this.fieldKey + '_placeholder']) { setTimeout(() => { - selectAll(this._editorView!.state, (tx: Transaction) => { - this._editorView?.dispatch(tx); - this._editorView!.focus(); + selectAll(this.EditorView!.state, (tx: Transaction) => { + this.EditorView?.dispatch(tx); + this.EditorView!.focus(); }); }); } @@ -1343,12 +1343,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - const view = this._editorView!; + const view = this.EditorView!; if (pdfAnchorId) { DocServer.GetRefField(pdfAnchorId).then(pdfAnchor => { if (pdfAnchor instanceof Doc) { @@ -1487,7 +1487,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { @@ -1519,14 +1519,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - this._editorView?.dispatch(tr.replaceSelectionWith(state.schema.nodes.paragraph.create({ align: textAlign }))); + this.EditorView?.dispatch(tr.replaceSelectionWith(state.schema.nodes.paragraph.create({ align: textAlign }))); }); } if (startupText) { - this._editorView?.dispatch(this._editorView.state.tr.insertText(startupText)); + this.EditorView?.dispatch(this.EditorView.state.tr.insertText(startupText)); } this.tryUpdateDoc(true); } @@ -1539,56 +1539,57 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent m.type !== mark.type), mark]; - const tr1 = this._editorView.state.tr.setStoredMarks(storedMarks); - const tr2 = selLoadChar === 'Enter' ? tr1.insert(this._editorView.state.doc.content.size - 1, schema.nodes.paragraph.create()) : tr1.insertText(selLoadChar, this._editorView.state.doc.content.size - 1); + const tr1 = this.EditorView.state.tr.setStoredMarks(storedMarks); + const tr2 = selLoadChar === 'Enter' ? tr1.insert(this.EditorView.state.doc.content.size - 1, schema.nodes.paragraph.create()) : tr1.insertText(selLoadChar, this.EditorView.state.doc.content.size - 1); const tr = tr2.setStoredMarks(storedMarks); - this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size)))); + this.EditorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size)))); this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data } else if (!FormattedTextBox.DontSelectInitialText) { const mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) }); - selectAll(this._editorView.state, (tx: Transaction) => { - this._editorView?.dispatch(tx.addStoredMark(mark)); + selectAll(this.EditorView.state, (tx: Transaction) => { + this.EditorView?.dispatch(tx.addStoredMark(mark)); }); + this.EditorView?.dispatch(this.EditorView.state.tr.setSelection(new TextSelection(this.EditorView.state.doc.resolve(1)))); this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data } else { - const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined; + const $from = this.EditorView.state.selection.anchor ? this.EditorView.state.doc.resolve(this.EditorView.state.selection.anchor - 1) : undefined; const mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) }); - const curMarks = this._editorView.state.storedMarks ?? $from?.marksAcross(this._editorView.state.selection.$head) ?? []; + const curMarks = this.EditorView.state.storedMarks ?? $from?.marksAcross(this.EditorView.state.selection.$head) ?? []; const storedMarks = [...curMarks.filter(m => m.type !== mark.type), mark]; - const { tr } = this._editorView.state; - this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))).setStoredMarks(storedMarks)); + const { tr } = this.EditorView.state; + this.EditorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))).setStoredMarks(storedMarks)); this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data } } if (selectOnLoad) { FormattedTextBox.DontSelectInitialText = false; - this._editorView!.focus(); + this.EditorView!.focus(); } if (this._props.isContentActive()) this.prepareForTyping(); - if (this._editorView && FormattedTextBox.PasteOnLoad) { + if (this.EditorView && FormattedTextBox.PasteOnLoad) { const pdfAnchorId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfAnchor'); FormattedTextBox.PasteOnLoad = undefined; pdfAnchorId && this.addPdfReference(pdfAnchorId); } - if (this._props.autoFocus) setTimeout(() => this._editorView!.focus()); // not sure why setTimeout is needed but editing dashFieldView's doesn't work without it. + if (this._props.autoFocus) setTimeout(() => this.EditorView!.focus()); // not sure why setTimeout is needed but editing dashFieldView's doesn't work without it. } // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet. prepareForTyping = () => { - if (this._editorView) { + if (this.EditorView) { const { text, paragraph } = schema.nodes; - const selNode = this._editorView.state.selection.$anchor.node(); - if (this._editorView.state.selection.from === 1 && this._editorView.state.selection.empty && [undefined, text, paragraph].includes(selNode?.type)) { + const selNode = this.EditorView.state.selection.$anchor.node(); + if (this.EditorView.state.selection.from === 1 && this.EditorView.state.selection.empty && [undefined, text, paragraph].includes(selNode?.type)) { const docDefaultMarks = [schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) })]; - this._editorView.state.selection.empty && this._editorView.state.selection.from === 1 && this._editorView?.dispatch(this._editorView?.state.tr.setStoredMarks(docDefaultMarks).removeStoredMark(schema.marks.pFontColor)); + this.EditorView.state.selection.empty && this.EditorView.state.selection.from === 1 && this.EditorView?.dispatch(this.EditorView?.state.tr.setStoredMarks(docDefaultMarks).removeStoredMark(schema.marks.pFontColor)); } } }; @@ -1602,7 +1603,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - const pos = ipos ?? (this._editorView?.state.selection.$from.pos || 1); - setTimeout(() => this._editorView?.dispatch(this._editorView.state.tr.setSelection(TextSelection.near(this._editorView.state.doc.resolve(pos)))), 100); + const pos = ipos ?? (this.EditorView?.state.selection.$from.pos || 1); + setTimeout(() => this.EditorView?.dispatch(this.EditorView.state.tr.setSelection(TextSelection.near(this.EditorView.state.doc.resolve(pos)))), 100); setTimeout(() => (this.ProseRef?.children?.[0] as HTMLElement).focus(), 200); }; @action onFocused = (e: React.FocusEvent): void => { - // applyDevTools.applyDevTools(this._editorView); + // applyDevTools.applyDevTools(this.EditorView); e.stopPropagation(); }; onClick = (e: React.MouseEvent): void => { if (!this._props.isContentActive()) return; - const editorView = this._editorView; + const editorView = this.EditorView; const editorRoot = editorView?.root instanceof Document ? editorView.root : undefined; if (editorView && (!this._forceUncollapse || editorRoot?.getSelection()?.isCollapsed)) { // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment. Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text. const pcords = editorView.posAtCoords({ left: e.clientX, top: e.clientY }); const node = pcords && editorView.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text) if (pcords && node?.type === editorView.state.schema.nodes.dashComment) { - this._editorView!.dispatch(editorView.state.tr.setSelection(TextSelection.create(editorView.state.doc, pcords.pos + 2))); + this.EditorView!.dispatch(editorView.state.tr.setSelection(TextSelection.create(editorView.state.doc, pcords.pos + 2))); e.preventDefault(); } if (!node && this.ProseRef) { @@ -1726,33 +1727,33 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { if (this.ProseRef?.children[0] !== e.nativeEvent.target) return; if (!(this.EditorView?.state.selection instanceof NodeSelection) || this.EditorView.state.selection.node.type !== this.EditorView.state.schema.nodes.footnote) { - const stordMarks = this._editorView?.state.storedMarks?.slice(); + const stordMarks = this.EditorView?.state.storedMarks?.slice(); if (!(this.EditorView?.state.selection instanceof NodeSelection)) { this.autoLink(); - if (this._editorView?.state.tr) { + if (this.EditorView?.state.tr) { const tr = stordMarks?.reduce((tr2, m) => { tr2.addStoredMark(m); return tr2; - }, this._editorView.state.tr); - tr && this._editorView.dispatch(tr); + }, this.EditorView.state.tr); + tr && this.EditorView.dispatch(tr); } } } - if (RichTextMenu.Instance?.view === this._editorView && !(this._props.isContentActive() || this._props.rootSelected?.())) { + if (RichTextMenu.Instance?.view === this.EditorView && !(this._props.isContentActive() || this._props.rootSelected?.())) { RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined); } @@ -1831,7 +1832,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - this._editorView!.dispatch(updateBullets(this._editorView!.state.tr, this._editorView!.state.schema)); + this.EditorView?.dispatch(updateBullets(this.EditorView.state.tr, this.EditorView.state.schema)); e.stopPropagation(); // drag n drop of text within text note will generate a new note if not caughst, as will dragging in from outside of Dash. }; onScroll = (e: React.UIEvent) => { @@ -1872,7 +1873,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { const margins = 2 * NumCast(this.layoutDoc._yMargin, this._props.yPadding || 0); const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined; - if (children && !SnappingManager.IsDragging) { + if (this.EditorView && children && !SnappingManager.IsDragging) { const getChildrenHeights = (kids: Element[] | undefined) => kids?.reduce((p, child) => p + toHgt(child), margins) ?? 0; const toNum = (val: string) => Number(val.replace('px', '')); const toHgt = (node: Element): number => { @@ -2093,7 +2094,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent - return styleFromLayout?.height === '0px' ? null : ( + return this.isLabel ? ( + + ) : styleFromLayout?.height === '0px' ? null : (
{ @@ -2156,8 +2159,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index 7a8b72be0..3c84e5a10 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -349,7 +349,9 @@ export function buildKeymap>(schema: S, props: any): KeyMa dispatch(tx4); } - if (view.state.selection.$anchor.nodeAfter?.type === schema.nodes.text && once) { + if (view.state.selection.$anchor.depth > 0 && + view.state.selection.$anchor.node(view.state.selection.$anchor.depth-1).type === schema.nodes.list_item && + view.state.selection.$anchor.nodeAfter?.type === schema.nodes.text && once) { // if text is selected across list items, then we need to forcibly insert a new line since the splitBlock code joins the two list items. enter(view.state, dispatch, view, false); } diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 55e6a3a5b..65a415a23 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -1,6 +1,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; -import { action, computed, IReactionDisposer, makeObservable, observable, runInAction } from 'mobx'; +import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { lift, toggleMark, wrapIn } from 'prosemirror-commands'; import { Mark, MarkType } from 'prosemirror-model'; @@ -32,7 +32,7 @@ export class RichTextMenu extends AntimodeMenu { public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable private _linkToRef = React.createRef(); - layoutDoc: Doc | undefined; + dataDoc: Doc | undefined; @observable public view?: EditorView & { TextView?: FormattedTextBox } = undefined; public editorProps: FieldViewProps | AntimodeMenuProps | undefined; @@ -49,6 +49,7 @@ export class RichTextMenu extends AntimodeMenu { @observable private _activeFontSize: string = '13px'; @observable private _activeFontFamily: string = ''; + @observable private _activeFitBox: boolean = false; @observable private _activeListType: string = ''; @observable private _activeAlignment: string = 'left'; @@ -64,13 +65,12 @@ export class RichTextMenu extends AntimodeMenu { @observable private currentLink: string | undefined = ''; @observable private showLinkDropdown: boolean = false; - _reaction: IReactionDisposer | undefined; constructor(props: AntimodeMenuProps) { super(props); makeObservable(this); runInAction(() => { RichTextMenu._instance.menu = this; - this.updateMenu(undefined, undefined, props, this.layoutDoc); + this.updateMenu(undefined, undefined, props, this.dataDoc); this._canFade = false; this.Pinned = true; }); @@ -101,6 +101,9 @@ export class RichTextMenu extends AntimodeMenu { @computed get fontHighlight() { return this._activeHighlightColor; } + @computed get fitBox() { + return this._activeFitBox; + } @computed get fontFamily() { return this._activeFontFamily; } @@ -114,26 +117,16 @@ export class RichTextMenu extends AntimodeMenu { return this._activeAlignment; } @computed get textVcenter() { - return BoolCast(this.layoutDoc?._layout_centered); - } - _disposer: IReactionDisposer | undefined; - componentDidMount() { - // this._disposer = reaction( - // () => DocumentView.Selected().slice(), - // () => this.updateMenu(undefined, undefined, undefined, undefined) - // ); - } - componentWillUnmount() { - this._disposer?.(); + return BoolCast(this.dataDoc?._layout_centered, BoolCast(Doc.UserDoc().layout_centered)); } @action - public updateMenu(view: EditorView | undefined, lastState: EditorState | undefined, props: FormattedTextBoxProps | AntimodeMenuProps | undefined, layoutDoc: Doc | undefined) { + public updateMenu(view: EditorView | undefined, lastState: EditorState | undefined, props: FormattedTextBoxProps | AntimodeMenuProps | undefined, dataDoc: Doc | undefined) { if (this._linkToRef.current?.getBoundingClientRect().width) { return; } this.view = view; - this.layoutDoc = layoutDoc; + this.dataDoc = dataDoc; props && (this.editorProps = props); // Don't do anything if the document/selection didn't change @@ -147,12 +140,13 @@ export class RichTextMenu extends AntimodeMenu { const { activeSizes } = active; const { activeColors } = active; const { activeHighlights } = active; - const refDoc = DocumentView.Selected().lastElement()?.layoutDoc ?? Doc.UserDoc(); + const refDoc = DocumentView.Selected().lastElement()?.dataDoc ?? Doc.UserDoc(); const refField = (pfx => (pfx ? pfx + '_' : ''))(DocumentView.Selected().lastElement()?.LayoutFieldKey); const refVal = (field: string, dflt: string) => StrCast(refDoc[refField + field], StrCast(Doc.UserDoc()[field], dflt)); this._activeListType = this.getActiveListStyle(); this._activeAlignment = this.getActiveAlignment(); + this._activeFitBox = BoolCast(refDoc[refField + 'fitBox'], BoolCast(Doc.UserDoc().fitBox)); this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document._text_fontFamily, refVal('fontFamily', 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various'; this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, refVal('fontSize', '10px')) : activeSizes[0]; this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, refVal('fontColor', 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...'; @@ -181,7 +175,6 @@ export class RichTextMenu extends AntimodeMenu { toggleMark(mark.type, mark.attrs)(state, dispatch); } } - // this.updateMenu(this.view, undefined, undefined, this.layoutDoc); } }; @@ -195,8 +188,10 @@ export class RichTextMenu extends AntimodeMenu { return node.attrs.align || 'left'; } } + } else if (this.dataDoc) { + return StrCast(this.dataDoc.text_align) || 'left'; } - return 'left'; + return StrCast(Doc.UserDoc().textAlign) || 'left'; }; // finds font sizes and families in selection @@ -330,6 +325,17 @@ export class RichTextMenu extends AntimodeMenu { this.view.focus(); } }; + toggleFitBox = () => { + if (this.dataDoc) { + const doc = this.dataDoc; + (document.activeElement as HTMLElement)?.blur(); + doc.text_fitBox = !doc.text_fitBox; + } else { + Doc.UserDoc().fitBox = !Doc.UserDoc().fitBox; + Doc.UserDoc().textAlign = Doc.UserDoc().fitBox ? 'center' : undefined; + } + this.updateMenu(undefined, undefined, undefined, this.dataDoc); + }; toggleBold = () => { if (this.view) { const mark = this.view.state.schema.mark(this.view.state.schema.marks.strong); @@ -354,11 +360,14 @@ export class RichTextMenu extends AntimodeMenu { } }; - setFontField = (value: string, fontField: 'fontSize' | 'fontFamily' | 'fontColor' | 'fontHighlight') => { - if (this.TextView && this.view) { + 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) { const { text, paragraph } = this.view.state.schema.nodes; const selNode = this.view.state.selection.$anchor.node(); - if (this.view.state.selection.from === 1 && this.view.state.selection.empty && [undefined, text, paragraph].includes(selNode?.type)) { + if ((fontField === 'fontSize' && value === '0px') || (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(); } @@ -369,7 +378,6 @@ export class RichTextMenu extends AntimodeMenu { this.view.focus(); } else { Doc.UserDoc()[fontField] = value; - // this.updateMenu(this.view, undefined, this.props, this.layoutDoc); } }; @@ -395,7 +403,6 @@ export class RichTextMenu extends AntimodeMenu { this.view!.dispatch(tx3); }); this.view.focus(); - // this.updateMenu(this.view, undefined, this.props, this.layoutDoc); }; insertSummarizer(state: EditorState, dispatch: (tr: Transaction) => void) { @@ -410,10 +417,11 @@ export class RichTextMenu extends AntimodeMenu { } vcenterToggle = () => { - this.layoutDoc && (this.layoutDoc._layout_centered = !this.layoutDoc._layout_centered); + if (this.dataDoc) this.dataDoc._layout_centered = !this.dataDoc._layout_centered; + else Doc.UserDoc()._layout_centered = !Doc.UserDoc()._layout_centered; }; - align = (view: EditorView, dispatch: (tr: Transaction) => void, alignment: 'left' | 'right' | 'center') => { - if (this.RootSelected) { + align = (view: EditorView | undefined, dispatch: undefined | ((tr: Transaction) => void), alignment: 'left' | 'right' | 'center') => { + if (view && dispatch && this.RootSelected) { let { tr } = view.state; view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos) => { if ([schema.nodes.paragraph, schema.nodes.heading].includes(node.type)) { @@ -425,6 +433,11 @@ export class RichTextMenu extends AntimodeMenu { }); view.focus(); dispatch?.(tr); + } else { + if (this.dataDoc) { + this.dataDoc.text_align = alignment; + } else Doc.UserDoc().textAlign = alignment; + this.updateMenu(undefined, undefined, undefined, this.dataDoc); } }; @@ -702,7 +715,7 @@ interface RichTextMenuPluginProps { } export class RichTextMenuPlugin extends React.Component { update(view: EditorView & { TextView?: FormattedTextBox }, lastState: EditorState | undefined) { - RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps, view.TextView?.layoutDoc); + RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps, view.TextView?.dataDoc); } render() { return null; diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index f58434906..3d14ce4c0 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -121,7 +121,7 @@ export class RichTextRules { annotationOn: textDoc, _layout_fitWidth: true, _layout_autoHeight: true, - _text_fontSize: '9px', + text_fontSize: '9px', title: 'inline comment', }); textDocInline.title = inlineFieldKey; // give the annotation its own title diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 5d3672aee..06869717a 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -2616,13 +2616,13 @@ export class PresBox extends ViewBoxBaseComponent() { createTemplate = (layout: string, input?: string) => { const x = this.activeItem && this.targetDoc ? NumCast(this.targetDoc.x) : 0; const y = this.activeItem && this.targetDoc ? NumCast(this.targetDoc.y) + NumCast(this.targetDoc._height) + 20 : 0; - const title = () => Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 58, _text_fontSize: '24pt' }); - const subtitle = () => Docs.Create.TextDocument('Click to change subtitle', { title: 'Slide subtitle', _width: 380, _height: 50, x: 10, y: 118, _text_fontSize: '16pt' }); - const header = () => Docs.Create.TextDocument('Click to change header', { title: 'Slide header', _width: 380, _height: 65, x: 10, y: 80, _text_fontSize: '20pt' }); - const contentTitle = () => Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 10, _text_fontSize: '24pt' }); - const content = () => Docs.Create.TextDocument('Click to change text', { title: 'Slide text', _width: 380, _height: 145, x: 10, y: 70, _text_fontSize: '14pt' }); - const content1 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 1', _width: 185, _height: 140, x: 10, y: 80, _text_fontSize: '14pt' }); - const content2 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 2', _width: 185, _height: 140, x: 205, y: 80, _text_fontSize: '14pt' }); + const title = () => Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 58, text_fontSize: '24pt' }); + const subtitle = () => Docs.Create.TextDocument('Click to change subtitle', { title: 'Slide subtitle', _width: 380, _height: 50, x: 10, y: 118, text_fontSize: '16pt' }); + const header = () => Docs.Create.TextDocument('Click to change header', { title: 'Slide header', _width: 380, _height: 65, x: 10, y: 80, text_fontSize: '20pt' }); + const contentTitle = () => Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 10, text_fontSize: '24pt' }); + const content = () => Docs.Create.TextDocument('Click to change text', { title: 'Slide text', _width: 380, _height: 145, x: 10, y: 70, text_fontSize: '14pt' }); + const content1 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 1', _width: 185, _height: 140, x: 10, y: 80, text_fontSize: '14pt' }); + const content2 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 2', _width: 185, _height: 140, x: 205, y: 80, text_fontSize: '14pt' }); // prettier-ignore switch (layout) { case 'blank': return Docs.Create.FreeformDocument([], { title: input || 'Blank slide', _width: 400, _height: 225, x, y }); diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index d5f5f620c..a7e78bcea 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -572,18 +572,19 @@ export class GPTPopup extends ObservableReactComponent {
{this.heading(this.mode === GPTPopupMode.SORT ? 'SORTING' : 'QUIZ')} <> - {!this.cardsDoneLoading ? ( -
-
- - {this.loading ? Loading... : Reading Cards...} + { + !this.cardsDoneLoading ? ( +
+
+ + {this.loading ? Loading... : Reading Cards...} +
-
- ) : this.mode === GPTPopupMode.CARD ? ( - this.cardMenu() - ) : ( - this.cardActual(this.mode) - ) // Call the functions to render JSX + ) : this.mode === GPTPopupMode.CARD ? ( + this.cardMenu() + ) : ( + this.cardActual(this.mode) + ) // Call the functions to render JSX }
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts index 335683270..b27816f55 100644 --- a/src/fields/documentSchemas.ts +++ b/src/fields/documentSchemas.ts @@ -48,8 +48,8 @@ export const documentSchema = createSchema({ _columnsHideIfEmpty: 'boolean', // whether empty stacking view column headings should be hidden // _columnHeaders: listSpec(SchemaHeaderField), // header descriptions for stacking/masonry // _schemaHeaders: listSpec(SchemaHeaderField), // header descriptions for schema views - _text_fontSize: 'string', - _text_fontFamily: 'string', + text_fontSize: 'string', + text_fontFamily: 'string', _layout_sidebarWidthPercent: 'string', // percent of text window width taken up by sidebar // appearance properties on the data document @@ -70,7 +70,7 @@ export const documentSchema = createSchema({ stroke_startMarker: 'string', stroke_endMarker: 'string', stroke_dash: 'string', - textTransform: 'string', + text_transform: 'string', treeView_Open: 'boolean', // flag denoting whether the documents sub-tree (contents) is visible or hidden treeView_ExpandedView: 'string', // name of field whose contents are being displayed as the document's subtree treeView_ExpandedViewLock: 'boolean', // whether the expanded view can be changed @@ -112,5 +112,4 @@ export const collectionSchema = createSchema({ }); export type Document = makeInterface<[typeof documentSchema]>; -// eslint-disable-next-line no-redeclare export const Document = makeInterface(documentSchema); -- 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