diff options
Diffstat (limited to 'src/client/views/global')
| -rw-r--r-- | src/client/views/global/globalCssVariables.scss | 4 | ||||
| -rw-r--r-- | src/client/views/global/globalScripts.ts | 420 | 
2 files changed, 422 insertions, 2 deletions
diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss index 422dae15b..7b2ac5713 100644 --- a/src/client/views/global/globalCssVariables.scss +++ b/src/client/views/global/globalCssVariables.scss @@ -36,8 +36,8 @@ $icon-size: 28px;  // fonts  $sans-serif: 'Roboto', sans-serif;  $large-header: 16px; -$body-text: 12px; -$small-text: 9px; +$body-text: 13px; +$small-text: 10px;  // $sans-serif: "Roboto Slab", sans-serif;  // misc values diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts new file mode 100644 index 000000000..b906065a0 --- /dev/null +++ b/src/client/views/global/globalScripts.ts @@ -0,0 +1,420 @@ +import { Colors } from "browndash-components"; +import { runInAction, action } from "mobx"; +import { aggregateBounds } from "../../../Utils"; +import { Doc } from "../../../fields/Doc"; +import { Width, Height } from "../../../fields/DocSymbols"; +import { InkTool } from "../../../fields/InkField"; +import { Cast, StrCast, NumCast, BoolCast } from "../../../fields/Types"; +import { WebField } from "../../../fields/URLField"; +import { GestureUtils } from "../../../pen-gestures/GestureUtils"; +import { LinkManager } from "../../util/LinkManager"; +import { ScriptingGlobals } from "../../util/ScriptingGlobals"; +import { SelectionManager } from "../../util/SelectionManager"; +import { UndoManager } from "../../util/UndoManager"; +import { GestureOverlay } from "../GestureOverlay"; +import { InkTranscription } from "../InkTranscription"; +import { ActiveFillColor, SetActiveFillColor, ActiveIsInkMask, SetActiveIsInkMask, ActiveInkWidth, SetActiveInkWidth, ActiveInkColor, SetActiveInkColor } from "../InkingStroke"; +import { CollectionFreeFormView } from "../collections/collectionFreeForm"; +import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; +import { WebBox } from "../nodes/WebBox"; +import { RichTextMenu } from "../nodes/formattedText/RichTextMenu"; +import { DocumentType } from "../../documents/DocumentTypes"; + +ScriptingGlobals.add(function IsNoneSelected() { return SelectionManager.Views().length <= 0; }, "are no document selected"); + +// 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(); +    console.log(color, checkResult); +    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 { +                console.log('setting color to: ', color) +                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().headingColor; +    } +    Doc.SharingDoc().headingColor = undefined; +    Doc.GetProto(Doc.SharingDoc()).headingColor = 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) { +    console.log(checkResult); +    const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined; +    if (checkResult) { +        if (NumCast(selected?.Document.z) >= 1) return true; +        return false; +    } +    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) => BoolCast(doc._freeform_backgroundGrid, false), +            setDoc: (doc:Doc) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid, +        }], +        ['snaplines', { +            checkResult: (doc:Doc) => BoolCast(doc._freeform_snapLines, false), +            setDoc: (doc:Doc) => doc._freeform_snapLines = !doc._freeform_snapLines, +        }], +        ['viewAll', { +            checkResult: (doc:Doc) => BoolCast(doc._freeform_fitContentsToBox, false), +            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) => BoolCast(doc._freeform_useClusters, false), +            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) => BoolCast(doc._autoArrange, false), +            setDoc: (doc:Doc) => doc._autoArrange = !doc._autoArrange, +        }], +        ['flashcards', { +            checkResult: (doc:Doc) => BoolCast(Doc.UserDoc().defaultToFlashcards, false), +            setDoc: (doc:Doc) => Doc.UserDoc().defaultToFlashcards = !Doc.UserDoc().defaultToFlashcards, +        }], +   ]); + +    if (checkResult) { +        console.log(attr, map.get(attr)?.checkResult(selected)) +        return map.get(attr)?.checkResult(selected); +    } +    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' | 'alignment', value: any, checkResult?: boolean) { +    const editorView = RichTextMenu.Instance?.TextView?.EditorView; +    const selected = SelectionManager.Docs().lastElement(); +    // prettier-ignore +    const map: Map<'font'|'fontColor'|'highlight'|'fontSize'|'alignment', { 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), +        }], +        ['alignment', { +            checkResult: () => RichTextMenu.Instance.textAlign, +            setDoc: () => value && editorView?.state ? RichTextMenu.Instance.align(editorView, editorView.dispatch, value):(Doc.UserDoc().textAlign = 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) { +        console.log(charStyle, checkResult, map.get(charStyle)?.checkResult()); +        return map.get(charStyle)?.checkResult(); +    } +    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) +                ? true +                : true +            : false; +    } +    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())), +            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; +});  | 
