aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/global/globalScripts.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/global/globalScripts.ts')
-rw-r--r--src/client/views/global/globalScripts.ts418
1 files changed, 418 insertions, 0 deletions
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
new file mode 100644
index 000000000..d22b6b4d9
--- /dev/null
+++ b/src/client/views/global/globalScripts.ts
@@ -0,0 +1,418 @@
+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";
+
+// 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;
+});