import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import * as React from "react"; import { Doc } from "../../../fields/Doc"; import { documentSchema } from "../../../fields/documentSchemas"; import { List } from "../../../fields/List"; import { createSchema, listSpec, makeInterface } from "../../../fields/Schema"; import { ScriptField } from "../../../fields/ScriptField"; import { Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types"; import { returnEmptyString } from "../../../Utils"; import { DragManager } from "../../util/DragManager"; import { InteractionUtils } from "../../util/InteractionUtils"; import { CompileScript, ScriptParam } from "../../util/Scripting"; import { ContextMenu } from "../ContextMenu"; import { ViewBoxAnnotatableComponent } from "../DocComponent"; import { EditableView } from "../EditableView"; import { FieldView, FieldViewProps } from "../nodes/FieldView"; import { OverlayView } from "../OverlayView"; import { DocumentIconContainer } from "./DocumentIcon"; import "./ScriptingBox.scss"; const ScriptingSchema = createSchema({}); type ScriptingDocument = makeInterface<[typeof ScriptingSchema, typeof documentSchema]>; const ScriptingDocument = makeInterface(ScriptingSchema, documentSchema); @observer export class ScriptingBox extends ViewBoxAnnotatableComponent(ScriptingDocument) { private dropDisposer?: DragManager.DragDropDisposer; protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer | undefined; public static LayoutString(fieldStr: string) { return FieldView.LayoutString(ScriptingBox, fieldStr); } private _overlayDisposer?: () => void; @observable private _errorMessage: string = ""; @observable private _applied: boolean = false; @computed get paramsNames() { return this.compileParams.map(p => p.split(":")[0].trim()); } @computed get paramsTypes() { return this.compileParams.map(p => p.split(":")[1].trim()); } @computed get rawScript() { return StrCast(this.dataDoc[this.props.fieldKey + "-rawScript"], ""); } @computed get compileParams() { return Cast(this.dataDoc[this.props.fieldKey + "-params"], listSpec("string"), []); } set rawScript(value) { this.dataDoc[this.props.fieldKey + "-rawScript"] = value; } set compileParams(value) { this.dataDoc[this.props.fieldKey + "-params"] = new List(value); } @action componentDidMount() { this.rawScript = ScriptCast(this.dataDoc[this.props.fieldKey])?.script?.originalScript ?? this.rawScript; } componentWillUnmount() { this._overlayDisposer?.(); } protected createDashEventsTarget = (ele: HTMLDivElement, dropFunc: (e: Event, de: DragManager.DropEvent) => void) => { //used for stacking and masonry view if (ele) { this.dropDisposer?.(); this.dropDisposer = DragManager.MakeDropTarget(ele, dropFunc, this.layoutDoc); } } @action onFinish = () => { this.rootDoc.layoutKey = "layout"; this.rootDoc._height = 50; this.rootDoc._width = 100; this.dataDoc.documentText = this.rawScript; } @action onError = (error: any) => { this._errorMessage = error?.map((entry: any) => entry.messageText).join(" ") || ""; } @action onCompile = () => { const params: ScriptParam = {}; this.compileParams.forEach(p => params[p.split(":")[0].trim()] = p.split(":")[1].trim()); const result = CompileScript(this.rawScript, { editable: true, transformer: DocumentIconContainer.getTransformer(), params, typecheck: true }); this.dataDoc.documentText = this.rawScript; this.dataDoc.data = result.compiled ? new ScriptField(result) : undefined; this.onError(result.compiled ? undefined : result.errors); } @action onRun = () => { this.onCompile(); const bindings: { [name: string]: any } = {}; this.paramsNames.forEach(key => bindings[key] = this.dataDoc[key]); ScriptCast(this.dataDoc.data, null)?.script.run({ self: this.rootDoc, this: this.layoutDoc, ...bindings }, this.onError); } @action onApply = () => { this.onCompile(); this._applied = true; } @action onEdit = () => { this._applied = false; } onFocus = () => { this._overlayDisposer?.(); this._overlayDisposer = OverlayView.Instance.addElement(, { x: 0, y: 0 }); } @action onDrop = (e: Event, de: DragManager.DropEvent, fieldKey: string) => { this.dataDoc[fieldKey] = de.complete.docDragData?.droppedDocuments[0]; e.stopPropagation(); } @action onDelete = (num: number) => { this.dataDoc[this.paramsNames[num]] = undefined; this.compileParams.splice(num, 1); return true; } @action viewChanged = (e: React.ChangeEvent, name: string) => { //@ts-ignore this.dataDoc[name] = e.target.selectedOptions[0].value; } onCopy = () => { const copy = Doc.MakeCopy(this.rootDoc, true); copy.x = NumCast(this.dataDoc.x) + NumCast(this.dataDoc._width); this.props.addDocument?.(copy); } specificContextMenu = (e: React.MouseEvent): void => { const existingOptions = ContextMenu.Instance.findByDescription("Options..."); const options = existingOptions && "subitems" in existingOptions ? existingOptions.subitems : []; options.push({ description: "Create a Copy", event: this.onCopy, icon: "copy" }); !existingOptions && ContextMenu.Instance.addItem({ description: "Options...", subitems: options, icon: "hand-point-right" }); } renderErrorMessage() { return !this._errorMessage ? (null) :
{this._errorMessage}
; } renderDoc(parameter: string) { return
this._overlayDisposer?.()} ref={ele => ele && this.createDashEventsTarget(ele, (e, de) => this.onDrop(e, de, parameter))} > this.dataDoc[parameter]?.title ?? ""} SetValue={action((value: string) => { const script = CompileScript(value, { addReturn: true, typecheck: false, transformer: DocumentIconContainer.getTransformer() }); const results = script.compiled && script.run(); if (results && results.success) { this._errorMessage = ""; this.dataDoc[parameter] = results.result; return true; } this._errorMessage = "invalid document"; return false; })} />
; } renderString(parameter: string) { return
StrCast(this.dataDoc[parameter]) ?? "undefined"} SetValue={action((value: string) => { if (value && value !== " ") { this._errorMessage = ""; this.dataDoc[parameter] = value; return true; } return false; })} />
; } renderNumber(parameter: string) { return
StrCast(this.dataDoc[parameter]) ?? "undefined"} SetValue={action((value: string) => { if (value && value !== " ") { if (parseInt(value)) { this._errorMessage = ""; this.dataDoc[parameter] = parseInt(value); return true; } this._errorMessage = "not a number"; } return false; })} />
; } renderEnum(parameter: string, types: string[]) { return
; } renderBoolean(parameter: string) { return
; } compileParam(value: string, whichParam?: number) { if (value.includes(":")) { const ptype = value.split(":")[1].trim(); const pname = value.split(":")[0].trim(); if (ptype === "Doc" || ptype === "string" || ptype === "number" || ptype === "boolean" || ptype.split("|")[1]) { if ((whichParam !== undefined && pname === this.paramsNames[whichParam]) || !this.paramsNames.includes(pname)) { this._errorMessage = ""; if (whichParam !== undefined) { this.compileParams[whichParam] = value; } else { this.compileParams = [...value.split(";").filter(s => s), ...this.compileParams]; } return true; } this._errorMessage = "this name has already been used"; } else { this._errorMessage = "this type is not supported"; } } else { this._errorMessage = "must set type of parameter"; } return false; } renderScriptingInputs() { const parameterInput =
value && value !== " " ? this.compileParam(value) : false} />
; const scriptingInputText =