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, HeightSym, StrListCast, WidthSym } from '../../../../fields/Doc'; 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() { 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.label, StrCast(this.rootDoc.title)); } Icon = (color: string) => { const icon = StrCast(this.dataDoc[this.fieldKey ?? 'icon'] ?? this.dataDoc.icon, 'user') as any; const trailsIcon = () => ; return !icon ? null : icon === 'pres-trail' ? trailsIcon() : ; }; @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 :
{this.label}
; const dropdown = (
e.stopPropagation()}> (this._batch = UndoManager.StartBatch('num slider changing'))} onPointerUp={() => this._batch?.end()} onChange={undoable(e => { e.stopPropagation(); numScript(Number(e.target.value)); }, 'set num value')} />
); return (
e.stopPropagation()} onClick={action(() => { this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen; this.noTooltip = this.rootDoc.dropDownOpen; Doc.UnBrushAllDocs(); })}> {checkResult} {label} {this.rootDoc.dropDownOpen ? dropdown : null}
); } /** * 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 (
numScript(value), `${this.rootDoc.title} button set from list`)}> {value}
); }); return (
numScript(Number(checkResult) - 1), `${this.rootDoc.title} decrement value`)}>
{ e.stopPropagation(); e.preventDefault(); }} onClick={action(() => { this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen; this.noTooltip = this.rootDoc.dropDownOpen; Doc.UnBrushAllDocs(); })}> numScript(Number(e.target.value)), `${this.rootDoc.title} button set value`)} />
numScript(Number(checkResult) + 1), `${this.rootDoc.title} increment value`)}>
{this.rootDoc.dropDownOpen ? (
{list}
{ e.stopPropagation(); this.rootDoc.dropDownOpen = false; this.noTooltip = false; Doc.UnBrushAllDocs(); })} />
) : null}
); } /** * Number button */ @computed get numberInlineButton() { return
; } /** * 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 (
{ this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen; this.noTooltip = this.rootDoc.dropDownOpen; Doc.UnBrushAllDocs(); })}> {this.Icon(color)} {!this.label || !FontIconBox.GetShowLabels() ? null : (
{' '} {this.label}{' '}
)}
{this.rootDoc.dropDownOpen ?
{/* DROPDOWN BOX CONTENTS */}
: null}
); } /** * 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 => (
script.script.run({ self: this.rootDoc, value }), value)}> {value[0].toUpperCase() + value.slice(1)}
)); const label = !this.label || !FontIconBox.GetShowLabels() ? null : (
{this.label}
); return (
{ this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen; this.noTooltip = this.rootDoc.dropDownOpen; Doc.UnBrushAllDocs(); }) : undefined }> {dropdown ? null : }
{text && text[0].toUpperCase() + text.slice(1)}
{label} {!dropdown ? null : (
)} {this.rootDoc.dropDownOpen ? (
{list}
{ e.stopPropagation(); this.rootDoc.dropDownOpen = false; this.noTooltip = false; Doc.UnBrushAllDocs(); })} />
) : null}
); } @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 ; }; /** * 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 : (
{this.label}
); return (
{ this.colorPickerClosed = !this.colorPickerClosed; this.noTooltip = !this.colorPickerClosed; setTimeout(() => Doc.UnBrushAllDocs()); e.stopPropagation(); })} onPointerDown={e => e.stopPropagation()}> {this.Icon(color)}
{label} {/* {dropdownCaret} */} {this.colorPickerClosed ? null : (
e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onClick={e => e.stopPropagation()}> {this.colorPicker(curColor)}
{ e.preventDefault(); e.stopPropagation(); this.colorPickerClosed = true; this.noTooltip = false; Doc.UnBrushAllDocs(); })} />
)}
); } @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 : (
{this.label}
); if (switchToggle) { return (
{buttonText ? buttonText : null}
); } else { return (
{this.Icon(color)} {label}
); } } /** * 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 (
{this.Icon(color)} {!this.label || !FontIconBox.GetShowLabels() ? null : (
{' '} {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 (
script?.script.run({ value: '', _readOnly_: true }).result} SetValue={setValue} oneLine={true} contents={checkResult} />
); } 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 : (
{this.label}
); // 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 = (
{this.Icon(color)} {StrCast(this.rootDoc.buttonText) ?
{StrCast(this.rootDoc.buttonText)}
: null} {label()}
); break; case ButtonType.ClickButton: case ButtonType.ToolButton: button = (
{this.Icon(color)} {label()}
); break; case ButtonType.MenuButton: button = (
{this.Icon(color)} {label(true)}
); break; } return !this.layoutDoc.toolTip || this.noTooltip ? button : {StrCast(this.layoutDoc.toolTip)}
}>{button}; } } // 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[WidthSym](); const height = d[HeightSym](); 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[HeightSym](); newCollection.width = newCollection[WidthSym](); } // 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._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; });