diff options
Diffstat (limited to 'src/client/views/nodes')
21 files changed, 504 insertions, 1167 deletions
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 2290e0711..ca5ec9389 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -46,7 +46,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl if (dropEvent.complete.docDragData) { const droppedDocs = dropEvent.complete.docDragData?.droppedDocuments; const added = dropEvent.complete.docDragData.moveDocument?.(droppedDocs, this.rootDoc, (doc: Doc | Doc[]) => this.addDoc(doc instanceof Doc ? doc : doc.lastElement(), fieldKey)); - droppedDocs.lastElement().embedContainer = this.dataDoc; + Doc.SetContainer(droppedDocs.lastElement(), this.dataDoc); !added && e.preventDefault(); e.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place return added; diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 0fe24fe8d..d548ab9f1 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -71,13 +71,14 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { }; getAnchor = (addAsAnnotation?: boolean, pinProps?: PinProps) => { - const anchor = - this._chartRenderer?.getAnchor(pinProps) ?? - Docs.Create.ConfigDocument({ - // when we clear selection -> we should have it so chartBox getAnchor returns undefined - // this is for when we want the whole doc (so when the chartBox getAnchor returns without a marker) - /*put in some options*/ - }); + const anchor = !pinProps + ? this.rootDoc + : this._chartRenderer?.getAnchor(pinProps) ?? + Docs.Create.ConfigDocument({ + // when we clear selection -> we should have it so chartBox getAnchor returns undefined + // this is for when we want the whole doc (so when the chartBox getAnchor returns without a marker) + /*put in some options*/ + }); anchor.presDataVizView = this.dataVizView; anchor.presDataVizAxes = this.axes.length ? new List<string>(this.axes) : undefined; diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index e954d0484..7e8eef0a5 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -18,7 +18,7 @@ import { SearchBox } from '../search/SearchBox'; import { DashWebRTCVideo } from '../webcam/DashWebRTCVideo'; import { YoutubeBox } from './../../apis/youtube/YoutubeBox'; import { AudioBox } from './AudioBox'; -import { FontIconBox } from './button/FontIconBox'; +import { FontIconBox } from './FontIconBox/FontIconBox'; import { ColorBox } from './ColorBox'; import { ComparisonBox } from './ComparisonBox'; import { DataVizBox } from './DataVizBox/DataVizBox'; diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index f1627e1e1..b25540dd3 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -115,7 +115,10 @@ width: 100%; height: 100%; transition: inherit; - + display: flex; + justify-content: center; + align-items: center; + .sharingIndicator { height: 30px; width: 30px; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 19f9f15a4..6b47564a4 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -535,7 +535,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps // if this is part of a template, let the event go up to the template root unless right/ctrl clicking if ( // prettier-ignore - this.props.isDocumentActive?.() && + (this.props.isDocumentActive?.() || this.props.isContentActive?.()) && !this.props.onBrowseClick?.() && !this.Document.ignoreClick && e.button === 0 && @@ -1084,8 +1084,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps @computed get innards() { TraceMobx(); const ffscale = () => this.props.DocumentView().props.CollectionFreeFormDocumentView?.().props.ScreenToLocalTransform().Scale || 1; - const layout_showTitle = this.layout_showTitle?.split(':')[0]; - const layout_showTitleHover = this.layout_showTitle?.includes(':hover'); + const showTitle = this.layout_showTitle?.split(':')[0]; + const showTitleHover = this.layout_showTitle?.includes(':hover'); const captionView = !this.layout_showCaption ? null : ( <div className="documentView-captionWrapper" @@ -1109,27 +1109,27 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps /> </div> ); - const targetDoc = layout_showTitle?.startsWith('_') ? this.layoutDoc : this.rootDoc; + const targetDoc = showTitle?.startsWith('_') ? this.layoutDoc : this.rootDoc; const background = StrCast( - SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.userColor, - Doc.UserDoc().layout_showTitle && [DocumentType.RTF, DocumentType.COL].includes(this.rootDoc.type as any) ? StrCast(Doc.SharingDoc().userColor) : 'rgba(0,0,0,0.4)' + SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, + Doc.UserDoc().layout_showTitle && [DocumentType.RTF, DocumentType.COL].includes(this.rootDoc.type as any) ? StrCast(Doc.SharingDoc().headingColor) : 'rgba(0,0,0,0.4)' ); - const layout_sidebarWidthPercent = +StrCast(this.layoutDoc.layout_sidebarWidthPercent).replace('%', ''); - const titleView = !layout_showTitle ? null : ( + const sidebarWidthPercent = +StrCast(this.layoutDoc.layout_sidebarWidthPercent).replace('%', ''); + const titleView = !showTitle ? null : ( <div - className={`documentView-titleWrapper${layout_showTitleHover ? '-hover' : ''}`} + className={`documentView-titleWrapper${showTitleHover ? '-hover' : ''}`} key="title" style={{ position: this.headerMargin ? 'relative' : 'absolute', height: this.titleHeight, - width: !this.headerMargin ? `calc(${layout_sidebarWidthPercent || 100}% - 18px)` : (layout_sidebarWidthPercent || 100) + '%', // leave room for annotation button + width: !this.headerMargin ? `calc(${sidebarWidthPercent || 100}% - 18px)` : (sidebarWidthPercent || 100) + '%', // leave room for annotation button color: lightOrDark(background), background, pointerEvents: (!this.disableClickScriptFunc && this.onClickHandler) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined, }}> <EditableView ref={this._titleRef} - contents={layout_showTitle + contents={showTitle .split(';') .map(field => field.trim()) .map(field => targetDoc[field]?.toString()) @@ -1138,7 +1138,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps fontSize={10} GetValue={() => { this.props.select(false); - return layout_showTitle.split(';').length === 1 ? layout_showTitle + '=' + Field.toString(targetDoc[layout_showTitle.split(';')[0]] as any as Field) : '#' + layout_showTitle; + return showTitle.split(';').length === 1 ? showTitle + '=' + Field.toString(targetDoc[showTitle.split(';')[0]] as any as Field) : '#' + showTitle; }} SetValue={undoBatch((input: string) => { if (input?.startsWith('#')) { @@ -1148,17 +1148,17 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps Doc.UserDoc().layout_showTitle = input?.substring(1) ? input.substring(1) : 'author_date'; } } else { - var value = input.replace(new RegExp(layout_showTitle + '='), '') as string | number; - if (layout_showTitle !== 'title' && Number(value).toString() === value) value = Number(value); - if (layout_showTitle.includes('Date') || layout_showTitle === 'author') return true; - Doc.SetInPlace(targetDoc, layout_showTitle, value, true); + var value = input.replace(new RegExp(showTitle + '='), '') as string | number; + if (showTitle !== 'title' && Number(value).toString() === value) value = Number(value); + if (showTitle.includes('Date') || showTitle === 'author') return true; + Doc.SetInPlace(targetDoc, showTitle, value, true); } return true; })} /> </div> ); - return this.props.hideTitle || (!layout_showTitle && !this.layout_showCaption) ? ( + return this.props.hideTitle || (!showTitle && !this.layout_showCaption) ? ( this.contents ) : ( <div className="documentView-styleWrapper"> @@ -1238,7 +1238,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps const renderDoc = this.renderDoc({ borderRadius: this.borderRounding, outline: highlighting && !this.borderRounding && !highlighting.highlightStroke ? `${highlighting.highlightColor} ${highlighting.highlightStyle} ${highlighting.highlightIndex}px` : 'solid 0px', - border: highlighting && this.borderRounding && highlighting.highlightStyle === 'dashed' ? `${highlighting.highlightStyle} ${highlighting.highlightColor} ${highlighting.highlightIndex}px` : undefined, + border: highlighting && this.borderRounding && highlighting.highlightStyle === 'dashed' ? `${highlighting.highlightStyle} ${highlighting.highlightColor} ${highlighting.highlightIndex}px` : undefined, boxShadow, clipPath: borderPath?.clipPath, }); diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx index 8d45c5724..a77e4bdd1 100644 --- a/src/client/views/nodes/EquationBox.tsx +++ b/src/client/views/nodes/EquationBox.tsx @@ -26,7 +26,8 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() { this.props.select(false); this._ref.current!.mathField.focus(); - this._ref.current!.mathField.select(); + this.rootDoc.text === 'x' && this._ref.current!.mathField.select(); + EquationBox.SelectOnLoad = ''; } reaction( () => StrCast(this.dataDoc.text), @@ -35,6 +36,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() { this._ref.current!.mathField.latex(text); } } + //{ fireImmediately: true } ); reaction( () => this.props.isSelected(), @@ -53,9 +55,8 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() { const _height = Number(getComputedStyle(this._ref.current!.element.current).height.replace('px', '')); const _width = Number(getComputedStyle(this._ref.current!.element.current).width.replace('px', '')); if (e.key === 'Enter') { - const nextEq = Docs.Create.EquationDocument({ + const nextEq = Docs.Create.EquationDocument(e.shiftKey ? StrCast(this.dataDoc.text) : 'x', { title: '# math', - text: StrCast(this.dataDoc.text), _width, _height: 25, x: NumCast(this.layoutDoc.x), diff --git a/src/client/views/nodes/button/ButtonInterface.ts b/src/client/views/nodes/FontIconBox/ButtonInterface.ts index 0aa2ac8e1..0aa2ac8e1 100644 --- a/src/client/views/nodes/button/ButtonInterface.ts +++ b/src/client/views/nodes/FontIconBox/ButtonInterface.ts diff --git a/src/client/views/nodes/button/FontIconBadge.scss b/src/client/views/nodes/FontIconBox/FontIconBadge.scss index 78f506e57..2ff5c651f 100644 --- a/src/client/views/nodes/button/FontIconBadge.scss +++ b/src/client/views/nodes/FontIconBox/FontIconBadge.scss @@ -1,11 +1,12 @@ .fontIconBadge { - background: red; + background: lightgreen; width: 15px; height: 15px; top: 8px; + color: black; display: block; position: absolute; right: 5; border-radius: 50%; text-align: center; -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/button/FontIconBadge.tsx b/src/client/views/nodes/FontIconBox/FontIconBadge.tsx index b50588ce2..b50588ce2 100644 --- a/src/client/views/nodes/button/FontIconBadge.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBadge.tsx diff --git a/src/client/views/nodes/button/FontIconBox.scss b/src/client/views/nodes/FontIconBox/FontIconBox.scss index 9d9fa26b0..9d9fa26b0 100644 --- a/src/client/views/nodes/button/FontIconBox.scss +++ b/src/client/views/nodes/FontIconBox/FontIconBox.scss diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx new file mode 100644 index 000000000..39be4022e --- /dev/null +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -0,0 +1,423 @@ +import { IconProp } from '@fortawesome/fontawesome-svg-core'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Button, ColorPicker, Dropdown, DropdownType, EditableText, IconButton, IListItemProps, NumberDropdown, NumberDropdownType, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components'; +import { action, computed, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc'; +import { ScriptField } from '../../../../fields/ScriptField'; +import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; +import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; +import { SelectionManager } from '../../../util/SelectionManager'; +import { undoable, UndoManager } from '../../../util/UndoManager'; +import { ContextMenu } from '../../ContextMenu'; +import { DocComponent } from '../../DocComponent'; +import { EditableView } from '../../EditableView'; +import { Colors } from '../../global/globalEnums'; +import { StyleProp } from '../../StyleProvider'; +import { FieldView, FieldViewProps } from '../FieldView'; +import { OpenWhere } from '../DocumentView'; +import { RichTextMenu } from '../formattedText/RichTextMenu'; +import './FontIconBox.scss'; +import { SelectedDocView } from '../../selectedDoc'; +import { Utils } from '../../../../Utils'; + +export enum ButtonType { + TextButton = 'textBtn', + MenuButton = 'menuBtn', + DropdownList = 'dropdownList', + DropdownButton = 'dropdownBtn', + ClickButton = 'clickBtn', + ToggleButton = 'toggleBtn', + ColorButton = 'colorBtn', + ToolButton = 'toolBtn', + MultiToggleButton = 'multiToggleBtn', + NumberSliderButton = 'numSliderBtn', + NumberDropdownButton = 'numDropdownBtn', + NumberInlineButton = 'numInlineBtn', + EditableText = 'editableText', +} + +export interface ButtonProps extends FieldViewProps { + type?: ButtonType; +} +@observer +export class FontIconBox extends DocComponent<ButtonProps>() { + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(FontIconBox, fieldKey); + } + @observable noTooltip = false; + showTemplate = (): void => { + const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null); + dragFactory && this.props.addDocTab(dragFactory, OpenWhere.addRight); + }; + dragAsTemplate = (): void => { + this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)'); + }; + useAsPrototype = (): void => { + this.layoutDoc.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)'); + }; + + specificContextMenu = (): void => { + if (!Doc.noviceMode) { + const cm = ContextMenu.Instance; + cm.addItem({ description: 'Show Template', event: this.showTemplate, icon: 'tag' }); + cm.addItem({ description: 'Use as Render Template', event: this.dragAsTemplate, icon: 'tag' }); + cm.addItem({ description: 'Use as Prototype', event: this.useAsPrototype, icon: 'tag' }); + } + }; + + static GetShowLabels() { + return BoolCast(Doc.UserDoc()._showLabel); + } + static SetShowLabels(show: boolean) { + Doc.UserDoc()._showLabel = show; + } + static GetRecognizeGestures() { + return BoolCast(Doc.UserDoc()._recognizeGestures); + } + static SetRecognizeGestures(show: boolean) { + Doc.UserDoc()._recognizeGestures = show; + } + + // Determining UI Specs + @computed get label() { + return StrCast(this.rootDoc.icon_label, StrCast(this.rootDoc.title)); + } + Icon = (color: string, iconFalse?: boolean) => { + let icon; + if (iconFalse ) { + icon = StrCast(this.dataDoc[this.fieldKey ?? 'iconFalse'] ?? this.dataDoc.icon, 'user') as any; + if (icon) return <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} /> + else return null + } + icon = StrCast(this.dataDoc[this.fieldKey ?? 'icon'] ?? this.dataDoc.icon, 'user') as any; + const trailsIcon = () => <img src={`/assets/${'presTrails.png'}`} style={{ width: 30, height: 30, filter: `invert(${color === Colors.DARK_GRAY ? '0%' : '100%'})` }} />; + return !icon ? null : icon === 'pres-trail' ? trailsIcon() : <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />; + }; + @computed get dropdown() { + return BoolCast(this.rootDoc.dropDownOpen); + } + @computed get buttonList() { + return StrListCast(this.rootDoc.btnList); + } + @computed get type() { + return StrCast(this.rootDoc.btnType); + } + + /** + * Types of buttons in dash: + * - Main menu button (LHS) + * - Tool button + * - Expandable button (CollectionLinearView) + * - Button inside of CollectionLinearView vs. outside of CollectionLinearView + * - Action button + * - Dropdown button + * - Color button + * - Dropdown list + * - Number button + **/ + + _batch: UndoManager.Batch | undefined = undefined; + /** + * Number button + */ + @computed get numberDropdown() { + let type: NumberDropdownType; + switch(this.type) { + case ButtonType.NumberDropdownButton: + type = 'dropdown' + break; + case ButtonType.NumberInlineButton: + type = 'input' + break; + case ButtonType.NumberSliderButton: + default: + type = 'slider' + break; + } + const numScript = (value?: number) => ScriptCast(this.rootDoc.script).script.run({ self: this.rootDoc, value, _readOnly_: value === undefined }); + const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); + // Script for checking the outcome of the toggle + const checkResult = Number(Number(numScript().result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3))); + const label = !FontIconBox.GetShowLabels() ? null : <div className="fontIconBox-label">{this.label}</div>; + + return <NumberDropdown + color={color} + numberDropdownType={type} + showPlusMinus={false} + tooltip={this.label} + type={Type.PRIM} + min={NumCast(this.rootDoc.numBtnMin, 0)} + max={NumCast(this.rootDoc.numBtnMax, 100)} + number={checkResult} + setNumber={undoable(value => numScript(value), `${this.rootDoc.title} button set from list`)} + fillWidth + /> + } + + /** + * Dropdown button + */ + @computed get dropdownButton() { + const active: string = StrCast(this.rootDoc.dropDownOpen); + const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); + const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); + return ( + <div + className={`menuButton ${this.type} ${active}`} + style={{ color: color, backgroundColor: backgroundColor, borderBottomLeftRadius: this.dropdown ? 0 : undefined }} + onClick={action(() => { + this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen; + this.noTooltip = this.rootDoc.dropDownOpen; + Doc.UnBrushAllDocs(); + })}> + {this.Icon(color)} + {!this.label || !FontIconBox.GetShowLabels() ? null : ( + <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor }}> + {' '} + {this.label}{' '} + </div> + )} + <div className="menuButton-dropdown" style={{ borderBottomRightRadius: this.dropdown ? 0 : undefined }}> + <FontAwesomeIcon icon={'caret-down'} color={color} size="sm" /> + </div> + {this.rootDoc.dropDownOpen ? <div className="menuButton-dropdownBox">{/* DROPDOWN BOX CONTENTS */}</div> : null} + </div> + ); + } + + /** + * Dropdown list + */ + @computed get dropdownListButton() { + const active: string = StrCast(this.rootDoc.dropDownOpen); + const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); + const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); + + const script = ScriptCast(this.rootDoc.script); + + let noviceList: string[] = []; + let text: string | undefined; + let dropdown = true; + let getStyle: (val: string) => any = () => {}; + let icon: IconProp = 'caret-down'; + let isViewDropdown: boolean = script?.script.originalScript.startsWith('setView') + try { + if (isViewDropdown) { + const selectedDocs: Doc[] = SelectionManager.Docs(); + const selected = SelectionManager.Docs().lastElement(); + console.log('selected') + if (selected) { + if (StrCast(selected.type) === DocumentType.COL) { + text = StrCast(selected._type_collection); + console.log("collection selected", text) + } else { + console.log("doc selected", selected.title); + dropdown = false; + if (selectedDocs.length > 1) { + text = selectedDocs.length + " selected" + } else { + text = Utils.cleanDocumentType(StrCast(selected.type) as DocumentType); + icon = Doc.toIcon(selected); + } + return <Popup + icon={<FontAwesomeIcon size={'1x'} icon={icon} />} + text={text} + type={Type.TERT} + color={color} + popup={<SelectedDocView selectedDocs={selectedDocs}/>} + fillWidth + /> + } + } else { + dropdown = false; + return <Button + text={`None Selected`} + type={Type.TERT} + color={color} + fillWidth + inactive + /> + } + noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking, CollectionViewType.NoteTaking]; + } else { + text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily); + getStyle = (val: string) => { return { fontFamily: val } } + } + } catch (e) { + console.log(e); + } + + console.log("current item: ", text); + + // Get items to place into the list + const list: IListItemProps[] = this.buttonList + .filter(value => !Doc.noviceMode || !noviceList.length || noviceList.includes(value)) + .map(value => ( + { + text: value.charAt(0).toUpperCase() + value.slice(1), + val: value, + style: getStyle(value), + onClick: undoable(() => script.script.run({ self: this.rootDoc, value }), value) + // shortcut: '#', + } + )); + + + return ( + <Dropdown + selectedVal={text} + setSelectedVal={undoable((val) => script.script.run({ self: this.rootDoc, val }), `dropdown select ${this.label}`)} + color={color} + type={isViewDropdown ? Type.TERT : Type.PRIM} + dropdownType={DropdownType.SELECT} + items={list} + tooltip={this.label} + fillWidth + /> + ) + } + + + @computed get colorScript() { + return ScriptCast(this.rootDoc.script); + } + + /** + * Color button + */ + @computed get colorButton() { + const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); + const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); + const curColor = this.colorScript?.script.run({ self: this.rootDoc, value: undefined, _readOnly_: true }).result ?? 'transparent'; + const tooltip: string = StrCast(this.rootDoc.toolTip); + + return ( + <ColorPicker + setSelectedColor={(value) => { + const s = this.colorScript; + s && undoable(() => s.script.run({ self: this.rootDoc, value: value, _readOnly_: false }).result, `Set ${tooltip} to ${value}`)(); + }} + selectedColor={curColor} + type={Type.PRIM} + color={color} + icon={this.Icon(color)!} + tooltip={tooltip} + label={this.label} + /> + ) + } + + @computed get toggleButton() { + // Determine the type of toggle button + const buttonText: string = StrCast(this.rootDoc.buttonText); + const tooltip: string = StrCast(this.rootDoc.toolTip); + + const script = ScriptCast(this.rootDoc.onClick); + const toggleStatus = script ? script.script.run({ self: this.rootDoc, value: undefined, _readOnly_: true }).result : false; + // Colors + const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); + const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); + + console.log(tooltip, toggleStatus); + return ( + <Toggle + tooltip={`Toggle ${tooltip}`} + toggleType={ToggleType.BUTTON} + type={Type.PRIM} + toggleStatus={toggleStatus} + text={buttonText} + color={color} + icon={this.Icon(color)!} + label={this.label} + onPointerDown={() => script.script.run({ self: this.rootDoc, value: !toggleStatus, _readOnly_: false })} + /> + ) + } + + /** + * Default + */ + @computed get defaultButton() { + const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); + const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); + const tooltip: string = StrCast(this.rootDoc.toolTip); + + return ( + <IconButton tooltip={tooltip} icon={this.Icon(color)!} label={this.label}/> + ) + } + + @computed get editableText() { + // Script for running the toggle + const script = ScriptCast(this.rootDoc.script); + // Function to run the script + const checkResult = script?.script.run({ value: '', _readOnly_: true }).result; + + const setValue = (value: string, shiftDown?: boolean): boolean => script?.script.run({ value, _readOnly_: false }).result; + + return <EditableText + editing={false} setEditing={(editing: boolean) => {}} + /> + + return ( + <div className="menuButton editableText"> + <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={'lock'} /> + <div style={{ width: 'calc(100% - .875em)', paddingLeft: '4px' }}> + <EditableView GetValue={() => script?.script.run({ value: '', _readOnly_: true }).result} SetValue={setValue} oneLine={true} contents={checkResult} /> + </div> + </div> + ); + } + + render() { + // determine dash button metadata + const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); + const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); + const tooltip: string = StrCast(this.rootDoc.toolTip); + // TODO:glr Add label of button type + let button: JSX.Element = this.defaultButton; + // prettier-ignore + switch (this.type) { + case ButtonType.EditableText: + button = this.editableText; + break; + case ButtonType.DropdownList: + button = this.dropdownListButton; + break; + case ButtonType.ColorButton: + button = this.colorButton; + break; + case ButtonType.NumberDropdownButton: + case ButtonType.NumberInlineButton: + case ButtonType.NumberSliderButton: + button = this.numberDropdown; + break; + case ButtonType.DropdownButton: + button = this.dropdownButton; + break; + case ButtonType.ToggleButton: button = this.toggleButton; break; + case ButtonType.TextButton: + const script = ScriptCast(this.rootDoc.script); + const checkResult = script?.script.run({ _readOnly_: true }).result; + // Script for checking the outcome of the toggle + button = ( + <Button tooltip={tooltip} color={checkResult ?? backgroundColor} icon={this.Icon(color)!} text={StrCast(this.rootDoc.buttonText)} label={this.label}/> + ); + break; + case ButtonType.ClickButton: + case ButtonType.ToolButton: + button = ( + <IconButton tooltip={tooltip} onPointerDown={() => script?.script.run({ _readOnly_: false })} color={color} icon={this.Icon(color)!} label={this.label}/> + ); + break; + case ButtonType.MenuButton: button = ( + <IconButton tooltip={tooltip} tooltipPlacement='right' size={Size.LARGE} color={color} icon={this.Icon(color)!} label={this.label}/> + ); + break; + } + + return button; + } +} diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx deleted file mode 100644 index 5bba51ec8..000000000 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ /dev/null @@ -1,953 +0,0 @@ -import { IconProp } from '@fortawesome/fontawesome-svg-core'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Tooltip } from '@material-ui/core'; -import { action, computed, observable, runInAction } from 'mobx'; -import { observer } from 'mobx-react'; -import * as React from 'react'; -import { ColorState, SketchPicker } from 'react-color'; -import { Doc, StrListCast } from '../../../../fields/Doc'; -import { Height, Width } from '../../../../fields/DocSymbols'; -import { InkTool } from '../../../../fields/InkField'; -import { ScriptField } from '../../../../fields/ScriptField'; -import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; -import { WebField } from '../../../../fields/URLField'; -import { GestureUtils } from '../../../../pen-gestures/GestureUtils'; -import { aggregateBounds, Utils } from '../../../../Utils'; -import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; -import { LinkManager } from '../../../util/LinkManager'; -import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; -import { SelectionManager } from '../../../util/SelectionManager'; -import { undoable, undoBatch, UndoManager } from '../../../util/UndoManager'; -import { CollectionFreeFormView } from '../../collections/collectionFreeForm'; -import { ContextMenu } from '../../ContextMenu'; -import { DocComponent } from '../../DocComponent'; -import { EditableView } from '../../EditableView'; -import { GestureOverlay } from '../../GestureOverlay'; -import { Colors } from '../../global/globalEnums'; -import { ActiveFillColor, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, SetActiveIsInkMask } from '../../InkingStroke'; -import { InkTranscription } from '../../InkTranscription'; -import { StyleProp } from '../../StyleProvider'; -import { FieldView, FieldViewProps } from '.././FieldView'; -import { CollectionFreeFormDocumentView } from '../CollectionFreeFormDocumentView'; -import { OpenWhere } from '../DocumentView'; -import { RichTextMenu } from '../formattedText/RichTextMenu'; -import { WebBox } from '../WebBox'; -import { FontIconBadge } from './FontIconBadge'; -import './FontIconBox.scss'; - -export enum ButtonType { - TextButton = 'textBtn', - MenuButton = 'menuBtn', - DropdownList = 'drpdownList', - DropdownButton = 'drpdownBtn', - ClickButton = 'clickBtn', - DoubleButton = 'dblBtn', - ToggleButton = 'tglBtn', - ColorButton = 'colorBtn', - ToolButton = 'toolBtn', - NumberSliderButton = 'numSliderBtn', - NumberDropdownButton = 'numDropdownBtn', - NumberInlineButton = 'numInlineBtn', - EditableText = 'editableText', -} - -export interface ButtonProps extends FieldViewProps { - type?: ButtonType; -} -@observer -export class FontIconBox extends DocComponent<ButtonProps>() { - public static LayoutString(fieldKey: string) { - return FieldView.LayoutString(FontIconBox, fieldKey); - } - @observable noTooltip = false; - showTemplate = (): void => { - const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null); - dragFactory && this.props.addDocTab(dragFactory, OpenWhere.addRight); - }; - dragAsTemplate = (): void => { - this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)'); - }; - useAsPrototype = (): void => { - this.layoutDoc.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)'); - }; - - specificContextMenu = (): void => { - if (!Doc.noviceMode) { - const cm = ContextMenu.Instance; - cm.addItem({ description: 'Show Template', event: this.showTemplate, icon: 'tag' }); - cm.addItem({ description: 'Use as Render Template', event: this.dragAsTemplate, icon: 'tag' }); - cm.addItem({ description: 'Use as Prototype', event: this.useAsPrototype, icon: 'tag' }); - } - }; - - static GetShowLabels() { - return BoolCast(Doc.UserDoc()._showLabel); - } - static SetShowLabels(show: boolean) { - Doc.UserDoc()._showLabel = show; - } - static GetRecognizeGestures() { - return BoolCast(Doc.UserDoc()._recognizeGestures); - } - static SetRecognizeGestures(show: boolean) { - Doc.UserDoc()._recognizeGestures = show; - } - - // Determining UI Specs - @computed get label() { - return StrCast(this.rootDoc.icon_label, StrCast(this.rootDoc.title)); - } - Icon = (color: string) => { - const icon = StrCast(this.dataDoc[this.fieldKey ?? 'icon'] ?? this.dataDoc.icon, 'user') as any; - const trailsIcon = () => <img src={`/assets/${'presTrails.png'}`} style={{ width: 30, height: 30, filter: `invert(${color === Colors.DARK_GRAY ? '0%' : '100%'})` }} />; - return !icon ? null : icon === 'pres-trail' ? trailsIcon() : <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />; - }; - @computed get dropdown() { - return BoolCast(this.rootDoc.dropDownOpen); - } - @computed get buttonList() { - return StrListCast(this.rootDoc.btnList); - } - @computed get type() { - return StrCast(this.rootDoc.btnType); - } - - /** - * Types of buttons in dash: - * - Main menu button (LHS) - * - Tool button - * - Expandable button (CollectionLinearView) - * - Button inside of CollectionLinearView vs. outside of CollectionLinearView - * - Action button - * - Dropdown button - * - Color button - * - Dropdown list - * - Number button - **/ - - _batch: UndoManager.Batch | undefined = undefined; - /** - * Number button - */ - @computed get numberSliderButton() { - const numScript = (value?: number) => ScriptCast(this.rootDoc.script).script.run({ self: this.rootDoc, value, _readOnly_: value === undefined }); - // Script for checking the outcome of the toggle - const checkResult = Number(numScript().result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3)); - const label = !FontIconBox.GetShowLabels() ? null : <div className="fontIconBox-label">{this.label}</div>; - - const dropdown = ( - <div className="menuButton-dropdownBox" onPointerDown={e => e.stopPropagation()}> - <input - className="menu-slider" - type="range" - step="1" - min={NumCast(this.rootDoc.numBtnMin, 0)} - max={NumCast(this.rootDoc.numBtnMax, 100)} - //readOnly={true} - value={checkResult} - onPointerDown={() => (this._batch = UndoManager.StartBatch('num slider changing'))} - onPointerUp={() => this._batch?.end()} - onChange={undoable(e => { - e.stopPropagation(); - numScript(Number(e.target.value)); - }, 'set num value')} - /> - </div> - ); - return ( - <div - className="menuButton numBtn slider" - onPointerDown={e => e.stopPropagation()} - onClick={action(() => { - this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen; - this.noTooltip = this.rootDoc.dropDownOpen; - Doc.UnBrushAllDocs(); - })}> - {checkResult} - {label} - {this.rootDoc.dropDownOpen ? dropdown : null} - </div> - ); - } - /** - * Number button - */ - @computed get numberDropdownButton() { - const numScript = (value?: number) => ScriptCast(this.rootDoc.script)?.script.run({ self: this.rootDoc, value, _readOnly_: value === undefined }); - - const checkResult = Number(numScript().result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3)); - - const items: number[] = []; - for (let i = 0; i < 100; i += 2) items.push(i); - - const list = items.map(value => { - return ( - <div - className="list-item" - key={`${value}`} - style={{ - backgroundColor: value.toString() === checkResult ? Colors.LIGHT_BLUE : undefined, - }} - onClick={undoable(value => numScript(value), `${this.rootDoc.title} button set from list`)}> - {value} - </div> - ); - }); - return ( - <div className="menuButton numBtn list"> - <div className="button" onClick={undoable(e => numScript(Number(checkResult) - 1), `${this.rootDoc.title} decrement value`)}> - <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon="minus" /> - </div> - <div - className={`button ${'number'}`} - onPointerDown={e => { - e.stopPropagation(); - e.preventDefault(); - }} - onClick={action(() => { - this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen; - this.noTooltip = this.rootDoc.dropDownOpen; - Doc.UnBrushAllDocs(); - })}> - <input style={{ width: 30 }} className="button-input" type="number" value={checkResult} readOnly={true} onChange={undoable(e => numScript(Number(e.target.value)), `${this.rootDoc.title} button set value`)} /> - </div> - <div className={`button`} onClick={undoable(e => numScript(Number(checkResult) + 1), `${this.rootDoc.title} increment value`)}> - <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={'plus'} /> - </div> - - {this.rootDoc.dropDownOpen ? ( - <div> - <div className="menuButton-dropdownList" style={{ left: '25%' }}> - {list} - </div> - <div - className="dropbox-background" - onClick={action(e => { - e.stopPropagation(); - this.rootDoc.dropDownOpen = false; - this.noTooltip = false; - Doc.UnBrushAllDocs(); - })} - /> - </div> - ) : null} - </div> - ); - } - /** - * Number button - */ - @computed get numberInlineButton() { - return <div />; - } - - /** - * Dropdown button - */ - @computed get dropdownButton() { - const active: string = StrCast(this.rootDoc.dropDownOpen); - const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); - const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - return ( - <div - className={`menuButton ${this.type} ${active}`} - style={{ color: color, backgroundColor: backgroundColor, borderBottomLeftRadius: this.dropdown ? 0 : undefined }} - onClick={action(() => { - this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen; - this.noTooltip = this.rootDoc.dropDownOpen; - Doc.UnBrushAllDocs(); - })}> - {this.Icon(color)} - {!this.label || !FontIconBox.GetShowLabels() ? null : ( - <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor }}> - {' '} - {this.label}{' '} - </div> - )} - <div className="menuButton-dropdown" style={{ borderBottomRightRadius: this.dropdown ? 0 : undefined }}> - <FontAwesomeIcon icon={'caret-down'} color={color} size="sm" /> - </div> - {this.rootDoc.dropDownOpen ? <div className="menuButton-dropdownBox">{/* DROPDOWN BOX CONTENTS */}</div> : null} - </div> - ); - } - - /** - * Dropdown list - */ - @computed get dropdownListButton() { - const active: string = StrCast(this.rootDoc.dropDownOpen); - const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); - const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - - const script = ScriptCast(this.rootDoc.script); - - let noviceList: string[] = []; - let text: string | undefined; - let dropdown = true; - let icon: IconProp = 'caret-down'; - try { - if (script?.script.originalScript.startsWith('setView')) { - const selected = SelectionManager.Docs().lastElement(); - if (selected) { - if (StrCast(selected.type) === DocumentType.COL) { - text = StrCast(selected._type_collection); - } else { - dropdown = false; - text = selected.type === DocumentType.RTF ? 'Text' : StrCast(selected.type); - icon = Doc.toIcon(selected); - } - } else { - dropdown = false; - icon = 'globe-asia'; - text = 'User Default'; - } - noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking, CollectionViewType.NoteTaking]; - } else text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily); - } catch (e) { - console.log(e); - } - - // Get items to place into the list - const list = this.buttonList - .filter(value => !Doc.noviceMode || !noviceList.length || noviceList.includes(value)) - .map(value => ( - <div - className="list-item" - key={value} - style={{ - fontFamily: script.script.originalScript.startsWith('{ return setFont') ? value : undefined, - backgroundColor: value === text ? Colors.LIGHT_BLUE : undefined, - }} - onClick={undoable(() => script.script.run({ self: this.rootDoc, value }), value)}> - {value[0].toUpperCase() + value.slice(1)} - </div> - )); - - const label = - !this.label || !FontIconBox.GetShowLabels() ? null : ( - <div className="fontIconBox-label" style={{ bottom: 0, position: 'absolute', color: color, backgroundColor: backgroundColor }}> - {this.label} - </div> - ); - - return ( - <div - className={`menuButton ${this.type} ${active}`} - style={{ backgroundColor: this.rootDoc.dropDownOpen ? Colors.MEDIUM_BLUE : backgroundColor, color: color, display: dropdown ? undefined : 'flex' }} - onClick={ - dropdown - ? action(() => { - this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen; - this.noTooltip = this.rootDoc.dropDownOpen; - Doc.UnBrushAllDocs(); - }) - : undefined - }> - {dropdown ? null : <FontAwesomeIcon style={{ marginLeft: 5 }} className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />} - <div className="menuButton-dropdown-header">{text && text[0].toUpperCase() + text.slice(1)}</div> - {label} - {!dropdown ? null : ( - <div className="menuButton-dropDown"> - <FontAwesomeIcon icon={icon} color={color} size="sm" /> - </div> - )} - {this.rootDoc.dropDownOpen ? ( - <div> - <div className="menuButton-dropdownList" style={{ left: 0 }}> - {list} - </div> - <div - className="dropbox-background" - onClick={action(e => { - e.stopPropagation(); - this.rootDoc.dropDownOpen = false; - this.noTooltip = false; - Doc.UnBrushAllDocs(); - })} - /> - </div> - ) : null} - </div> - ); - } - - @observable colorPickerClosed: boolean = true; - @computed get colorScript() { - return ScriptCast(this.rootDoc.script); - } - - colorPicker = (curColor: string) => { - const change = (value: ColorState, ev: MouseEvent) => { - ev.preventDefault(); - ev.stopPropagation(); - const s = this.colorScript; - s && undoBatch(() => s.script.run({ self: this.rootDoc, value: Utils.colorString(value), _readOnly_: false }).result)(); - }; - const presets = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']; - return <SketchPicker onChange={change as any /* SketchPicker passes the mouse event to the callback, but the type system doesn't know that */} color={curColor} presetColors={presets} />; - }; - /** - * Color button - */ - @computed get colorButton() { - const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); - const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - const curColor = this.colorScript?.script.run({ self: this.rootDoc, value: undefined, _readOnly_: true }).result ?? 'transparent'; - - const label = - !this.label || !FontIconBox.GetShowLabels() ? null : ( - <div className="fontIconBox-label" style={{ color, backgroundColor }}> - {this.label} - </div> - ); - - return ( - <div - className={`menuButton ${this.type + (FontIconBox.GetShowLabels() ? 'Label' : '')} ${this.colorPickerClosed}`} - style={{ color: color, borderBottomLeftRadius: this.dropdown ? 0 : undefined }} - onClick={action(e => { - this.colorPickerClosed = !this.colorPickerClosed; - this.noTooltip = !this.colorPickerClosed; - setTimeout(() => Doc.UnBrushAllDocs()); - e.stopPropagation(); - })} - onPointerDown={e => e.stopPropagation()}> - {this.Icon(color)} - <div className="colorButton-color" style={{ backgroundColor: curColor }} /> - {label} - {/* {dropdownCaret} */} - {this.colorPickerClosed ? null : ( - <div> - <div className="menuButton-dropdownBox" onPointerDown={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onClick={e => e.stopPropagation()}> - {this.colorPicker(curColor)} - </div> - <div - className="dropbox-background" - onPointerDown={action(e => { - e.preventDefault(); - e.stopPropagation(); - this.colorPickerClosed = true; - this.noTooltip = false; - Doc.UnBrushAllDocs(); - })} - /> - </div> - )} - </div> - ); - } - - @computed get toggleButton() { - // Determine the type of toggle button - const switchToggle: boolean = BoolCast(this.rootDoc.switchToggle); - const buttonText: string = StrCast(this.rootDoc.buttonText); - // Colors - const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); - const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - - // Button label - const label = - !this.label || !FontIconBox.GetShowLabels() ? null : ( - <div className="fontIconBox-label" style={{ color, backgroundColor }}> - {this.label} - </div> - ); - - if (switchToggle) { - return ( - <div className={`menuButton ${this.type} ${'switch'}`}> - {buttonText ? buttonText : null} - <label className="switch"> - <input type="checkbox" checked={backgroundColor === Colors.MEDIUM_BLUE} /> - <span className="slider round" /> - </label> - </div> - ); - } else { - return ( - <div className={`menuButton ${this.type + (FontIconBox.GetShowLabels() ? 'Label' : '')}`} style={{ opacity: 1, backgroundColor, color }}> - {this.Icon(color)} - {label} - </div> - ); - } - } - - /** - * Default - */ - @computed get defaultButton() { - const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); - const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - return ( - <div className={`menuButton ${this.type}`} onContextMenu={this.specificContextMenu} style={{ backgroundColor: 'transparent', borderBottomLeftRadius: this.dropdown ? 0 : undefined }}> - <div className="menuButton-wrap"> - {this.Icon(color)} - {!this.label || !FontIconBox.GetShowLabels() ? null : ( - <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor }}> - {' '} - {this.label}{' '} - </div> - )} - </div> - </div> - ); - } - - @computed get editableText() { - // Script for running the toggle - const script = ScriptCast(this.rootDoc.script); - // Function to run the script - const checkResult = script?.script.run({ value: '', _readOnly_: true }).result; - - const setValue = (value: string, shiftDown?: boolean): boolean => script?.script.run({ value, _readOnly_: false }).result; - return ( - <div className="menuButton editableText"> - <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={'lock'} /> - <div style={{ width: 'calc(100% - .875em)', paddingLeft: '4px' }}> - <EditableView GetValue={() => script?.script.run({ value: '', _readOnly_: true }).result} SetValue={setValue} oneLine={true} contents={checkResult} /> - </div> - </div> - ); - } - - render() { - const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); - const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - const label = (noBackground: boolean = false) => - !this.label || !FontIconBox.GetShowLabels() ? null : ( - <div className="fontIconBox-label" style={{ color, backgroundColor: noBackground ? 'transparent' : backgroundColor }}> - {this.label} - </div> - ); - // TODO:glr Add label of button type - let button: JSX.Element = this.defaultButton; - - // prettier-ignore - switch (this.type) { - case ButtonType.EditableText: return this.editableText; - case ButtonType.DropdownList: button = this.dropdownListButton; break; - case ButtonType.ColorButton: button = this.colorButton; break; - case ButtonType.NumberDropdownButton: button = this.numberDropdownButton; break; - case ButtonType.NumberInlineButton: button = this.numberInlineButton; break; - case ButtonType.NumberSliderButton: button = this.numberSliderButton; break; - case ButtonType.DropdownButton: button = this.dropdownButton; break; - case ButtonType.ToggleButton: button = this.toggleButton; break; - case ButtonType.TextButton: - // Script for checking the outcome of the toggle - const script = ScriptCast(this.rootDoc.script); - const checkResult = script?.script.run({ _readOnly_: true }).result; - button = ( - <div className={`menuButton ${this.type}`} style={{ color, backgroundColor:checkResult ?? backgroundColor, opacity: 1, gridAutoColumns: `${NumCast(this.rootDoc._height)} auto` }}> - {this.Icon(color)} - {StrCast(this.rootDoc.buttonText) ? <div className="button-text">{StrCast(this.rootDoc.buttonText)}</div> : null} - {label()} - </div> - ); - break; - case ButtonType.ClickButton: - case ButtonType.ToolButton: button = ( - <div className={`menuButton ${this.type + (FontIconBox.GetShowLabels() ? 'Label' : '')}`} style={{ backgroundColor, color, opacity: 1 }}> - {this.Icon(color)} - {label()} - </div> - ); - break; - case ButtonType.MenuButton: button = ( - <div className={`menuButton ${this.type}`} style={{ color, backgroundColor }}> - {this.Icon(color)} - {label(true)} - <FontIconBadge value={Cast(this.Document.badgeValue, 'string', null)} /> - </div> - ); - break; - } - - return !this.layoutDoc.toolTip || this.noTooltip ? button : <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>{button}</Tooltip>; - } -} - -// toggle: Set overlay status of selected document -ScriptingGlobals.add(function setView(view: string) { - const selected = SelectionManager.Docs().lastElement(); - selected ? (selected._type_collection = view) : console.log('[FontIconBox.tsx] changeView failed'); -}); - -// toggle: Set overlay status of selected document -ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: boolean) { - const selectedViews = SelectionManager.Views(); - if (Doc.ActiveTool !== InkTool.None) { - if (checkResult) { - return ActiveFillColor(); - } - SetActiveFillColor(color ?? 'transparent'); - } else if (selectedViews.length) { - if (checkResult) { - const selView = selectedViews.lastElement(); - const fieldKey = selView.rootDoc.type === DocumentType.INK ? 'fillColor' : 'backgroundColor'; - const layoutFrameNumber = Cast(selView.props.docViewPath().lastElement()?.rootDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values - const contentFrameNumber = Cast(selView.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed - return CollectionFreeFormDocumentView.getStringValues(selView?.rootDoc, contentFrameNumber)[fieldKey] ?? 'transparent'; - } - selectedViews.forEach(dv => { - const fieldKey = dv.rootDoc.type === DocumentType.INK ? 'fillColor' : 'backgroundColor'; - const layoutFrameNumber = Cast(dv.props.docViewPath().lastElement()?.rootDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values - const contentFrameNumber = Cast(dv.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed - if (contentFrameNumber !== undefined) { - CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.rootDoc, { fieldKey: color }); - } else { - dv.rootDoc['_' + fieldKey] = color; - } - }); - } else { - const selected = SelectionManager.Docs().length ? SelectionManager.Docs() : LinkManager.currentLink ? [LinkManager.currentLink] : []; - if (checkResult) { - return selected.lastElement()?._backgroundColor ?? 'transparent'; - } - selected.forEach(doc => (doc._backgroundColor = color)); - } -}); - -// toggle: Set overlay status of selected document -ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boolean) { - if (checkResult) { - return Doc.SharingDoc().userColor; - } - Doc.SharingDoc().userColor = undefined; - Doc.GetProto(Doc.SharingDoc()).userColor = color; - Doc.UserDoc().layout_showTitle = color === 'transparent' ? undefined : StrCast(Doc.UserDoc().layout_showTitle, 'author_date'); -}); - -// toggle: Set overlay status of selected document -ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) { - const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined; - if (checkResult) { - if (NumCast(selected?.Document.z) >= 1) return Colors.MEDIUM_BLUE; - return 'transparent'; - } - selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log('[FontIconBox.tsx] toggleOverlay failed'); -}); - -ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll', checkResult?: boolean) { - const selected = SelectionManager.Docs().lastElement(); - // prettier-ignore - const map: Map<'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([ - ['grid', { - checkResult: (doc:Doc) => doc._freeform_backgroundGrid, - setDoc: (doc:Doc) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid, - }], - ['snaplines', { - checkResult: (doc:Doc) => doc._freeform_snapLines, - setDoc: (doc:Doc) => doc._freeform_snapLines = !doc._freeform_snapLines, - }], - ['viewAll', { - checkResult: (doc:Doc) => doc._freeform_fitContentsToBox, - setDoc: (doc:Doc) => doc._freeform_fitContentsToBox = !doc._freeform_fitContentsToBox, - }], - ['clusters', { - waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire - checkResult: (doc:Doc) => doc._freeform_useClusters, - setDoc: (doc:Doc) => doc._freeform_useClusters = !doc._freeform_useClusters, - }], - ['arrange', { - waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire - checkResult: (doc:Doc) => doc._autoArrange, - setDoc: (doc:Doc) => doc._autoArrange = !doc._autoArrange, - }], - ['flashcards', { - checkResult: (doc:Doc) => Doc.UserDoc().defaultToFlashcards, - setDoc: (doc:Doc) => Doc.UserDoc().defaultToFlashcards = !Doc.UserDoc().defaultToFlashcards, - }], - ]); - - if (checkResult) { - return map.get(attr)?.checkResult(selected) ? Colors.MEDIUM_BLUE : 'transparent'; - } - const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} }; - SelectionManager.Docs().map(dv => map.get(attr)?.setDoc(dv)); - setTimeout(() => batch.end(), 100); -}); -ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highlight' | 'fontSize', value: any, checkResult?: boolean) { - const editorView = RichTextMenu.Instance?.TextView?.EditorView; - const selected = SelectionManager.Docs().lastElement(); - // prettier-ignore - const map: Map<'font'|'fontColor'|'highlight'|'fontSize', { checkResult: () => any; setDoc: () => void;}> = new Map([ - ['font', { - checkResult: () => RichTextMenu.Instance?.fontFamily, - setDoc: () => value && RichTextMenu.Instance.setFontFamily(value), - }], - ['highlight', { - checkResult: () =>(selected ?? Doc.UserDoc())._fontHighlight, - setDoc: () => value && RichTextMenu.Instance.setHighlight(value), - }], - ['fontColor', { - checkResult: () => RichTextMenu.Instance?.fontColor, - setDoc: () => value && RichTextMenu.Instance.setColor(value), - }], - ['fontSize', { - checkResult: () => RichTextMenu.Instance?.fontSize.replace('px', ''), - setDoc: () => { - if (typeof value === 'number') value = value.toString(); - if (value && Number(value).toString() === value) value += 'px'; - RichTextMenu.Instance.setFontSize(value); - }, - }], - ]); - - if (checkResult) { - return map.get(attr)?.checkResult(); - } - map.get(attr)?.setDoc?.(); -}); - -type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'underline' | 'left' | 'center' | 'right' | 'bullet' | 'decimal'; -type attrfuncs = [attrname, { checkResult: () => boolean; toggle: () => any }]; -ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: boolean) { - const textView = RichTextMenu.Instance?.TextView; - const editorView = textView?.EditorView; - // prettier-ignore - const alignments:attrfuncs[] = (['left','right','center'] as ("left"|"center"|"right")[]).map((where) => - [ where, { checkResult: () =>(editorView ? (RichTextMenu.Instance.textAlign ===where): (Doc.UserDoc().textAlign ===where) ? true:false), - toggle: () => (editorView?.state ? RichTextMenu.Instance.align(editorView, editorView.dispatch, where):(Doc.UserDoc().textAlign = where))}]); - // prettier-ignore - const listings:attrfuncs[] = (['bullet','decimal'] as attrname[]).map(list => - [ list, { checkResult: () => (editorView ? RichTextMenu.Instance.getActiveListStyle() === list:false), - toggle: () => editorView?.state && RichTextMenu.Instance.changeListType(list) }]); - // prettier-ignore - const attrs:attrfuncs[] = [ - ['dictation', { checkResult: () => textView?._recording ? true:false, - toggle: () => textView && runInAction(() => (textView._recording = !textView._recording)) }], - ['noAutoLink',{ checkResult: () => (editorView ? RichTextMenu.Instance.noAutoLink : false), - toggle: () => editorView && RichTextMenu.Instance?.toggleNoAutoLinkAnchor()}], - ['bold', { checkResult: () => (editorView ? RichTextMenu.Instance.bold : (Doc.UserDoc().fontWeight === 'bold') ? true:false), - toggle: editorView ? RichTextMenu.Instance.toggleBold : () => (Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold')}], - ['italics', { checkResult: () => (editorView ? RichTextMenu.Instance.italics : (Doc.UserDoc().fontStyle === 'italics') ? true:false), - toggle: editorView ? RichTextMenu.Instance.toggleItalics : () => (Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics')}], - ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance.underline : (Doc.UserDoc().textDecoration === 'underline') ? true:false), - toggle: editorView ? RichTextMenu.Instance.toggleUnderline : () => (Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline') }]] - - const map = new Map(attrs.concat(alignments).concat(listings)); - if (checkResult) return map.get(charStyle)?.checkResult() ? Colors.MEDIUM_BLUE : 'transparent'; - map.get(charStyle)?.toggle(); -}); - -export function checkInksToGroup() { - // console.log("getting here to inks group"); - if (Doc.ActiveTool === InkTool.Write) { - CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => { - // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those - // find all inkDocs in ffView.unprocessedDocs that are within 200 pixels of each other - const inksToGroup = ffView.unprocessedDocs.filter(inkDoc => { - // console.log(inkDoc.x, inkDoc.y); - }); - }); - } -} - -export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) { - // TODO nda - if document being added to is a inkGrouping then we can just add to that group - if (Doc.ActiveTool === InkTool.Write) { - CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => { - // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those - const selected = ffView.unprocessedDocs; - // loop through selected an get the bound - const bounds: { x: number; y: number; width?: number; height?: number }[] = []; - - selected.map( - action(d => { - const x = NumCast(d.x); - const y = NumCast(d.y); - const width = d[Width](); - const height = d[Height](); - bounds.push({ x, y, width, height }); - }) - ); - - const aggregBounds = aggregateBounds(bounds, 0, 0); - const marqViewRef = ffView._marqueeViewRef.current; - - // set the vals for bounds in marqueeView - if (marqViewRef) { - marqViewRef._downX = aggregBounds.x; - marqViewRef._downY = aggregBounds.y; - marqViewRef._lastX = aggregBounds.r; - marqViewRef._lastY = aggregBounds.b; - } - - selected.map( - action(d => { - const dx = NumCast(d.x); - const dy = NumCast(d.y); - delete d.x; - delete d.y; - delete d.activeFrame; - delete d._timecodeToShow; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection - delete d._timecodeToHide; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection - // calculate pos based on bounds - if (marqViewRef?.Bounds) { - d.x = dx - marqViewRef.Bounds.left - marqViewRef.Bounds.width / 2; - d.y = dy - marqViewRef.Bounds.top - marqViewRef.Bounds.height / 2; - } - return d; - }) - ); - ffView.props.removeDocument?.(selected); - // TODO: nda - this is the code to actually get a new grouped collection - const newCollection = marqViewRef?.getCollection(selected, undefined, true); - if (newCollection) { - newCollection.height = newCollection[Height](); - newCollection.width = newCollection[Width](); - } - - // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs - newCollection && ffView.props.addDocument?.(newCollection); - // TODO: nda - will probably need to go through and only remove the unprocessed selected docs - ffView.unprocessedDocs = []; - - InkTranscription.Instance.transcribeInk(newCollection, selected, false); - }); - } - CollectionFreeFormView.collectionsWithUnprocessedInk.clear(); -} - -function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean, checkResult?: boolean) { - InkTranscription.Instance?.createInkGroup(); - if (checkResult) { - return (Doc.ActiveTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool - ? GestureOverlay.Instance?.KeepPrimitiveMode || ![GestureUtils.Gestures.Circle, GestureUtils.Gestures.Line, GestureUtils.Gestures.Rectangle].includes(tool as GestureUtils.Gestures) - ? Colors.MEDIUM_BLUE - : Colors.MEDIUM_BLUE_ALT - : 'transparent'; - } - runInAction(() => { - if (GestureOverlay.Instance) { - GestureOverlay.Instance.KeepPrimitiveMode = keepPrim; - } - if (Object.values(GestureUtils.Gestures).includes(tool as any)) { - if (GestureOverlay.Instance.InkShape === tool && !keepPrim) { - Doc.ActiveTool = InkTool.None; - GestureOverlay.Instance.InkShape = undefined; - } else { - Doc.ActiveTool = InkTool.Pen; - GestureOverlay.Instance.InkShape = tool as GestureUtils.Gestures; - } - } else if (tool) { - // pen or eraser - if (Doc.ActiveTool === tool && !GestureOverlay.Instance.InkShape && !keepPrim) { - Doc.ActiveTool = InkTool.None; - } else { - Doc.ActiveTool = tool as any; - GestureOverlay.Instance.InkShape = undefined; - } - } else { - Doc.ActiveTool = InkTool.None; - } - }); -} - -ScriptingGlobals.add(setActiveTool, 'sets the active ink tool mode'); - -// toggle: Set overlay status of selected document -ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', value: any, checkResult?: boolean) { - const selected = SelectionManager.Docs().lastElement(); - // prettier-ignore - const map: Map<'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', { checkResult: () => any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([ - ['inkMask', { - checkResult: () => ((selected?.type === DocumentType.INK ? BoolCast(selected.stroke_isInkMask) : ActiveIsInkMask()) ? Colors.MEDIUM_BLUE : 'transparent'), - setInk: (doc: Doc) => (doc.stroke_isInkMask = !doc.stroke_isInkMask), - setMode: () => selected?.type !== DocumentType.INK && SetActiveIsInkMask(!ActiveIsInkMask()), - }], - ['fillColor', { - checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.fillColor) : ActiveFillColor() ?? "transparent"), - setInk: (doc: Doc) => (doc.fillColor = StrCast(value)), - setMode: () => SetActiveFillColor(StrCast(value)), - }], - [ 'strokeWidth', { - checkResult: () => (selected?.type === DocumentType.INK ? NumCast(selected.stroke_width) : ActiveInkWidth()), - setInk: (doc: Doc) => (doc.stroke_width = NumCast(value)), - setMode: () => SetActiveInkWidth(value.toString()), - }], - ['strokeColor', { - checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.color) : ActiveInkColor()), - setInk: (doc: Doc) => (doc.color = String(value)), - setMode: () => SetActiveInkColor(StrCast(value)), - }], - ]); - - if (checkResult) { - return map.get(option)?.checkResult(); - } - map.get(option)?.setMode(); - SelectionManager.Docs() - .filter(doc => doc.type === DocumentType.INK) - .map(doc => map.get(option)?.setInk(doc)); -}); - -/** WEB - * webSetURL - **/ -ScriptingGlobals.add(function webSetURL(url: string, checkResult?: boolean) { - const selected = SelectionManager.Views().lastElement(); - if (selected?.rootDoc.type === DocumentType.WEB) { - if (checkResult) { - return StrCast(selected.rootDoc.data, Cast(selected.rootDoc.data, WebField, null)?.url?.href); - } - selected.ComponentView?.setData?.(url); - //selected.rootDoc.data = new WebField(url); - } -}); -ScriptingGlobals.add(function webForward(checkResult?: boolean) { - const selected = SelectionManager.Views().lastElement()?.ComponentView as WebBox; - if (checkResult) { - return selected?.forward(checkResult) ? undefined : 'lightGray'; - } - selected?.forward(); -}); -ScriptingGlobals.add(function webBack(checkResult?: boolean) { - const selected = SelectionManager.Views().lastElement()?.ComponentView as WebBox; - if (checkResult) { - return selected?.back(checkResult) ? undefined : 'lightGray'; - } - selected?.back(); -}); - -/** Schema - * toggleSchemaPreview - **/ -ScriptingGlobals.add(function toggleSchemaPreview(checkResult?: boolean) { - const selected = SelectionManager.Docs().lastElement(); - if (checkResult && selected) { - const result: boolean = NumCast(selected.schema_previewWidth) > 0; - if (result) return Colors.MEDIUM_BLUE; - else return 'transparent'; - } else if (selected) { - if (NumCast(selected.schema_previewWidth) > 0) { - selected.schema_previewWidth = 0; - } else { - selected.schema_previewWidth = 200; - } - } -}); -ScriptingGlobals.add(function toggleSingleLineSchema(checkResult?: boolean) { - const selected = SelectionManager.Docs().lastElement(); - if (checkResult && selected) { - return NumCast(selected._schema_singleLine) > 0 ? Colors.MEDIUM_BLUE : 'transparent'; - } - if (selected) { - selected._schema_singleLine = !selected._schema_singleLine; - } -}); - -/** STACK - * groupBy - */ -ScriptingGlobals.add(function setGroupBy(key: string, checkResult?: boolean) { - SelectionManager.Docs().map(doc => (doc._text_fontFamily = key)); - const editorView = RichTextMenu.Instance.TextView?.EditorView; - if (checkResult) { - return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily); - } - if (editorView) RichTextMenu.Instance.setFontFamily(key); - else Doc.UserDoc().fontFamily = key; -}); diff --git a/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx b/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx deleted file mode 100644 index 74c3c563c..000000000 --- a/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import React, { Component } from 'react'; -import { BoolCast, StrCast } from '../../../../../fields/Types'; -import { IButtonProps } from '../ButtonInterface'; -import { ColorState, SketchPicker } from 'react-color'; -import { ScriptField } from '../../../../../fields/ScriptField'; -import { Doc } from '../../../../../fields/Doc'; -import { FontIconBox } from '../FontIconBox'; - -export class ColorDropdown extends Component<IButtonProps> { - render() { - const active: string = StrCast(this.props.rootDoc.dropDownOpen); - - const script: string = StrCast(this.props.rootDoc.script); - const scriptCheck: string = script + '(undefined, true)'; - const boolResult = ScriptField.MakeScript(scriptCheck)?.script.run().result; - - const stroke: boolean = false; - // if (script === "setStrokeColor") { - // stroke = true; - // const checkWidth = ScriptField.MakeScript("setStrokeWidth(0, true)")?.script.run().result; - // const width = 20 + (checkWidth / 100) * 70; - // const height = 20 + (checkWidth / 100) * 70; - // strokeIcon = (<div style={{ borderRadius: "100%", width: width + '%', height: height + '%', backgroundColor: boolResult ? boolResult : "#FFFFFF" }} />); - // } - - const colorOptions: string[] = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb']; - - const colorBox = (func: (color: ColorState) => void) => <SketchPicker disableAlpha={!stroke} onChange={func} color={boolResult ? boolResult : '#FFFFFF'} presetColors={colorOptions} />; - const label = - !this.props.label || !FontIconBox.GetShowLabels() ? null : ( - <div className="fontIconBox-label" style={{ color: this.props.color, backgroundColor: this.props.backgroundColor, position: 'absolute' }}> - {this.props.label} - </div> - ); - - const dropdownCaret = ( - <div className="menuButton-dropDown" style={{ borderBottomRightRadius: active ? 0 : undefined }}> - <FontAwesomeIcon icon={'caret-down'} color={this.props.color} size="sm" /> - </div> - ); - - const click = (value: ColorState) => { - const hex: string = value.hex; - const s = ScriptField.MakeScript(script + '("' + hex + '", false)'); - if (s) { - s.script.run().result; - } - }; - return ( - <div - className={`menuButton ${this.props.type} ${active}`} - style={{ color: this.props.color, borderBottomLeftRadius: active ? 0 : undefined }} - onClick={() => (this.props.rootDoc.dropDownOpen = !this.props.rootDoc.dropDownOpen)} - onPointerDown={e => e.stopPropagation()}> - <FontAwesomeIcon className={`fontIconBox-icon-${this.props.type}`} icon={this.props.icon} color={this.props.color} /> - <div className="colorButton-color" style={{ backgroundColor: boolResult ? boolResult : '#FFFFFF' }} /> - {label} - {/* {dropdownCaret} */} - {this.props.rootDoc.dropDownOpen ? ( - <div> - <div className="menuButton-dropdownBox" onPointerDown={e => e.stopPropagation()} onClick={e => e.stopPropagation()}> - {colorBox(click)} - </div> - <div - className="dropbox-background" - onClick={e => { - e.stopPropagation(); - this.props.rootDoc.dropDownOpen = false; - }} - /> - </div> - ) : null} - </div> - ); - } -} diff --git a/src/client/views/nodes/button/colorDropdown/index.ts b/src/client/views/nodes/button/colorDropdown/index.ts deleted file mode 100644 index 1147d6457..000000000 --- a/src/client/views/nodes/button/colorDropdown/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ColorDropdown';
\ No newline at end of file diff --git a/src/client/views/nodes/button/textButton/TextButton.tsx b/src/client/views/nodes/button/textButton/TextButton.tsx deleted file mode 100644 index 5d7d55863..000000000 --- a/src/client/views/nodes/button/textButton/TextButton.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import React, { Component } from 'react'; -import { BoolCast } from '../../../../../fields/Types'; -import { IButtonProps } from '../ButtonInterface'; - -export class TextButton extends Component<IButtonProps> { - render() { - const type = this.props.type; - // Determine the type of toggle button - const buttonText: boolean = BoolCast(this.props.rootDoc.switchToggle); - - return ( - <div - className={`menuButton ${this.props.type}`} - style={{ - opacity: 1, - backgroundColor: this.props.backgroundColor, - color: this.props.color, - }} - > - <FontAwesomeIcon - className={`fontIconBox-icon-${this.props.type}`} - icon={this.props.icon} - color={this.props.color} - /> - {this.props.label} - </div> - ); - } -} diff --git a/src/client/views/nodes/button/textButton/index.ts b/src/client/views/nodes/button/textButton/index.ts deleted file mode 100644 index 01d62eb7e..000000000 --- a/src/client/views/nodes/button/textButton/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './TextButton';
\ No newline at end of file diff --git a/src/client/views/nodes/button/toggleButton/ToggleButton.tsx b/src/client/views/nodes/button/toggleButton/ToggleButton.tsx deleted file mode 100644 index dca6487d8..000000000 --- a/src/client/views/nodes/button/toggleButton/ToggleButton.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import React, { Component } from 'react'; -import { BoolCast } from '../../../../../fields/Types'; -import { Colors } from '../../../global/globalEnums'; -import { IButtonProps } from '../ButtonInterface'; - -export class ToggleButton extends Component<IButtonProps> { - render() { - const type = this.props.type; - // Determine the type of toggle button - const switchToggle: boolean = BoolCast(this.props.rootDoc.switchToggle); - - if (switchToggle) { - return ( - <div className={`menuButton ${type} ${'switch'}`}> - <label className="switch"> - <input type="checkbox" - checked={this.props.backgroundColor === Colors.MEDIUM_BLUE} - /> - <span className="slider round"></span> - </label> - </div> - ); - } else { - return ( - <div className={`menuButton ${type}`} - style={{ opacity: 1, backgroundColor: this.props.backgroundColor, color: this.props.color }}> - <FontAwesomeIcon className={`fontIconBox-icon-${type}`} icon={this.props.icon} color={this.props.color} /> - {this.props.label} - </div> - ); - } - } -}
\ No newline at end of file diff --git a/src/client/views/nodes/button/toggleButton/index.ts b/src/client/views/nodes/button/toggleButton/index.ts deleted file mode 100644 index cdb9c527c..000000000 --- a/src/client/views/nodes/button/toggleButton/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ToggleButton';
\ No newline at end of file diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 44cb56d53..a0eb328a1 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -12,8 +12,8 @@ import { Fragment, Mark, Node, Slice } from 'prosemirror-model'; import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from 'prosemirror-state'; import { EditorView } from 'prosemirror-view'; import { DateField } from '../../../../fields/DateField'; -import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc'; -import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, ForceServerWrite, Height, UpdatingFromServer, Width } from '../../../../fields/DocSymbols'; +import { Doc, DocListCast, StrListCast, Field, Opt } from '../../../../fields/Doc'; +import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, Height, Width, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { InkTool } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; @@ -34,7 +34,6 @@ import { DictationManager } from '../../../util/DictationManager'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; import { MakeTemplate } from '../../../util/DropConverter'; -import { IsFollowLinkScript } from '../../../util/LinkFollower'; import { LinkManager } from '../../../util/LinkManager'; import { RTFMarkup } from '../../../util/RTFMarkup'; import { SelectionManager } from '../../../util/SelectionManager'; @@ -305,13 +304,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const newText = state.doc.textBetween(0, state.doc.content.size, ' \n'); const newJson = JSON.stringify(state.toJSON()); const prevData = Cast(this.layoutDoc[this.fieldKey], RichTextField, null); // the actual text in the text box - const prevLayoutData = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template + const templateData = this.rootDoc !== this.layoutDoc ? prevData : undefined; // the default text stored in a layout template const protoData = Cast(Cast(dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype const effectiveAcl = GetEffectiveAcl(dataDoc); - const removeSelection = (json: string | undefined) => (json?.indexOf('"storedMarks"') === -1 ? json?.replace(/"selection":.*/, '') : json?.replace(/"selection":"\"storedMarks\""/, '"storedMarks"')); + const removeSelection = (json: string | undefined) => json?.replace(/"selection":.*/, ''); - if ([AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl)) { + if ([AclEdit, AclAdmin, AclSelfEdit, AclAugment].includes(effectiveAcl)) { const accumTags = [] as string[]; state.tr.doc.nodesBetween(0, state.doc.content.size, (node: any, pos: number, parent: any) => { if (node.type === schema.nodes.dashField && node.attrs.fieldKey.startsWith('#')) { @@ -325,12 +324,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps this._applyingChange = this.fieldKey; const textChange = newText !== prevData?.Text; textChange && (dataDoc[this.fieldKey + '_modificationDate'] = new DateField(new Date(Date.now()))); - if ((!prevData && !protoData) || newText || (!newText && !protoData)) { + if ((!prevData && !protoData) || newText || (!newText && !templateData)) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) - if (removeSelection(newJson) !== removeSelection(prevLayoutData?.Data)) { + if (this.props.isContentActive() && removeSelection(newJson) !== removeSelection(prevData?.Data)) { const numstring = NumCast(dataDoc[this.fieldKey], null); dataDoc[this.fieldKey] = numstring !== undefined ? Number(newText) : new RichTextField(newJson, newText); - dataDoc[this.fieldKey + '_noTemplate'] = true; //(curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited + dataDoc[this.fieldKey + '_noTemplate'] = true; // mark the data field as being split from the template if it has been edited textChange && ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: newText }); unchanged = false; } @@ -415,6 +414,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps DocListCast(Doc.MyPublishedDocs.data).forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks))); tr = tr.setSelection(isNodeSel && false ? new NodeSelection(tr.doc.resolve(f)) : new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t))); this._editorView?.dispatch(tr); + this.prepareForTyping(); } oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.rootDoc).forEach(LinkManager.Instance.deleteLink); }; @@ -564,7 +564,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } if (added) { draggedDoc._freeform_fitContentsToBox = true; - draggedDoc.embedContainer = this.rootDoc; + Doc.SetContainer(draggedDoc, this.rootDoc); const view = this._editorView!; view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node)); } @@ -1230,6 +1230,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps this._disposers.selected = reaction( () => this.props.isSelected(), action(selected => { + selected && this.prepareForTyping(); if (FormattedTextBox._globalHighlights.has('Bold Text')) { this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css change happens outside of mobx/react, so this will notify anyone interested in the layout that it has changed } @@ -1524,24 +1525,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } } selectOnLoad && this._editorView!.focus(); - // 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. + if (this.props.isContentActive()) this.prepareForTyping(); if (this._editorView) { const tr = this._editorView.state.tr; const { from, to } = tr.selection; // for some reason, the selection is sometimes lost in the sidebar view when prosemirror syncs the seledtion with the dom, so reset the selection after the document has ben fully instantiated. if (FormattedTextBox.DontSelectInitialText) setTimeout(() => this._editorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(from), tr.doc.resolve(to)))), 250); - this._editorView.dispatch( - this._editorView.state.tr.setStoredMarks([ - ...(this._editorView.state.storedMarks ?? []), - ...[schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })], - ...(Doc.UserDoc().fontColor !== 'transparent' && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []), - ...(Doc.UserDoc().fontStyle === 'italics' ? [schema.mark(schema.marks.em)] : []), - ...(Doc.UserDoc().textDecoration === 'underline' ? [schema.mark(schema.marks.underline)] : []), - ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontFamily) })] : []), - ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontSize) })] : []), - ...(Doc.UserDoc().fontWeight === 'bold' ? [schema.mark(schema.marks.strong)] : []), - ]) - ); + if (FormattedTextBox.PasteOnLoad) { const pdfAnchorId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfAnchor'); FormattedTextBox.PasteOnLoad = undefined; @@ -1551,6 +1541,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps FormattedTextBox.DontSelectInitialText = false; } + // 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 = () => { + this._editorView?.dispatch( + this._editorView?.state.tr.setStoredMarks([ + ...(this._editorView.state.storedMarks?.filter(mark => ![schema.marks.em, schema.marks.underline, schema.marks.pFontFamily, schema.marks.pFontSize, schema.marks.strong, schema.marks.pFontColor].includes(mark.type)) ?? []), + ...[schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })], + ...(Doc.UserDoc().fontColor !== 'transparent' && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []), + ...(Doc.UserDoc().fontStyle === 'italics' ? [schema.mark(schema.marks.em)] : []), + ...(Doc.UserDoc().textDecoration === 'underline' ? [schema.mark(schema.marks.underline)] : []), + ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontFamily) })] : []), + ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontSize) })] : []), + ...(Doc.UserDoc().fontWeight === 'bold' ? [schema.mark(schema.marks.strong)] : []), + ]) + ); + }; + componentWillUnmount() { Object.values(this._disposers).forEach(disposer => disposer?.()); this.endUndoTypingBatch(); @@ -1829,8 +1835,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps default: if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break; case ' ': - [AclEdit, AclAdmin, AclSelfEdit].includes(GetEffectiveAcl(this.dataDoc)) && - this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }))); + if (e.code !== 'Space') { + [AclEdit, AclAugment, AclAdmin].includes(GetEffectiveAcl(this.rootDoc)) && + this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }))); + } + break; } this.startUndoTypingBatch(); }; diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index 8d57cc081..112a0d87e 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -4,8 +4,7 @@ import { Schema } from 'prosemirror-model'; import { splitListItem, wrapInList } from 'prosemirror-schema-list'; import { EditorState, NodeSelection, TextSelection, Transaction } from 'prosemirror-state'; import { liftTarget } from 'prosemirror-transform'; -import { Doc } from '../../../../fields/Doc'; -import { AclAugment, AclSelfEdit } from '../../../../fields/DocSymbols'; +import { AclAdmin, AclAugment, AclEdit } from '../../../../fields/DocSymbols'; import { GetEffectiveAcl } from '../../../../fields/util'; import { Utils } from '../../../../Utils'; import { Docs } from '../../../documents/Documents'; @@ -13,6 +12,7 @@ import { RTFMarkup } from '../../../util/RTFMarkup'; import { SelectionManager } from '../../../util/SelectionManager'; import { OpenWhere } from '../DocumentView'; import { liftListItem, sinkListItem } from './prosemirrorPatches.js'; +import { Doc } from '../../../../fields/Doc'; const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false; @@ -49,15 +49,11 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey const canEdit = (state: any) => { switch (GetEffectiveAcl(props.DataDoc)) { case AclAugment: - return false; - case AclSelfEdit: - for (var i = state.selection.from; i < state.selection.to; i++) { - const marks = state.doc.resolve(i)?.marks?.(); - if (marks?.some((mark: any) => mark.type === schema.marks.user_mark && mark.attrs.userid !== Doc.CurrentUserEmail)) { - return false; - } + const prevNode = state.selection.$cursor.nodeBefore; + const prevUser = !prevNode ? Doc.CurrentUserEmail : prevNode.marks[prevNode.marks.length - 1].attrs.userid; + if (prevUser != Doc.CurrentUserEmail) { + return false; } - break; } return true; }; @@ -338,7 +334,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey //Command to create a blank space bind('Space', (state: EditorState, dispatch: (tx: Transaction) => void) => { - if (!canEdit(state)) return true; + if (GetEffectiveAcl(props.DataDoc) != AclEdit && GetEffectiveAcl(props.DataDoc) != AclAugment && GetEffectiveAcl(props.DataDoc) != AclAdmin) return true; const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); dispatch(splitMetadata(marks, state.tr)); return false; diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index ef1c3911c..56af67802 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -1025,7 +1025,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { }); return true; }; - childLayoutTemplate = () => (!this.isTreeOrStack ? undefined : DocCast(Doc.UserDoc().presElement)); + childLayoutTemplate = () => (!this.isTreeOrStack ? DocCast(Doc.UserDoc().presElement) : DocCast(Doc.UserDoc().presElement)); removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.rootDoc, this.fieldKey, doc); getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65); // listBox padding-left and pres-box-cont minHeight panelHeight = () => this.props.PanelHeight() - 40; |
