aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/views/OverlayView.scss21
-rw-r--r--src/client/views/OverlayView.tsx71
-rw-r--r--src/client/views/ScriptingRepl.scss11
-rw-r--r--src/client/views/ScriptingRepl.tsx60
-rw-r--r--src/client/views/nodes/DocumentView.tsx3
5 files changed, 158 insertions, 8 deletions
diff --git a/src/client/views/OverlayView.scss b/src/client/views/OverlayView.scss
new file mode 100644
index 000000000..9d0abc96d
--- /dev/null
+++ b/src/client/views/OverlayView.scss
@@ -0,0 +1,21 @@
+.overlayWindow-outerDiv {
+ position: absolute;
+ border-radius: 5px;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+}
+
+.overlayWindow-titleBar {
+ height: 30px;
+ background: darkslategray;
+ color: whitesmoke;
+ text-align: center;
+ cursor: move;
+}
+
+.overlayWindow-closeButton {
+ float: right;
+ height: 30px;
+ width: 30px;
+} \ No newline at end of file
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index f8fc94274..6b72abebf 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -3,6 +3,8 @@ import { observer } from "mobx-react";
import { observable, action } from "mobx";
import { Utils } from "../../Utils";
+import './OverlayView.scss';
+
export type OverlayDisposer = () => void;
export type OverlayElementOptions = {
@@ -10,13 +12,67 @@ export type OverlayElementOptions = {
y: number;
width?: number;
height?: number;
+ title?: string;
};
+export interface OverlayWindowProps {
+ children: JSX.Element;
+ overlayOptions: OverlayElementOptions;
+ onClick: () => void;
+}
+
+@observer
+export class OverlayWindow extends React.Component<OverlayWindowProps> {
+ @observable x: number;
+ @observable y: number;
+ @observable width?: number;
+ @observable height?: number;
+ constructor(props: OverlayWindowProps) {
+ super(props);
+
+ const opts = props.overlayOptions;
+ this.x = opts.x;
+ this.y = opts.y;
+ this.width = opts.width;
+ this.height = opts.height;
+ }
+
+ onPointerDown = (_: React.PointerEvent) => {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointerup", this.onPointerUp);
+ }
+
+ @action
+ onPointerMove = (e: PointerEvent) => {
+ this.x += e.movementX;
+ this.y += e.movementY;
+ }
+
+ onPointerUp = (e: PointerEvent) => {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ }
+
+ render() {
+ return (
+ <div className="overlayWindow-outerDiv" style={{ transform: `translate(${this.x}px, ${this.y}px)`, width: this.width, height: this.height }}>
+ <div className="overlayWindow-titleBar" onPointerDown={this.onPointerDown} >
+ {this.props.overlayOptions.title || "Untitled"}
+ <button onClick={this.props.onClick} className="overlayWindow-closeButton">X</button>
+ </div>
+ {this.props.children}
+ </div>
+ );
+ }
+}
+
@observer
export class OverlayView extends React.Component {
public static Instance: OverlayView;
@observable.shallow
- private _elements: { ele: JSX.Element, id: string, options: OverlayElementOptions }[] = [];
+ private _elements: JSX.Element[] = [];
constructor(props: any) {
super(props);
@@ -27,20 +83,19 @@ export class OverlayView extends React.Component {
@action
addElement(ele: JSX.Element, options: OverlayElementOptions): OverlayDisposer {
- const eleWithPosition = { ele, options, id: Utils.GenerateGuid() };
- this._elements.push(eleWithPosition);
- return action(() => {
- const index = this._elements.indexOf(eleWithPosition);
+ const remove = action(() => {
+ const index = this._elements.indexOf(ele);
if (index !== -1) this._elements.splice(index, 1);
});
+ ele = <OverlayWindow onClick={remove} key={Utils.GenerateGuid()} overlayOptions={options}>{ele}</OverlayWindow>;
+ this._elements.push(ele);
+ return remove;
}
render() {
return (
<div>
- {this._elements.map(({ ele, options: { x, y, width, height }, id }) => (
- <div key={id} style={{ position: "absolute", transform: `translate(${x}px, ${y}px)`, width, height }}>{ele}</div>
- ))}
+ {this._elements}
</div>
);
}
diff --git a/src/client/views/ScriptingRepl.scss b/src/client/views/ScriptingRepl.scss
new file mode 100644
index 000000000..1eedb52fa
--- /dev/null
+++ b/src/client/views/ScriptingRepl.scss
@@ -0,0 +1,11 @@
+.scriptingRepl-outerContainer {
+ background-color: whitesmoke;
+}
+
+.scriptingRepl-resultContainer {
+ padding-bottom: 5px;
+}
+
+.scriptingRepl-commandInput {
+ width: 100%;
+} \ No newline at end of file
diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx
new file mode 100644
index 000000000..bd6fc9dfb
--- /dev/null
+++ b/src/client/views/ScriptingRepl.tsx
@@ -0,0 +1,60 @@
+import * as React from 'react';
+import { observer } from 'mobx-react';
+import { observable, action } from 'mobx';
+import './ScriptingRepl.scss';
+import { Scripting, CompileScript } from '../util/Scripting';
+
+@observer
+export class ScriptingRepl extends React.Component {
+ @observable private commands: { command: string, result: any }[] = [];
+
+ @observable private commandString: string = "";
+
+ private args: any = {};
+
+ @action
+ onKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === "Enter") {
+ e.stopPropagation();
+
+ const script = CompileScript(this.commandString, { typecheck: false, addReturn: true, editable: true, params: { args: "any" } });
+ if (!script.compiled) {
+ return;
+ }
+ const result = script.run({ args: this.args });
+ if (!result.success) {
+ return;
+ }
+ this.commands.push({ command: this.commandString, result: result.result });
+
+ this.commandString = "";
+ }
+ }
+
+ @action
+ onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this.commandString = e.target.value;
+ }
+
+ render() {
+ return (
+ <div className="scriptingRepl-outerContainer">
+ <div className="scriptingRepl-commandsContainer">
+ {this.commands.map(({ command, result }) => {
+ return (
+ <div className="scriptingRepl-resultContainer">
+ <div className="scriptingRepl-commandString">{command}</div>
+ <div className="scriptingRepl-commandResult">{String(result)}</div>
+ </div>
+ );
+ })}
+ </div>
+ <input
+ className="scriptingRepl-commandInput"
+ value={this.commandString}
+ onChange={this.onChange}
+ onKeyDown={this.onKeyDown}></input>
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index fb8319934..52ba643e0 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -35,6 +35,8 @@ import { list, object, createSimpleSchema } from 'serializr';
import { LinkManager } from '../../util/LinkManager';
import { RouteStore } from '../../../server/RouteStore';
import { FormattedTextBox } from './FormattedTextBox';
+import { OverlayView } from '../OverlayView';
+import { ScriptingRepl } from '../ScriptingRepl';
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
library.add(fa.faTrash);
@@ -555,6 +557,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this.props.addDocTab && this.props.addDocTab(Docs.Create.SchemaDocument(["title"], aliases, {}), undefined, "onRight"); // bcz: dataDoc?
}, icon: "search"
});
+ cm.addItem({ description: "Add Repl", event: () => OverlayView.Instance.addElement(<ScriptingRepl />, { x: 100, y: 100 }) });
cm.addItem({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" });
cm.addItem({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" });
cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "link" });