diff options
author | bobzel <zzzman@gmail.com> | 2022-02-10 17:12:19 -0500 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2022-02-10 17:12:19 -0500 |
commit | 956628a22c2d8ae21eb76c70f8f0a5a4edc9ae75 (patch) | |
tree | 535fd5b10420d579d5334d22c2e6374b12feecf2 /src | |
parent | a6d904bcd18a2c9962abfd9b5b325340f6b18b0d (diff) |
switched scripts to use a cache to avoid recompiling. simplified some things with documentView and zooming to avoid invalidations.
Diffstat (limited to 'src')
-rw-r--r-- | src/client/documents/Documents.ts | 4 | ||||
-rw-r--r-- | src/client/util/CurrentUserUtils.ts | 87 | ||||
-rw-r--r-- | src/client/util/Scripting.ts | 1 | ||||
-rw-r--r-- | src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 11 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentLinksButton.scss | 4 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentLinksButton.tsx | 27 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.scss | 5 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 33 | ||||
-rw-r--r-- | src/client/views/nodes/KeyValueBox.tsx | 2 | ||||
-rw-r--r-- | src/client/views/nodes/WebBox.tsx | 72 | ||||
-rw-r--r-- | src/client/views/nodes/button/FontIconBox.tsx | 139 | ||||
-rw-r--r-- | src/fields/ScriptField.ts | 68 | ||||
-rw-r--r-- | src/fields/util.ts | 2 | ||||
-rw-r--r-- | src/server/ApiManagers/UploadManager.ts | 2 | ||||
-rw-r--r-- | src/server/DashUploadUtils.ts | 3 |
15 files changed, 198 insertions, 262 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 0cb4f4c5e..12b57351d 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -235,7 +235,7 @@ export class DocumentOptions { docColorBtn?: string; userColorBtn?: string; canClick?: string; - script?: string; + script?: ScriptField; numBtnType?: string; numBtnMax?: number; numBtnMin?: number; @@ -594,7 +594,7 @@ export namespace Docs { const options: DocumentOptions = { system: true, _layoutKey: "layout", title, type, baseProto: true, x: 0, y: 0, _width: 300, ...(template.options || {}), layout: layout.view?.LayoutString(layout.dataField), data: template.data, layout_keyValue: KeyValueBox.LayoutString("") - } + }; Object.entries(options).map(pair => { if (typeof pair[1] === "string" && pair[1].startsWith("@")) { (options as any)[pair[0]] = ComputedField.MakeFunction(pair[1].substring(1)); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 9ecb2c277..c00fd5bc8 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -51,7 +51,6 @@ interface Button { numBtnMax?: number; switchToggle?: boolean; script?: string; - checkResult?: string; width?: number; list?: string[]; ignoreClick?: boolean; @@ -735,12 +734,16 @@ export class CurrentUserUtils { ]; return docProtoData.map(data => Docs.Create.FontIconDocument({ _nativeWidth: 10, _nativeHeight: 10, _width: 10, _height: 10, title: data.title, icon: data.icon, - _dropAction: data.pointerDown ? "copy" : undefined, ignoreClick: data.ignoreClick, + _dropAction: data.pointerDown ? "copy" : undefined, + ignoreClick: data.ignoreClick, onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, clipboard: data.clipboard, - onPointerUp: data.pointerUp ? ScriptField.MakeScript(data.pointerUp) : undefined, onPointerDown: data.pointerDown ? ScriptField.MakeScript(data.pointerDown) : undefined, + onPointerUp: data.pointerUp ? ScriptField.MakeScript(data.pointerUp) : undefined, + onPointerDown: data.pointerDown ? ScriptField.MakeScript(data.pointerDown) : undefined, backgroundColor: data.backgroundColor, - _removeDropProperties: new List<string>(["dropAction"]), dragFactory: data.dragFactory, system: true + _removeDropProperties: new List<string>(["dropAction"]), + dragFactory: data.dragFactory, + system: true })); } @@ -987,37 +990,37 @@ export class CurrentUserUtils { title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, list: ["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"], - script: 'setFont' + script: 'setFont(value, _readOnly_)' }, - { title: "Font size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions, ignoreClick: true, script: 'setFontSize' }, - { title: "Font color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", ignoreClick: true, script: 'setFontColor' }, - { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", click: 'toggleBold()', checkResult: 'toggleBold(true)' }, - { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", click: 'toggleItalic()', checkResult: 'toggleItalic(true)' }, - { title: "Underline", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", click: 'toggleUnderline()', checkResult: 'toggleUnderline(true)' }, - { title: "Bullet List", toolTip: "Bullet", btnType: ButtonType.ToggleButton, icon: "list", click: 'setBulletList("bullet")', checkResult: 'setBulletList("bullet", true)' }, - { title: "Number List", toolTip: "Number", btnType: ButtonType.ToggleButton, icon: "list-ol", click: 'setBulletList("decimal")', checkResult: 'setBulletList("decimal", true)' }, + { title: "Font size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions, ignoreClick: true, script: 'setFontSize(value, _readOnly_)' }, + { title: "Font color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", ignoreClick: true, script: 'setFontColor(value, _readOnly_)' }, + { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", click: 'toggleBold(_readOnly_)' }, + { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", click: 'toggleItalic(_readOnly_)' }, + { title: "Underline", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", click: 'toggleUnderline(_readOnly_)' }, + { title: "Bullet List", toolTip: "Bullet", btnType: ButtonType.ToggleButton, icon: "list", click: 'setBulletList("bullet", _readOnly_)' }, + { title: "Number List", toolTip: "Number", btnType: ButtonType.ToggleButton, icon: "list-ol", click: 'setBulletList("decimal", _readOnly_)' }, // { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", click: 'toggleStrikethrough()'}, // { title: "Superscript", tooltip: "Superscript", btnType: ButtonType.ToggleButton, icon: "superscript", click: 'toggleSuperscript()'}, // { title: "Subscript", tooltip: "Subscript", btnType: ButtonType.ToggleButton, icon: "subscript", click: 'toggleSubscript()'}, - { title: "Left align", toolTip: "Left align", btnType: ButtonType.ToggleButton, icon: "align-left", click: 'setAlignment("left")', checkResult: 'setAlignment("left", true)' }, - { title: "Center align", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center", click: 'setAlignment("center")', checkResult: 'setAlignment("center", true)' }, - { title: "Right align", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", click: 'setAlignment("right")', checkResult: 'setAlignment("right", true)' }, + { title: "Left align", toolTip: "Left align", btnType: ButtonType.ToggleButton, icon: "align-left", click: 'setAlignment("left", _readOnly_)' }, + { title: "Center align", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center", click: 'setAlignment("center", _readOnly_)' }, + { title: "Right align", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", click: 'setAlignment("right", _readOnly_)' }, ]; return tools; } static inkTools(doc: Doc) { const tools: Button[] = [ - { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen", click: 'setActiveInkTool("pen")', checkResult: 'setActiveInkTool("pen" , true)' }, - { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", click: 'setActiveInkTool("eraser")', checkResult: 'setActiveInkTool("eraser", true)' }, - // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", click: 'setActiveInkTool("highlighter")', checkResult: 'setActiveInkTool("highlighter", true)' }, - { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", click: 'setActiveInkTool("circle")', checkResult: 'setActiveInkTool("circle" , true)' }, - // { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveInkTool("square")', checkResult: 'setActiveInkTool("square" , true)' }, - { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "minus", click: 'setActiveInkTool("line")', checkResult: 'setActiveInkTool("line" , true)' }, - { title: "Fill color", toolTip: "Fill color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", script: "setFillColor" }, - { title: "Stroke width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, numBtnType: NumButtonType.Slider, numBtnMin: 1, ignoreClick: true, script: 'setStrokeWidth' }, - { title: "Stroke color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", ignoreClick: true, script: 'setStrokeColor' }, + { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen", click: 'setActiveInkTool("pen", _readOnly_)' }, + { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", click: 'setActiveInkTool("eraser", _readOnly_)' }, + // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", click: 'setActiveInkTool("highlighter")' }, + { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", click: 'setActiveInkTool("circle", _readOnly_)' }, + // { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveInkTool("square")' }, + { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "minus", click: 'setActiveInkTool("line", _readOnly_)' }, + { title: "Fill color", toolTip: "Fill color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", script: "setFillColor(value, _readOnly_)" }, + { title: "Stroke width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, numBtnType: NumButtonType.Slider, numBtnMin: 1, ignoreClick: true, script: 'setStrokeWidth(value, _readOnly_)' }, + { title: "Stroke color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", ignoreClick: true, script: 'setStrokeColor(value, _readOnly_)' }, ]; return tools; } @@ -1031,8 +1034,7 @@ export class CurrentUserUtils { btnType: ButtonType.ToggleButton, buttonText: "Show Preview", icon: "eye", - click: 'toggleSchemaPreview()', - checkResult: 'toggleSchemaPreview(true)' + click: 'toggleSchemaPreview(_readOnly_)', }, ]; return tools; @@ -1041,10 +1043,10 @@ export class CurrentUserUtils { static webTools(doc: Doc) { const tools: Button[] = [ - { title: "Back", toolTip: "Go back", btnType: ButtonType.ClickButton, icon: "arrow-left", click: 'webBack()' }, - { title: "Forward", toolTip: "Go forward", btnType: ButtonType.ClickButton, icon: "arrow-right", click: 'webForward()' }, + { title: "Back", toolTip: "Go back", btnType: ButtonType.ClickButton, icon: "arrow-left", click: 'webBack(_readOnly_)' }, + { title: "Forward", toolTip: "Go forward", btnType: ButtonType.ClickButton, icon: "arrow-right", click: 'webForward(_readOnly_)' }, //{ title: "Reload", toolTip: "Reload webpage", btnType: ButtonType.ClickButton, icon: "redo-alt", click: 'webReload()' }, - { title: "URL", toolTip: "URL", width: 250, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, script: 'webSetURL' }, + { title: "URL", toolTip: "URL", width: 250, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, script: 'webSetURL(value, _readOnly_)' }, ]; return tools; @@ -1059,17 +1061,17 @@ export class CurrentUserUtils { CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel, CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map, CollectionViewType.Grid], - script: 'setView', + script: 'setView(value, _readOnly_)', }, // Always show { title: "Background Color", toolTip: "Background Color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", - script: "setBackgroundColor", hidden: 'selectedDocumentType()' + script: "setBackgroundColor(value, _readOnly_)", hidden: 'selectedDocumentType()' }, // Only when a document is selected { title: "Header Color", toolTip: "Header Color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "heading", - script: "setHeaderColor", hidden: 'selectedDocumentType()', + script: "setHeaderColor(value, _readOnly_)", hidden: 'selectedDocumentType()', }, // Only when a document is selected - { title: "Overlay", toolTip: "Overlay", btnType: ButtonType.ToggleButton, icon: "layer-group", click: 'toggleOverlay()', checkResult: 'toggleOverlay(true)', hidden: 'selectedDocumentType(undefined, "freeform", true)' }, // Only when floating document is selected in freeform + { title: "Overlay", toolTip: "Overlay", btnType: ButtonType.ToggleButton, icon: "layer-group", click: 'toggleOverlay(_readOnly_)', hidden: 'selectedDocumentType(undefined, "freeform", true)' }, // Only when floating document is selected in freeform // { title: "Alias", btnType: ButtonType.ClickButton, icon: "copy", hidden: 'selectedDocumentType()' }, // Only when a document is selected { title: "Text", type: "textTools", subMenu: true, expanded: 'selectedDocumentType("rtf")' }, // Always available { title: "Ink", type: "inkTools", subMenu: true, expanded: 'selectedDocumentType("ink")' }, // Always available @@ -1083,7 +1085,7 @@ export class CurrentUserUtils { if (doc.contextMenuBtns === undefined) { const docList: Doc[] = []; - (await CurrentUserUtils.contextMenuTools(doc)).map(({ title, width, list, toolTip, ignoreClick, icon, type, btnType, click, script, subMenu, hidden, expanded, checkResult }) => { + (await CurrentUserUtils.contextMenuTools(doc)).map(({ title, width, list, toolTip, ignoreClick, icon, type, btnType, click, script, subMenu, hidden, expanded }) => { const menuDocList: Doc[] = []; if (subMenu) { // default is textTools @@ -1105,7 +1107,8 @@ export class CurrentUserUtils { tools = CurrentUserUtils.textTools(doc); break; } - tools.map(({ title, toolTip, icon, btnType, numBtnType, numBtnMax, numBtnMin, click, script, width, list, ignoreClick, switchToggle, checkResult }) => { + tools.map(({ title, toolTip, icon, btnType, numBtnType, numBtnMax, numBtnMin, click, script, width, list, ignoreClick, switchToggle }) => { + var computed = click ? ComputedField.MakeFunction(click) as any : "transparent"; menuDocList.push(Docs.Create.FontIconDocument({ _nativeWidth: width ? width : 25, _nativeHeight: 25, @@ -1116,7 +1119,7 @@ export class CurrentUserUtils { numBtnType, numBtnMin, numBtnMax, - script, + script: script ? ScriptField.MakeScript(script, { value: "any" }) : undefined, btnType: btnType, btnList: new List<string>(list), ignoreClick: ignoreClick, @@ -1128,10 +1131,10 @@ export class CurrentUserUtils { title, switchToggle, color: Colors.WHITE, - backgroundColor: checkResult ? ComputedField.MakeFunction(checkResult) as any : "transparent", + backgroundColor: computed, _dropAction: "alias", _removeDropProperties: new List<string>(["dropAction", "_stayInCollection"]), - onClick: click ? ScriptField.MakeScript(click, { doc: Doc.name }) : undefined + onClick: click ? ScriptField.MakeScript(click) : undefined })); }); docList.push(CurrentUserUtils.linearButtonList({ @@ -1141,7 +1144,7 @@ export class CurrentUserUtils { linearViewExpandable: true, icon: title, _height: 30, - backgroundColor: checkResult ? ComputedField.MakeFunction(checkResult) as any : "transparent", + // backgroundColor: hidden ? ComputedField.MakeFunction(hidden, { }, { _readOnly_: true }) as any : "transparent", linearViewIsExpanded: expanded ? !(ComputedField.MakeFunction(expanded) as any) : undefined, hidden: hidden ? ComputedField.MakeFunction(hidden) as any : undefined, }, menuDocList)); @@ -1153,7 +1156,7 @@ export class CurrentUserUtils { _height: 25, icon, toolTip, - script, + script: script ? ScriptField.MakeScript(script, { value: "any" }) : undefined, btnType, btnList: new List<string>(list), ignoreClick, @@ -1164,11 +1167,11 @@ export class CurrentUserUtils { dontUndo: true, title, color: Colors.WHITE, - backgroundColor: checkResult ? ComputedField.MakeFunction(checkResult) as any : "transparent", + // backgroundColor: checkResult ? ComputedField.MakeFunction(checkResult, {}, {_readOnly_:true}) as any : "transparent", _dropAction: "alias", hidden: hidden ? ComputedField.MakeFunction(hidden) as any : undefined, _removeDropProperties: new List<string>(["dropAction", "_stayInCollection"]), - onClick: click ? ScriptField.MakeScript(click, { scriptContext: "any" }) : undefined + onClick: click ? ScriptField.MakeScript(click, { scriptContext: "any" }, { _readOnly_: false }) : undefined })); } }); diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index 66cc7766f..40b94024e 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -269,7 +269,6 @@ function forEachNode(node: ts.Node, onEnter: Traverser, onExit?: Traverser, inde } export function CompileScript(script: string, options: ScriptOptions = {}): CompileResult { - console.log("script = " + script); const { requiredType = "", addReturn = false, params = {}, capturedVariables = {}, typecheck = true } = options; if (options.params && !options.params.this) options.params.this = Doc.name; if (options.params && !options.params.self) options.params.self = Doc.name; diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 28a65f628..d8ba88c02 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,4 +1,4 @@ -import { action, computed, observable } from "mobx"; +import { action, computed, observable, trace } from "mobx"; import { observer } from "mobx-react"; import { Doc, Opt } from "../../../fields/Doc"; import { Document } from "../../../fields/documentSchemas"; @@ -154,14 +154,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF returnThis = () => this; render() { TraceMobx(); - - const panx = this.props.CollectionFreeFormView.panX(); - const pany = this.props.CollectionFreeFormView.panY(); - const viewWidth = this.props.CollectionFreeFormView.props.PanelWidth() / this.props.CollectionFreeFormView.zoomScaling() / 2; - const viewHeight = this.props.CollectionFreeFormView.props.PanelHeight() / this.props.CollectionFreeFormView.zoomScaling() / 2; - const hideContent = !this.props.CollectionFreeFormView.props.isAnnotationOverlay && - (Math.min(Math.abs(panx - (this.X + this.panelWidth())), Math.abs(panx - (this.X))) > viewWidth || - Math.min(Math.abs(pany - (this.Y + this.panelHeight())), Math.abs(pany - (this.Y))) > viewHeight) ? true : false; const divProps: DocumentViewProps = { ...this.props, CollectionFreeFormDocumentView: this.returnThis, @@ -170,7 +162,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF ScreenToLocalTransform: this.screenToLocalTransform, PanelWidth: this.panelWidth, PanelHeight: this.panelHeight, - hideContent }; const background = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); const mixBlendMode = StrCast(this.layoutDoc.mixBlendMode) as any || (typeof background === "string" && DashColor(background).alpha() !== 1 ? "multiply" : undefined); diff --git a/src/client/views/nodes/DocumentLinksButton.scss b/src/client/views/nodes/DocumentLinksButton.scss index 228e1bdcb..9ab3171d3 100644 --- a/src/client/views/nodes/DocumentLinksButton.scss +++ b/src/client/views/nodes/DocumentLinksButton.scss @@ -1,6 +1,8 @@ @import "../global/globalCssVariables.scss"; - +.documentLinksButton-wrapper { + transform-origin: top left; +} .documentLinksButton-menu { width: 100%; height: 100%; diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 93cd02d93..eb5571d16 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -20,6 +20,7 @@ import { DocumentView } from "./DocumentView"; import { LinkDescriptionPopup } from "./LinkDescriptionPopup"; import { TaskCompletionBox } from "./TaskCompletedBox"; import React = require("react"); +import { Transform } from "../../util/Transform"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; @@ -31,6 +32,8 @@ interface DocumentLinksButtonProps { AlwaysOn?: boolean; InMenu?: boolean; StartLink?: boolean; //whether the link HAS been started (i.e. now needs to be completed) + ScreenToLocalTransform: () => Transform; + ContentScaling?: () => number; } @observer export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> { @@ -302,16 +305,18 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp const title = this.props.InMenu ? menuTitle : buttonTitle; //render circular tooltip if it isn't set to invisible and show the number of doc links the node has, and render inner-menu link button for starting/stopping links if currently in menu - return !Array.from(this.filteredLinks).length && !this.props.AlwaysOn ? (null) : - this.props.InMenu && (DocumentLinksButton.StartLink || this.props.StartLink) ? - <Tooltip title={<div className="dash-tooltip">{title}</div>}> - {this.linkButtonInner} - </Tooltip> - : - !DocumentLinksButton.LinkEditorDocView && !this.props.InMenu ? - <Tooltip title={<div className="dash-tooltip">{title}</div>}> - {this.linkButtonInner} - </Tooltip> - : this.linkButtonInner; + return (!Array.from(this.filteredLinks).length && !this.props.AlwaysOn) || !this.props.ScreenToLocalTransform ? (null) : + <div className="documentLinksButton-wrapper" style={{ + //transform: `scale(${Math.min(1, this.props.ScreenToLocalTransform().scale(this.props.ContentScaling?.() || 1).Scale)})` + }}> + { + (this.props.InMenu && (DocumentLinksButton.StartLink || this.props.StartLink)) || + (!DocumentLinksButton.LinkEditorDocView && !this.props.InMenu) ? + <Tooltip title={<div className="dash-tooltip">{title}</div>}> + {this.linkButtonInner} + </Tooltip> + : this.linkButtonInner + } + </div>; } } diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 9fcd45e72..4565f8504 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -58,9 +58,10 @@ .documentView-audioBackground { display: inline-block; width: 10%; + height: 25; position: absolute; - top: 0px; - left: 0px; + top: 10px; + left: 10px; border-radius: 25px; background: white; opacity: 0.3; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index a83fba37d..19b16f071 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; +import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from "mobx"; import { observer } from "mobx-react"; import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast } from "../../../fields/Doc"; import { Document } from '../../../fields/documentSchemas'; @@ -148,7 +148,6 @@ export interface DocumentViewSharedProps { export interface DocumentViewProps extends DocumentViewSharedProps { // properties specific to DocumentViews but not to FieldView freezeDimensions?: boolean; - hideContent?: boolean; // whether to show content or not (used to speed up rendering complex scenes when documents are not in view) hideResizeHandles?: boolean; // whether to suppress DocumentDecorations when this document is selected hideTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings hideDecorationTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings @@ -488,6 +487,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps const func = () => this.onClickHandler.script.run({ this: this.layoutDoc, self: this.rootDoc, + _readOnly_: false, scriptContext: this.props.scriptContext, thisContainer: this.props.ContainingCollectionDoc, documentView: this.props.DocumentView(), @@ -840,13 +840,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps } @computed get contents() { TraceMobx(); - const thumb = ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc["thumb"]))?.url.href; + const thumb = ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumb))?.url.href; const audioView = !this.layoutDoc._showAudio ? (null) : - <div className="documentView-audioBackground" - onPointerDown={this.recordAudioAnnotation} - onPointerEnter={this.onPointerEnter} - style={{ height: 25, position: "absolute", top: 10, left: 10 }} - > + <div className="documentView-audioBackground" onPointerDown={this.recordAudioAnnotation} onPointerEnter={this.onPointerEnter} > <FontAwesomeIcon className="documentView-audioFont" style={{ color: [DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]).length ? "blue" : "gray", "green", "red"][this._mediaState] }} icon={!DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]).length ? "microphone" : "file-audio"} size="sm" /> @@ -872,11 +868,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps <img style={{ background: "white", top: 0, position: "absolute" }} src={thumb}// + '?d=' + (new Date()).getTime()} width={this.props.PanelWidth()} height={this.props.PanelHeight()} />} {this.layoutDoc.hideAllLinks ? (null) : this.allLinkEndpoints} - {this.hideLinkButton || this.props.renderDepth === -1 ? (null) : - <div style={{ transformOrigin: "top left", transform: `scale(${Math.min(1, this.props.ScreenToLocalTransform().scale(this.props.ContentScaling?.() || 1).Scale)})` }}> - <DocumentLinksButton View={this.props.DocumentView()} Offset={[this.topMost ? 0 : -15, undefined, undefined, this.topMost ? 10 : -20]} /> - </div>} - + {this.hideLinkButton || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() ? (null) : + <DocumentLinksButton View={this.props.DocumentView()} + ContentScaling={this.props.ContentScaling} + ScreenToLocalTransform={this.props.ScreenToLocalTransform} + Offset={[this.topMost ? 0 : -15, undefined, undefined, this.topMost ? 10 : -20]} /> + } {audioView} </div>; } @@ -1088,7 +1085,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps highlighting = highlighting && this.props.focus !== emptyFunction && this.layoutDoc.title !== "[pres element template]"; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way const borderPath = this.props.styleProvider?.(this.props.Document, this.props, StyleProp.BorderPath) || { path: undefined }; - const internal = this.props.hideContent ? (null) : PresBox.EffectsProvider(this.layoutDoc, this.renderDoc) || this.renderDoc; + const internal = PresBox.EffectsProvider(this.layoutDoc, this.renderDoc) || this.renderDoc; const boxShadow = this.props.treeViewDoc ? null : highlighting && this.borderRounding && highlightStyle !== "dashed" ? `0 0 0 ${highlightIndex}px ${highlightColor}` : this.boxShadow || (this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" : undefined); @@ -1180,10 +1177,10 @@ export class DocumentView extends React.Component<DocumentViewProps> { } return this.props.PanelHeight(); } - @computed get Xshift() { return this.effectiveNativeWidth ? (this.props.PanelWidth() - this.effectiveNativeWidth * this.nativeScaling) / 2 : 0; } - @computed get Yshift() { return this.effectiveNativeWidth && this.effectiveNativeHeight && Math.abs(this.Xshift) < 0.001 ? (this.props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2 : 0; } - @computed get centeringX() { return this.props.dontCenter?.includes("x") ? 0 : this.Xshift; } - @computed get centeringY() { return this.fitWidth || this.props.dontCenter?.includes("y") ? 0 : this.Yshift; } + @computed get Xshift() { return 0; } //this.effectiveNativeWidth ? (this.props.PanelWidth() - this.effectiveNativeWidth * this.nativeScaling) / 2 : 0; } + @computed get Yshift() { return 0; }//this.effectiveNativeWidth && this.effectiveNativeHeight && Math.abs(this.Xshift) < 0.001 ? (this.props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2 : 0; } + @computed get centeringX() { return 0; }//this.props.dontCenter?.includes("x") ? 0 : this.Xshift; } + @computed get centeringY() { return 0; }//this.fitWidth || this.props.dontCenter?.includes("y") ? 0 : this.Yshift; } toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.ContentScale, this.props.PanelWidth(), this.props.PanelHeight()); focus = (doc: Doc, options?: DocFocusOptions) => this.docView?.focus(doc, options); diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 111509fdb..c44c8c828 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -58,7 +58,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> { value = eq ? value.substr(1) : value; const dubEq = value.startsWith(":=") ? "computed" : value.startsWith(";=") ? "script" : false; value = dubEq ? value.substr(2) : value; - const options: ScriptOptions = { addReturn: true, params: { this: "Doc", _last_: "any" }, editable: false }; + const options: ScriptOptions = { addReturn: true, params: { this: Doc.name, self: Doc.name, _last_: "any", _readOnly_: "boolean" }, editable: false }; if (dubEq) options.typecheck = false; const script = CompileScript(value, options); return !script.compiled ? undefined : { script, type: dubEq, onDelegate: eq }; diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index a1910c722..30f905738 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -10,7 +10,7 @@ import { InkTool } from "../../../fields/InkField"; import { List } from "../../../fields/List"; import { listSpec, makeInterface } from "../../../fields/Schema"; import { ComputedField } from "../../../fields/ScriptField"; -import { Cast, NumCast, StrCast, WebCast, ImageCast } from "../../../fields/Types"; +import { Cast, NumCast, StrCast, WebCast, ImageCast, BoolCast } from "../../../fields/Types"; import { WebField, ImageField } from "../../../fields/URLField"; import { TraceMobx } from "../../../fields/util"; import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, Utils } from "../../../Utils"; @@ -106,13 +106,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return true; } async componentDidMount() { - this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. + this.props.setContentView?.(this); // this tells the DocumentView that this WebBox is the "content" of the document. this allows the DocumentView to call WebBox relevant methods to configure the UI (eg, show back/forward buttons) runInAction(() => { this._annotationKeySuffix = () => this._urlHash + "-annotations"; // bcz: need to make sure that doc.data-annotations points to the currently active web page's annotations (this could/should be when the doc is created) - this.dataDoc[this.fieldKey + "-annotations"] = ComputedField.GetAnnoCopyField(this.fieldKey); - this.dataDoc[this.fieldKey + "-sidebar"] = ComputedField.GetSidebarCopyField(this.fieldKey); + this.dataDoc[this.fieldKey + "-annotations"] = ComputedField.MakeFunction(`copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-annotations"`); + this.dataDoc[this.fieldKey + "-sidebar"] = ComputedField.MakeFunction(`copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-sidebar"`); }); reaction(() => this.props.isSelected(), async (selected) => { @@ -388,42 +388,46 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } } - @action - forward = () => { + forward = (checkAvailable?: boolean) => { const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"), []); const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), []); - if (future.length) { - const curUrl = this._url; - this.dataDoc[this.fieldKey + "-history"] = new List<string>([...history, this._url]); - this.dataDoc[this.fieldKey] = new WebField(new URL(future.pop()!)); - if (this._webUrl === this._url) { - this._webUrl = curUrl; - setTimeout(action(() => this._webUrl = this._url)); - } else { - this._webUrl = this._url; + if (checkAvailable) return future.length; + runInAction(() => { + if (future.length) { + const curUrl = this._url; + this.dataDoc[this.fieldKey + "-history"] = new List<string>([...history, this._url]); + this.dataDoc[this.fieldKey] = new WebField(new URL(future.pop()!)); + if (this._webUrl === this._url) { + this._webUrl = curUrl; + setTimeout(action(() => this._webUrl = this._url)); + } else { + this._webUrl = this._url; + } + return true; } - return true; - } + }); return false; } - @action - back = () => { + back = (checkAvailable?: boolean) => { const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string")); const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), []); - if (history.length) { - const curUrl = this._url; - if (future === undefined) this.dataDoc[this.fieldKey + "-future"] = new List<string>([this._url]); - else this.dataDoc[this.fieldKey + "-future"] = new List<string>([...future, this._url]); - this.dataDoc[this.fieldKey] = new WebField(new URL(history.pop()!)); - if (this._webUrl === this._url) { - this._webUrl = curUrl; - setTimeout(action(() => this._webUrl = this._url)); - } else { - this._webUrl = this._url; + if (checkAvailable) return history.length; + runInAction(() => { + if (history.length) { + const curUrl = this._url; + if (future === undefined) this.dataDoc[this.fieldKey + "-future"] = new List<string>([this._url]); + else this.dataDoc[this.fieldKey + "-future"] = new List<string>([...future, this._url]); + this.dataDoc[this.fieldKey] = new WebField(new URL(history.pop()!)); + if (this._webUrl === this._url) { + this._webUrl = curUrl; + setTimeout(action(() => this._webUrl = this._url)); + } else { + this._webUrl = this._url; + } + return true; } - return true; - } + }); return false; } @@ -487,8 +491,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps <button className="submitUrl" onClick={() => this.submitURL(this._keyInput.current!.value)} onDragOver={e => e.stopPropagation()} onDrop={this.onWebUrlDrop}> GO </button> - <button className="submitUrl" onClick={this.back}> <FontAwesomeIcon icon="caret-left" size="lg" /> </button> - <button className="submitUrl" onClick={this.forward}> <FontAwesomeIcon icon="caret-right" size="lg" /> </button> + <button className="submitUrl" onClick={() => this.back}> <FontAwesomeIcon icon="caret-left" size="lg" /> </button> + <button className="submitUrl" onClick={() => this.forward}> <FontAwesomeIcon icon="caret-right" size="lg" /> </button> </div> </div> ); @@ -699,7 +703,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps pointerEvents={this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none"} />; return ( <div className="webBox" ref={this._mainCont} - style={{ pointerEvents: this.pointerEvents() }} > + style={{ pointerEvents: this.pointerEvents(), display: !this.props.isSelected() && !this.isAnyChildContentActive() ? "none" : undefined }} > <div hidden={!this.props.isSelected() && !this.isAnyChildContentActive() ? true : false} className={`webBox-container`} style={{ pointerEvents }} onContextMenu={this.specificContextMenu}> <div className={"webBox-outerContent"} ref={this._outerRef} diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index bd103dcf7..91365121f 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -1,7 +1,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; -import { action, computed, observable } from 'mobx'; +import { action, computed, observable, trace } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { ColorState, SketchPicker } from 'react-color'; @@ -9,7 +9,7 @@ import { Doc, StrListCast, WidthSym, HeightSym } from '../../../../fields/Doc'; import { InkTool } from '../../../../fields/InkField'; import { createSchema, makeInterface } from '../../../../fields/Schema'; import { ScriptField } from '../../../../fields/ScriptField'; -import { BoolCast, Cast, NumCast, StrCast } from '../../../../fields/Types'; +import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from '../../../../fields/Types'; import { WebField } from '../../../../fields/URLField'; import { DocumentType } from '../../../documents/DocumentTypes'; import { Scripting } from "../../../util/Scripting"; @@ -105,16 +105,11 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon */ @computed get numberButton() { const numBtnType: string = StrCast(this.rootDoc.numBtnType); - const setValue = (value: number) => { - // Script for running the toggle - const script: string = StrCast(this.rootDoc.script) + "(" + value + ")"; - ScriptField.MakeScript(script)?.script.run(); - }; + const numScript = ScriptCast(this.rootDoc.script); + const setValue = (value: number) => numScript?.script.run({ value, _readOnly_: false }); // Script for checking the outcome of the toggle - const checkScript: string = StrCast(this.rootDoc.script) + "(0, true)"; - const checkResult: number = ScriptField.MakeScript(checkScript)?.script.run().result || 0; - + const checkResult: number = numScript?.script.run({ value: 0, _readOnly_: true }).result || 0; if (numBtnType === NumButtonType.Slider) { const dropdown = @@ -237,34 +232,37 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - const script: string = StrCast(this.rootDoc.script); + const script = ScriptCast(this.rootDoc.script); let noviceList: string[] = []; let text: string | undefined; let dropdown = true; let icon: IconProp = "caret-down"; - - if (script === 'setView') { - const selected = SelectionManager.Docs().lastElement(); - if (selected) { - if (StrCast(selected.type) === DocumentType.COL) { - text = StrCast(selected._viewType); + try { + if (script.script.originalScript.startsWith('setView')) { + const selected = SelectionManager.Docs().lastElement(); + if (selected) { + if (StrCast(selected.type) === DocumentType.COL) { + text = StrCast(selected._viewType); + } else { + dropdown = false; + text = selected.type === DocumentType.RTF ? "Text" : StrCast(selected.type); + icon = Doc.toIcon(selected); + } } else { dropdown = false; - text = selected.type === DocumentType.RTF ? "Text" : StrCast(selected.type); - icon = Doc.toIcon(selected); + icon = "globe-asia"; + text = "User Default"; } - } else { - dropdown = false; - icon = "globe-asia"; - text = "User Default"; + noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking]; + } else if (script.script.originalScript.startsWith('setFont')) { + const editorView = RichTextMenu.Instance?.TextView?.EditorView; + text = StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily); + noviceList = ["Roboto", "Times New Roman", "Arial", "Georgia", + "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]; } - noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking]; - } else if (script === 'setFont') { - const editorView = RichTextMenu.Instance?.TextView?.EditorView; - text = StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily); - noviceList = ["Roboto", "Times New Roman", "Arial", "Georgia", - "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]; + } catch (e) { + console.log(e); } // Get items to place into the list @@ -272,18 +270,12 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon if (Doc.UserDoc().noviceMode && !noviceList.includes(value)) { return; } - const click = () => { - const s = ScriptField.MakeScript(script + '("' + value + '")'); - if (s) { - s.script.run().result; - } - }; return <div className="list-item" key={`${value}`} style={{ - fontFamily: script === 'setFont' ? value : undefined, + fontFamily: script.script.originalScript.startsWith('setFont') ? value : undefined, backgroundColor: value === text ? Colors.LIGHT_BLUE : undefined }} - onClick={click}> + onClick={() => script.script.run({ value }).result}> {value[0].toUpperCase() + value.slice(1)} </div>; }); @@ -319,15 +311,12 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon } @observable colorPickerClosed: boolean = true; - @computed get colorScript() { - const script = StrCast(this.rootDoc.script); - return ScriptField.MakeScript(script + '(colValue, checkResult)', { colValue: "string", checkResult: "boolean" }); - } + @computed get colorScript() { return ScriptCast(this.rootDoc.script); } colorPicker = (curColor: string) => { const change = (value: ColorState) => { const s = this.colorScript; - s && undoBatch(() => s.script.run({ colValue: Utils.colorString(value), checkResult: false }).result)(); + s && undoBatch(() => s.script.run({ value: Utils.colorString(value), _readOnly_: false }).result)(); }; const presets = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', @@ -343,7 +332,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon @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({ colValue: undefined, checkResult: true }).result ?? "transparent"; + const curColor = this.colorScript?.script.run({ value: undefined, _readOnly_: true }).result ?? "transparent"; const label = !this.label || !Doc.UserDoc()._showLabel ? (null) : <div className="fontIconBox-label" style={{ color, backgroundColor, position: "absolute" }}> @@ -439,52 +428,34 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon @computed get editableText() { // Script for running the toggle - const script: string = StrCast(this.rootDoc.script); - - // Script for checking the outcome of the toggle - const checkScript: string = StrCast(this.rootDoc.script) + "('', true)"; - + const script = ScriptCast(this.rootDoc.script); // Function to run the script - const checkResult = ScriptField.MakeScript(checkScript)?.script.run().result; + const checkResult = script?.script.run({ value: "", _readOnly_: true }).result; - const setValue = (value: string, shiftDown?: boolean): boolean => { - ScriptField.MakeScript(script + "('" + value + "')")?.script.run(); - return true; - }; + 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={() => checkResult} SetValue={setValue} contents={checkResult} /> + <EditableView GetValue={() => script?.script.run({ value: "", _readOnly_: true }).result} SetValue={setValue} 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 = !this.label || !Doc.UserDoc()._showLabel ? (null) : - <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor, position: "absolute" }}> + <div className="fontIconBox-label" style={{ color, backgroundColor, position: "absolute" }}> {this.label} </div>; const menuLabel = !this.label || !Doc.UserDoc()._showMenuLabel ? (null) : - <div className="fontIconBox-label" style={{ color: color, backgroundColor: "transparent" }}> + <div className="fontIconBox-label" style={{ color, backgroundColor: "transparent" }}> {this.label} </div>; - const buttonProps: IButtonProps = { - type: this.type, - rootDoc: this.rootDoc, - label: label, - backgroundColor: backgroundColor, - icon: this.icon, - color: color - }; - const buttonText = StrCast(this.rootDoc.buttonText); // TODO:glr Add label of button type @@ -493,7 +464,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon switch (this.type) { case ButtonType.TextButton: button = ( - <div className={`menuButton ${this.type}`} style={{ color: color, backgroundColor: backgroundColor, opacity: 1, gridAutoColumns: `${NumCast(this.rootDoc._height)} auto` }}> + <div className={`menuButton ${this.type}`} style={{ color, backgroundColor, opacity: 1, gridAutoColumns: `${NumCast(this.rootDoc._height)} auto` }}> <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} /> {buttonText ? <div className="button-text"> @@ -522,7 +493,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon break; case ButtonType.ToolButton: button = ( - <div className={`menuButton ${this.type}`} style={{ opacity: 1, backgroundColor: backgroundColor, color: color }}> + <div className={`menuButton ${this.type}`} style={{ opacity: 1, backgroundColor, color }}> <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} /> {label} </div> @@ -534,7 +505,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon break; case ButtonType.ClickButton: button = ( - <div className={`menuButton ${this.type}`} style={{ color: color, backgroundColor: backgroundColor, opacity: 1 }}> + <div className={`menuButton ${this.type}`} style={{ color, backgroundColor, opacity: 1 }}> <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} /> {label} </div> @@ -544,7 +515,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon const trailsIcon = <img src={`/assets/${"presTrails.png"}`} style={{ width: 30, height: 30, filter: `invert(${color === Colors.DARK_GRAY ? "0%" : "100%"})` }} />; button = ( - <div className={`menuButton ${this.type}`} style={{ color: color, backgroundColor: backgroundColor }}> + <div className={`menuButton ${this.type}`} style={{ color, backgroundColor }}> {this.icon === "pres-trail" ? trailsIcon : <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />} {menuLabel} <FontIconBadge collection={Cast(this.rootDoc.watchedDocuments, Doc, null)} /> @@ -658,7 +629,7 @@ Scripting.addGlobal(function setBulletList(mapStyle: "bullet" | "decimal", check // toggle: Set overlay status of selected document Scripting.addGlobal(function setFontColor(color?: string, checkResult?: boolean) { - const editorView = RichTextMenu.Instance.TextView?.EditorView; + const editorView = RichTextMenu.Instance?.TextView?.EditorView; if (checkResult) { return editorView ? RichTextMenu.Instance.fontColor : Doc.UserDoc().fontColor; @@ -687,12 +658,12 @@ Scripting.addGlobal(function setFontHighlight(color?: string, checkResult?: bool // toggle: Set overlay status of selected document Scripting.addGlobal(function setFontSize(size: string | number, checkResult?: boolean) { - if (typeof size === "number") size = size.toString(); - if (size && Number(size).toString() === size) size += "px"; - const editorView = RichTextMenu.Instance.TextView?.EditorView; + const editorView = RichTextMenu.Instance?.TextView?.EditorView; if (checkResult) { return (editorView ? RichTextMenu.Instance.fontSize : StrCast(Doc.UserDoc().fontSize, "10px")).replace("px", ""); } + if (typeof size === "number") size = size.toString(); + if (size && Number(size).toString() === size) size += "px"; if (editorView) RichTextMenu.Instance.setFontSize(size); else Doc.UserDoc()._fontSize = size; }); @@ -810,17 +781,19 @@ Scripting.addGlobal(function webSetURL(url: string, checkResult?: boolean) { //selected.rootDoc.data = new WebField(url); } }); -Scripting.addGlobal(function webForward() { - const selected = SelectionManager.Views().lastElement(); - if (selected?.rootDoc.type === DocumentType.WEB) { - (selected.ComponentView as WebBox).forward(); +Scripting.addGlobal(function webForward(checkResult?: boolean) { + const selected = (SelectionManager.Views().lastElement()?.ComponentView as WebBox); + if (checkResult) { + return selected?.forward(checkResult) ? undefined : "lightGray"; } + selected?.forward(); }); -Scripting.addGlobal(function webBack() { - const selected = SelectionManager.Views().lastElement(); - if (selected?.rootDoc.type === DocumentType.WEB) { - (selected.ComponentView as WebBox).back(); +Scripting.addGlobal(function webBack(checkResult?: boolean) { + const selected = (SelectionManager.Views().lastElement()?.ComponentView as WebBox); + if (checkResult) { + return selected?.back(checkResult) ? undefined : "lightGray"; } + selected?.back(); }); diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index c84eb7d88..85cd73dfe 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -40,38 +40,9 @@ const scriptSchema = createSimpleSchema({ }); async function deserializeScript(script: ScriptField) { - //console.log("Scripting = " + script.script.originalScript) - if (script.script.originalScript === 'copyDragFactory(this.dragFactory)') { - return (script as any).script = (ScriptField.GetCopyOfDragFactory ?? (ScriptField.GetCopyOfDragFactory = ScriptField.MakeFunction('copyDragFactory(this.dragFactory)')))?.script; - } - if (script.script.originalScript.startsWith("renameAlias")) { // === 'renameAlias(this)') { - return (script as any).script = (ScriptField.RenameAlias as Opt<ScriptField>)?.script; - } - if (script.script.originalScript.startsWith("copyField") && script.script.originalScript.endsWith("annotations\"")) { - return (script as any).script = ScriptField.GetAnnoCopyField(script.script.originalScript.replace(/.*\["([^"]*)/, "$1"))?.script; - } - if (script.script.originalScript.startsWith("copyField") && script.script.originalScript.endsWith("sidebar\"")) { - return (script as any).script = ScriptField.GetSidebarCopyField(script.script.originalScript.replace(/.*\["([^"]*)/, "$1"))?.script; - } - if (script.script.originalScript === 'links(self)') { - return (script as any).script = (ScriptField.LinksSelf as Opt<ScriptField>)?.script; - } - if (script.script.originalScript === 'openOnRight(copyDragFactory(this.dragFactory))') { - return (script as any).script = (ScriptField.OpenOnRight ?? (ScriptField.OpenOnRight = ComputedField.MakeFunction('openOnRight(copyDragFactory(this.dragFactory))')))?.script; - } - if (script.script.originalScript === 'deiconifyView(self)') { - return (script as any).script = (ScriptField.DeiconifyView ?? (ScriptField.DeiconifyView = ComputedField.MakeFunction('deiconifyView(self)')))?.script; - } - if (script.script.originalScript === 'convertToButtons(dragData)') { - return (script as any).script = (ScriptField.ConvertToButtons ?? (ScriptField.ConvertToButtons = ComputedField.MakeFunction('convertToButtons(dragData)', { dragData: "DocumentDragData" })))?.script; - } - if (script.script.originalScript === 'IsNoviceMode()') { - return (script as any).script = (ScriptField.NoviceMode as Opt<ScriptField>)?.script; - } - if (script.script.originalScript === `selectMainMenu(self)`) { - return (script as any).script = (ScriptField.SelectMenu ?? (ScriptField.SelectMenu = ComputedField.MakeFunction('selectMainMenu(self)')))?.script; - } const captures: ProxyField<Doc> = (script as any).captures; + const cache = captures ? undefined : ScriptField.GetScriptFieldCache(script.script.originalScript); + if (cache) return (script as any).script = cache; if (captures) { const doc = (await captures.value())!; const captured: any = {}; @@ -85,6 +56,7 @@ async function deserializeScript(script: ScriptField) { throw new Error("Couldn't compile loaded script"); } (script as any).script = comp; + !captures && ScriptField._scriptFieldCache.set(script.script.originalScript, comp); if (script.setterscript) { const compset = CompileScript(script.setterscript?.originalScript, script.setterscript.options); if (!compset.compiled) { @@ -105,27 +77,9 @@ export class ScriptField extends ObjectField { @serializable(autoObject()) private captures?: ProxyField<Doc>; - public static GetCopyOfDragFactory: Opt<ScriptField>; - public static _annoCopyField: Map<string, Opt<ScriptField>> = new Map(); - public static GetAnnoCopyField(field: string) { - if (!this._annoCopyField.get(field)) this._annoCopyField.set(field, ComputedField.MakeFunction(`copyField(this["${field}-"+urlHash(this["${field}"]?.url?.toString())+"-annotations"`)) - return this._annoCopyField.get(field)?.[Copy]; - } - public static _sidebarCopyField: Map<string, Opt<ScriptField>> = new Map(); - public static GetSidebarCopyField(field: string) { - if (!this._sidebarCopyField.get(field)) this._sidebarCopyField.set(field, ComputedField.MakeFunction(`copyField(this["${field}-"+urlHash(this["${field}"]?.url?.toString())+"-sidebar"`)) - return this._sidebarCopyField.get(field)?.[Copy]; - } - public static _renameAlias: Opt<ScriptField>; - public static get RenameAlias() { return (this._renameAlias ?? (this._renameAlias = ComputedField.MakeFunction('renameAlias(this)'))); } - public static _linksSelf: Opt<ScriptField>; - public static get LinksSelf() { return (this._linksSelf ?? (this._linksSelf = ComputedField.MakeFunction('links(self)'))); } - public static OpenOnRight: Opt<ScriptField>; - public static DeiconifyView: Opt<ScriptField>; - public static ConvertToButtons: Opt<ScriptField>; - public static _noviceMode: Opt<ScriptField>; - public static get NoviceMode() { return (this._noviceMode ?? (this._noviceMode = ComputedField.MakeFunction("IsNoviceMode()"))); } - public static SelectMenu: Opt<ScriptField>; + public static _scriptFieldCache: Map<string, Opt<CompiledScript>> = new Map(); + public static GetScriptFieldCache(field: string) { return this._scriptFieldCache.get(field); } + constructor(script: CompiledScript, setterscript?: CompiledScript) { super(); @@ -174,7 +128,13 @@ export class ScriptField extends ObjectField { } public static CompileScript(script: string, params: object = {}, addReturn = false, capturedVariables?: { [name: string]: Field }) { const compiled = CompileScript(script, { - params: { this: Doc?.name || "Doc", self: Doc?.name || "Doc", _last_: "any", ...params }, + params: { + this: Doc?.name || "Doc", // this is the doc that executes the script + self: Doc?.name || "Doc", // self is the root doc of the doc that executes the script + _last_: "any", // _last_ is the previous value of a computed field when it is being triggered to re-run. + _readOnly_: "boolean", // _readOnly_ is set when a computed field is executed to indicate that it should not have mobx side-effects. used for checking the value of a set function (see FontIconBox) + ...params + }, typecheck: false, editable: true, addReturn: addReturn, @@ -199,7 +159,7 @@ export class ComputedField extends ScriptField { _lastComputedResult: any; //TODO maybe add an observable cache based on what is passed in for doc, considering there shouldn't really be that many possible values for doc value = computedFn((doc: Doc) => this._valueOutsideReaction(doc)); - _valueOutsideReaction = (doc: Doc) => this._lastComputedResult = this.script.run({ this: doc, self: Cast(doc.rootDocument, Doc, null) || doc, _last_: this._lastComputedResult }, console.log).result; + _valueOutsideReaction = (doc: Doc) => this._lastComputedResult = this.script.run({ this: doc, self: Cast(doc.rootDocument, Doc, null) || doc, _last_: this._lastComputedResult, _readOnly_: true }, console.log).result; [Copy](): ObjectField { diff --git a/src/fields/util.ts b/src/fields/util.ts index c708affe3..cef3123d6 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -20,7 +20,7 @@ function _readOnlySetter(): never { throw new Error("Documents can't be modified in read-only mode"); } -const tracing = false; +const tracing = true; export function TraceMobx() { tracing && trace(); } diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index 102e9fd5c..a1fdd9f19 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -255,7 +255,7 @@ export default class UploadManager extends ApiManager { secureHandler: ({ req, res }) => { const uri = req.body.uri; const filename = req.body.name; - const origSuffix = req.body.nosuffix ? "" : SizeSuffix.Original; + const origSuffix = req.body.nosuffix ? SizeSuffix.None : SizeSuffix.Original; if (!uri || !filename) { res.status(401).send("incorrect parameters specified"); return; diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index f13580865..800717d99 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -25,7 +25,8 @@ export enum SizeSuffix { Small = "_s", Medium = "_m", Large = "_l", - Original = "_o" + Original = "_o", + None = "" } export function InjectSize(filename: string, size: SizeSuffix) { |