diff options
-rw-r--r-- | src/client/documents/Documents.ts | 4 | ||||
-rw-r--r-- | src/client/util/Scripting.ts | 8 | ||||
-rw-r--r-- | src/client/views/MainView.tsx | 3 | ||||
-rw-r--r-- | src/client/views/nodes/ScriptingBox.scss | 33 | ||||
-rw-r--r-- | src/client/views/nodes/ScriptingBox.tsx | 137 | ||||
-rw-r--r-- | src/server/authentication/models/current_user_utils.ts | 1 |
6 files changed, 123 insertions, 63 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 5231dbb8c..5eca71ca9 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -509,8 +509,8 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.PRES), initial, options); } - export function ScriptingDocument(url: string, options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), new YoutubeField(new URL(url)), options); + export function ScriptingDocument(options: DocumentOptions = {}) { + return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), "", options); } export function VideoDocument(url: string, options: DocumentOptions = {}) { diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index cf04c44ca..f97d91d10 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -89,9 +89,9 @@ const _scriptingGlobals: { [name: string]: any } = {}; let scriptingGlobals: { [name: string]: any } = _scriptingGlobals; function Run(script: string | undefined, customParams: string[], diagnostics: any[], originalScript: string, options: ScriptOptions): CompileResult { - const errors = diagnostics.some(diag => diag.category === ts.DiagnosticCategory.Error); - if ((options.typecheck !== false && errors) || !script) { - return { compiled: false, errors: diagnostics }; + const errors = diagnostics.filter(diag => diag.category === ts.DiagnosticCategory.Error); + if ((options.typecheck !== false && errors.length) || !script) { + return { compiled: false, errors }; } const paramNames = Object.keys(scriptingGlobals); @@ -201,7 +201,7 @@ export interface ScriptOptions { capturedVariables?: { [name: string]: Field }; // list of captured variables typecheck?: boolean; // should the compiler perform typechecking editable?: boolean; // can the script edit Docs - traverser?: TraverserParam; + traverser?: TraverserParam; transformer?: Transformer; // does the editor display a text label by each document that can be used as a captured document reference globals?: { [name: string]: any }; } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 7c9f47fe4..1edfc871f 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -1,5 +1,5 @@ import { library } from '@fortawesome/fontawesome-svg-core'; -import { faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt, faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faThumbtack, faTree, faTv, faUndoAlt, faVideo } from '@fortawesome/free-solid-svg-icons'; +import { faTerminal, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt, faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faThumbtack, faTree, faTv, faUndoAlt, faVideo } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, configure, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; @@ -101,6 +101,7 @@ export class MainView extends React.Component { } } + library.add(faTerminal); library.add(faFileAlt); library.add(faStickyNote); library.add(faFont); diff --git a/src/client/views/nodes/ScriptingBox.scss b/src/client/views/nodes/ScriptingBox.scss index e69de29bb..f31c72bb2 100644 --- a/src/client/views/nodes/ScriptingBox.scss +++ b/src/client/views/nodes/ScriptingBox.scss @@ -0,0 +1,33 @@ +.scriptingBox-outerDiv { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + pointer-events: all; + background-color: rgb(241, 239, 235); + padding: 10px; + .scriptingBox-inputDiv { + display: flex; + flex-direction: column; + height: calc(100% - 30px); + .scriptingBox-errorMessage { + overflow: auto; + } + .scriptingBox-textArea { + width: 100%; + height: 100%; + box-sizing: border-box; + resize: none; + padding: 7px; + } + } + + .scriptingBox-toolbar { + width: 100%; + height: 30px; + .scriptingBox-button { + width: 50% + } + } +} + diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx index a2dd134ed..74d9c2e94 100644 --- a/src/client/views/nodes/ScriptingBox.tsx +++ b/src/client/views/nodes/ScriptingBox.tsx @@ -1,71 +1,96 @@ -import { library } from '@fortawesome/fontawesome-svg-core'; -import { faEdit } from '@fortawesome/free-regular-svg-icons'; -import { computed } from 'mobx'; -import { observer } from 'mobx-react'; -import * as React from 'react'; -import { Doc } from '../../../new_fields/Doc'; -import { documentSchema } from '../../../new_fields/documentSchemas'; +import { action, observable, computed } from "mobx"; +import { observer } from "mobx-react"; +import * as React from "react"; +import { Opt } from "../../../new_fields/Doc"; +import { documentSchema } from "../../../new_fields/documentSchemas"; +import { createSchema, makeInterface } from "../../../new_fields/Schema"; +import { ScriptField } from "../../../new_fields/ScriptField"; +import { StrCast, ScriptCast } from "../../../new_fields/Types"; +import { returnTrue } from "../../../Utils"; +import { InteractionUtils } from "../../util/InteractionUtils"; import { CompileScript } from "../../util/Scripting"; -import { ScriptBox } from '../ScriptBox'; -import { createSchema, listSpec, makeInterface } from '../../../new_fields/Schema'; -import { ScriptField } from '../../../new_fields/ScriptField'; -import { BoolCast, ScriptCast } from '../../../new_fields/Types'; -import { DocComponent } from '../DocComponent'; -import { FieldView, FieldViewProps } from './FieldView'; -import './ScriptingBox.scss'; -import { DocumentIconContainer } from './DocumentIcon'; - - -library.add(faEdit as any); +import { DocAnnotatableComponent } from "../DocComponent"; +import { EditableView } from "../EditableView"; +import { FieldView, FieldViewProps } from "../nodes/FieldView"; +import "./ScriptingBox.scss"; const ScriptingSchema = createSchema({ - onClick: ScriptField, - buttonParams: listSpec("string"), - text: "string" }); type ScriptingDocument = makeInterface<[typeof ScriptingSchema, typeof documentSchema]>; const ScriptingDocument = makeInterface(ScriptingSchema, documentSchema); @observer -export class ScriptingBox extends DocComponent<FieldViewProps, ScriptingDocument>(ScriptingDocument) { - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ScriptingBox, fieldKey); } +export class ScriptingBox extends DocAnnotatableComponent<FieldViewProps, ScriptingDocument>(ScriptingDocument) { + protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer | undefined; + public static LayoutString(fieldStr: string) { return FieldView.LayoutString(ScriptingBox, fieldStr); } - @computed get dataDoc() { - return this.props.DataDoc && - (this.Document.isTemplateForField || BoolCast(this.props.DataDoc.isTemplateForField) || - this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); + @observable private _errorMessage: string = ""; + @computed get rawScript() { return StrCast(this.dataDoc[this.props.fieldKey + "-raw"]); } + set rawScript(value) { this.dataDoc[this.props.fieldKey + "-raw"] = value; } + + @action + componentDidMount() { + this.rawScript = ScriptCast(this.dataDoc[this.props.fieldKey])?.script?.originalScript || this.rawScript; } - specificContextMenu = (e: React.MouseEvent): void => { } + @action + onChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { + this.rawScript = e.target.value; + } - render() { - const script = ScriptCast(this.props.Document[this.props.fieldKey]); - let originalText: string | undefined = undefined; - if (script) { - originalText = script.script.originalScript; + @action + onError = (error: any) => { + this._errorMessage = (error as any).map((e: any) => e.messageText).join(" "); + } + + @action + onCompile = () => { + const result = CompileScript(this.rawScript, {}); + this._errorMessage = ""; + if (result.compiled) { + this._errorMessage = ""; + this.dataDoc[this.props.fieldKey] = new ScriptField(result); } - return !(this.props.Document instanceof Doc) ? (null) : - <ScriptBox initialText={originalText} - setParams={() => { }} - onCancel={() => { }} - onSave={(text, onError) => { - if (!text) { - this.dataDoc[this.props.fieldKey] = undefined; - } else { - const script = CompileScript(text, { - params: { this: Doc.name }, - typecheck: false, - editable: true, - transformer: DocumentIconContainer.getTransformer() - }); - if (!script.compiled) { - onError(script.errors.map(error => error.messageText).join("\n")); - } - else { - this.dataDoc[this.props.fieldKey] = new ScriptField(script); - } - } - }} showDocumentIcons />; + else { + this.dataDoc[this.props.fieldKey] = undefined; + this.onError(result.errors); + } + } + + @action + onRun = () => { + this.onCompile(); + ScriptCast(this.dataDoc[this.props.fieldKey])?.script.run({}, + (err: any) => { + this._errorMessage = ""; + this.onError(err); + }); + } + + render() { + let onFocus: Opt<() => void> = undefined, onBlur: Opt<() => void> = undefined; + const params = <EditableView + contents={""} + display={"block"} + maxHeight={72} + height={35} + fontSize={28} + GetValue={() => ""} + SetValue={returnTrue} + />; + return ( + <div className="scriptingBox-outerDiv" onPointerDown={(e) => this.props.isSelected() && e.stopPropagation()} onWheel={(e) => this.props.isSelected() && e.stopPropagation()}> + <div className="scriptingBox-inputDiv" > + <textarea className="scriptingBox-textarea" placeholder="write your script here" onChange={this.onChange} value={this.rawScript} onFocus={onFocus} onBlur={onBlur} /> + <div className="scriptingBox-errorMessage">{this._errorMessage}</div> + <div style={{ background: "beige" }} >{params}</div> + </div> + <div className="scriptingBox-toolbar"> + <button className="scriptingBox-button" onPointerDown={e => { this.onCompile(); e.stopPropagation(); }}>Compile</button> + <button className="scriptingBox-button" onPointerDown={e => { this.onRun(); e.stopPropagation(); }}>Run</button> + </div> + </div> + ); } -}
\ No newline at end of file +} diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index fa1d49e4e..37b680765 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -80,6 +80,7 @@ export class CurrentUserUtils { { title: "record", icon: "microphone", ignoreClick: true, drag: `Docs.Create.AudioDocument("${nullAudio}", { _width: 200, title: "ready to record audio" })` }, { title: "clickable button", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ _width: 150, _height: 50, title: "Button" })' }, { title: "presentation", icon: "tv", click: 'openOnRight(Doc.UserDoc().curPresentation = getCopy(this.dragFactory, true))', drag: `Doc.UserDoc().curPresentation = getCopy(this.dragFactory,true)`, dragFactory: emptyPresentation }, + { title: "script", icon: "terminal", ignoreClick: true, drag: 'Docs.Create.ScriptingDocument({ _width: 200, _height: 250 title: "untitled script" })' }, { title: "import folder", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", _width: 400, _height: 400 })' }, { title: "mobile view", icon: "phone", ignoreClick: true, drag: 'Doc.UserDoc().activeMobile' }, { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc }, |