aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes')
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx225
-rw-r--r--src/client/views/nodes/DocumentView.scss (renamed from src/client/views/nodes/NodeView.scss)2
-rw-r--r--src/client/views/nodes/DocumentView.tsx329
-rw-r--r--src/client/views/nodes/FieldView.tsx31
-rw-r--r--src/client/views/nodes/FormattedTextBox.scss12
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx59
-rw-r--r--src/client/views/nodes/ImageBox.scss13
-rw-r--r--src/client/views/nodes/ImageBox.tsx27
-rw-r--r--src/client/views/nodes/KeyValueBox.scss31
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx85
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx58
-rw-r--r--src/client/views/nodes/WebBox.scss14
-rw-r--r--src/client/views/nodes/WebBox.tsx38
13 files changed, 561 insertions, 363 deletions
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index f37232c08..50dc5a619 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,24 +1,16 @@
-import { action, computed } from "mobx";
+import { computed, trace } from "mobx";
import { observer } from "mobx-react";
-import { Key, KeyStore } from "../../../fields/Key";
+import { KeyStore } from "../../../fields/KeyStore";
import { NumberField } from "../../../fields/NumberField";
-import { DragManager } from "../../util/DragManager";
-import { SelectionManager } from "../../util/SelectionManager";
-import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { CollectionFreeFormView } from "../collections/CollectionFreeFormView";
-import { ContextMenu } from "../ContextMenu";
-import "./NodeView.scss";
-import React = require("react");
-import { DocumentView, DocumentViewProps } from "./DocumentView";
import { Transform } from "../../util/Transform";
+import { DocumentView, DocumentViewProps } from "./DocumentView";
+import "./DocumentView.scss";
+import React = require("react");
@observer
-export class CollectionFreeFormDocumentView extends DocumentView {
- private _contextMenuCanOpen = false;
- private _downX: number = 0;
- private _downY: number = 0;
- // private _mainCont = React.createRef<HTMLDivElement>();
+export class CollectionFreeFormDocumentView extends React.Component<DocumentViewProps> {
+ private _mainCont = React.createRef<HTMLDivElement>();
constructor(props: DocumentViewProps) {
super(props);
@@ -31,212 +23,63 @@ export class CollectionFreeFormDocumentView extends DocumentView {
}
@computed
- get x(): number {
- return this.props.Document.GetData(KeyStore.X, NumberField, Number(0));
- }
-
- @computed
- get y(): number {
- return this.props.Document.GetData(KeyStore.Y, NumberField, Number(0));
- }
-
- set x(x: number) {
- this.props.Document.SetData(KeyStore.X, x, NumberField)
- }
-
- set y(y: number) {
- this.props.Document.SetData(KeyStore.Y, y, NumberField)
- }
-
- @computed
get transform(): string {
- return `scale(${this.props.Scaling}, ${this.props.Scaling}) translate(${this.x}px, ${this.y}px)`;
- }
-
- @computed
- get width(): number {
- return this.props.Document.GetNumber(KeyStore.Width, 0);
+ return `scale(${this.props.ContentScaling()}, ${this.props.ContentScaling()}) translate(${this.props.Document.GetNumber(KeyStore.X, 0)}px, ${this.props.Document.GetNumber(KeyStore.Y, 0)}px)`;
}
- @computed
- get nativeWidth(): number {
- return this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
- }
+ @computed get zIndex(): number { return this.props.Document.GetNumber(KeyStore.ZIndex, 0); }
+ @computed get width(): number { return this.props.Document.Width(); }
+ @computed get height(): number { return this.props.Document.Height(); }
+ @computed get nativeWidth(): number { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
+ @computed get nativeHeight(): number { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
set width(w: number) {
this.props.Document.SetData(KeyStore.Width, w, NumberField)
- if (this.nativeWidth > 0 && this.nativeHeight > 0) {
+ if (this.nativeWidth && this.nativeHeight) {
this.props.Document.SetNumber(KeyStore.Height, this.nativeHeight / this.nativeWidth * w)
}
}
- @computed
- get height(): number {
- return this.props.Document.GetNumber(KeyStore.Height, 0);
- }
- @computed
- get nativeHeight(): number {
- return this.props.Document.GetNumber(KeyStore.NativeHeight, 0);
- }
-
set height(h: number) {
this.props.Document.SetData(KeyStore.Height, h, NumberField);
- if (this.nativeWidth > 0 && this.nativeHeight > 0) {
+ if (this.nativeWidth && this.nativeHeight) {
this.props.Document.SetNumber(KeyStore.Width, this.nativeWidth / this.nativeHeight * h)
}
}
- @computed
- get zIndex(): number {
- return this.props.Document.GetData(KeyStore.ZIndex, NumberField, Number(0));
- }
-
set zIndex(h: number) {
this.props.Document.SetData(KeyStore.ZIndex, h, NumberField)
}
- @action
- dragComplete = (e: DragManager.DragCompleteEvent) => {
+ contentScaling = () => {
+ return this.nativeWidth > 0 ? this.width / this.nativeWidth : 1;
}
- @computed
- get active(): boolean {
- return SelectionManager.IsSelected(this) || this.props.ContainingCollectionView === undefined ||
- this.props.ContainingCollectionView.active;
+ getTransform = (): Transform => {
+ return this.props.ScreenToLocalTransform().
+ translate(-this.props.Document.GetNumber(KeyStore.X, 0), -this.props.Document.GetNumber(KeyStore.Y, 0)).scale(1 / this.contentScaling());
}
@computed
- get topMost(): boolean {
- return this.props.ContainingCollectionView == undefined || this.props.ContainingCollectionView instanceof CollectionDockingView;
- }
-
- onPointerDown = (e: React.PointerEvent): void => {
- this._downX = e.clientX;
- this._downY = e.clientY;
- var me = this;
- if (e.shiftKey && e.buttons === 1) {
- CollectionDockingView.StartOtherDrag(this._mainCont.current!, this.props.Document);
- e.stopPropagation();
- return;
- }
- this._contextMenuCanOpen = e.button == 2;
- if (this.active && !e.isDefaultPrevented()) {
- e.stopPropagation();
- if (e.buttons === 2) {
- e.preventDefault();
- }
- document.removeEventListener("pointermove", this.onPointerMove)
- document.addEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp)
- document.addEventListener("pointerup", this.onPointerUp);
- }
- }
-
- onPointerMove = (e: PointerEvent): void => {
- if (e.cancelBubble) {
- this._contextMenuCanOpen = false;
- return;
- }
- if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
- this._contextMenuCanOpen = false;
- if (this._mainCont.current != null && !this.topMost) {
- this._contextMenuCanOpen = false;
- const rect = this.screenRect;
- let dragData: { [id: string]: any } = {};
- dragData["document"] = this;
- dragData["xOffset"] = e.x - rect.left;
- dragData["yOffset"] = e.y - rect.top;
- DragManager.StartDrag(this._mainCont.current, dragData, {
- handlers: {
- dragComplete: this.dragComplete,
- },
- hideSource: true
- })
- }
- }
- e.stopPropagation();
- e.preventDefault();
- }
-
- onPointerUp = (e: PointerEvent): void => {
- document.removeEventListener("pointermove", this.onPointerMove)
- document.removeEventListener("pointerup", this.onPointerUp)
- e.stopPropagation();
- if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientY - this._downY) < 4) {
- SelectionManager.SelectDoc(this, e.ctrlKey);
- }
- }
-
- openRight = (e: React.MouseEvent): void => {
- CollectionDockingView.AddRightSplit(this.props.Document);
- }
-
- deleteClicked = (e: React.MouseEvent): void => {
- if (this.props.ContainingCollectionView instanceof CollectionFreeFormView) {
- this.props.ContainingCollectionView.removeDocument(this.props.Document)
- }
- }
- @action
- fullScreenClicked = (e: React.MouseEvent): void => {
- CollectionDockingView.OpenFullScreen(this.props.Document);
- ContextMenu.Instance.clearItems();
- ContextMenu.Instance.addItem({ description: "Close Full Screen", event: this.closeFullScreenClicked });
- ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
- }
- @action
- closeFullScreenClicked = (e: React.MouseEvent): void => {
- CollectionDockingView.CloseFullScreen();
- ContextMenu.Instance.clearItems();
- ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked })
- ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
- }
-
- @action
- onContextMenu = (e: React.MouseEvent): void => {
- if (!SelectionManager.IsSelected(this)) {
- return;
- }
- e.preventDefault()
-
- if (!this._contextMenuCanOpen) {
- return;
- }
-
- if (this.topMost) {
- ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked })
- ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
- }
- else {
- // DocumentViews should stop propogation of this event
- e.stopPropagation();
-
- ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked })
- ContextMenu.Instance.addItem({ description: "Open Right", event: this.openRight })
- ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked })
- ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
- SelectionManager.SelectDoc(this, e.ctrlKey);
- }
- }
-
- getTransform = (): Transform => {
- return this.props.GetTransform().translated(this.x, this.y);
+ get docView() {
+ return <DocumentView {...this.props}
+ ContentScaling={this.contentScaling}
+ ScreenToLocalTransform={this.getTransform}
+ />
}
render() {
- var freestyling = this.props.ContainingCollectionView instanceof CollectionFreeFormView;
return (
- <div className="node" ref={this._mainCont} style={{
+ <div className="collectionFreeFormDocumentView-container" ref={this._mainCont} style={{
transformOrigin: "left top",
- transform: freestyling ? this.transform : "",
- width: freestyling ? this.width : "100%",
- height: freestyling ? this.height : "100%",
- position: freestyling ? "absolute" : "relative",
- zIndex: freestyling ? this.zIndex : 0,
- }}
- onContextMenu={this.onContextMenu}
- onPointerDown={this.onPointerDown}>
-
- <DocumentView {...this.props} GetTransform={this.getTransform} DocumentView={this} />
+ transform: this.transform,
+ width: this.width,
+ height: this.height,
+ position: "absolute",
+ zIndex: this.zIndex,
+ backgroundColor: "transparent"
+ }} >
+ {this.docView}
</div>
);
}
diff --git a/src/client/views/nodes/NodeView.scss b/src/client/views/nodes/DocumentView.scss
index dac1c0a8e..8e2ebd690 100644
--- a/src/client/views/nodes/NodeView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -1,4 +1,4 @@
-.node {
+.documentView-node {
position: absolute;
background: #cdcdcd;
overflow: hidden;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 691ac6311..e01e1d4cd 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,173 +1,250 @@
import { action, computed } from "mobx";
import { observer } from "mobx-react";
import { Document } from "../../../fields/Document";
-import { Opt, FieldWaiting } from "../../../fields/Field";
-import { Key, KeyStore } from "../../../fields/Key";
+import { Field, FieldWaiting, Opt } from "../../../fields/Field";
+import { Key } from "../../../fields/Key";
+import { KeyStore } from "../../../fields/KeyStore";
import { ListField } from "../../../fields/ListField";
-import { NumberField } from "../../../fields/NumberField";
-import { TextField } from "../../../fields/TextField";
-import { Utils } from "../../../Utils";
+import { DragManager } from "../../util/DragManager";
+import { SelectionManager } from "../../util/SelectionManager";
+import { Transform } from "../../util/Transform";
import { CollectionDockingView } from "../collections/CollectionDockingView";
import { CollectionFreeFormView } from "../collections/CollectionFreeFormView";
import { CollectionSchemaView } from "../collections/CollectionSchemaView";
-import { CollectionViewBase, COLLECTION_BORDER_WIDTH } from "../collections/CollectionViewBase";
+import { CollectionView, CollectionViewType } from "../collections/CollectionView";
+import { ContextMenu } from "../ContextMenu";
import { FormattedTextBox } from "../nodes/FormattedTextBox";
import { ImageBox } from "../nodes/ImageBox";
-import "./NodeView.scss";
+import { Documents } from "../../documents/Documents"
+import { KeyValueBox } from "./KeyValueBox"
+import { WebBox } from "../nodes/WebBox";
+import "./DocumentView.scss";
import React = require("react");
-import { Transform } from "../../util/Transform";
-const JsxParser = require('react-jsx-parser').default;//TODO Why does this need to be imported like this?
+const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
-export interface DocumentViewProps {
- DocumentView: Opt<DocumentView> // needed only to set ContainingDocumentView on CollectionViewProps when invoked from JsxParser -- is there a better way?
- ContainingCollectionView: Opt<CollectionViewBase>;
+export interface DocumentViewProps {
+ ContainingCollectionView: Opt<CollectionView>;
Document: Document;
AddDocument?: (doc: Document) => void;
RemoveDocument?: (doc: Document) => boolean;
- GetTransform: () => Transform;
- Scaling: number;
+ ScreenToLocalTransform: () => Transform;
+ isTopMost: boolean;
+ ContentScaling: () => number;
+ PanelWidth: () => number;
+ PanelHeight: () => number;
+ focus: (doc: Document) => void;
+ SelectOnLoad: boolean;
+}
+export interface JsxArgs extends DocumentViewProps {
+ Keys: { [name: string]: Key }
+ Fields: { [name: string]: Field }
}
-@observer
-export class DocumentView extends React.Component<DocumentViewProps> {
-
- protected _mainCont = React.createRef<any>();
- get MainContent() {
- return this._mainCont;
+/*
+This function is pretty much a hack that lets us fill out the fields in JsxArgs with something that
+jsx-to-string can recover the jsx from
+Example usage of this function:
+ public static LayoutString() {
+ let args = FakeJsxArgs(["Data"]);
+ return jsxToString(
+ <CollectionFreeFormView
+ doc={args.Document}
+ fieldKey={args.Keys.Data}
+ DocumentViewForField={args.DocumentView} />,
+ { useFunctionCode: true, functionNameOnly: true }
+ )
}
- @computed
- get layout(): string {
- return this.props.Document.GetData(KeyStore.Layout, TextField, String("<p>Error loading layout data</p>"));
+*/
+export function FakeJsxArgs(keys: string[], fields: string[] = []): JsxArgs {
+ let Keys: { [name: string]: any } = {}
+ let Fields: { [name: string]: any } = {}
+ for (const key of keys) {
+ let fn = () => { }
+ Object.defineProperty(fn, "name", { value: key + "Key" })
+ Keys[key] = fn;
}
-
- @computed
- get backgroundLayout(): string {
- return this.props.Document.GetData(KeyStore.BackgroundLayout, TextField, String("<p>Error loading layout data</p>"));
+ for (const field of fields) {
+ let fn = () => { }
+ Object.defineProperty(fn, "name", { value: field })
+ Fields[field] = fn;
}
+ let args: JsxArgs = {
+ Document: function Document() { },
+ DocumentView: function DocumentView() { },
+ Keys,
+ Fields
+ } as any;
+ return args;
+}
- @computed
- get layoutKeys(): Key[] {
- return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array<Key>());
+@observer
+export class DocumentView extends React.Component<DocumentViewProps> {
+ private _mainCont = React.createRef<HTMLDivElement>();
+ private _documentBindings: any = null;
+ private _downX: number = 0;
+ private _downY: number = 0;
+ @computed get active(): boolean { return SelectionManager.IsSelected(this) || !this.props.ContainingCollectionView || this.props.ContainingCollectionView.active(); }
+ @computed get topMost(): boolean { return !this.props.ContainingCollectionView || this.props.ContainingCollectionView.collectionViewType == CollectionViewType.Docking; }
+ @computed get layout(): string { return this.props.Document.GetText(KeyStore.Layout, "<p>Error loading layout data</p>"); }
+ @computed get layoutKeys(): Key[] { return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array<Key>()); }
+ @computed get layoutFields(): Key[] { return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array<Key>()); }
+ screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect();
+ onPointerDown = (e: React.PointerEvent): void => {
+ this._downX = e.clientX;
+ this._downY = e.clientY;
+ if (e.shiftKey && e.buttons === 1) {
+ CollectionDockingView.Instance.StartOtherDrag(this.props.Document, e);
+ e.stopPropagation();
+ } else {
+ if (this.active && !e.isDefaultPrevented()) {
+ e.stopPropagation();
+ if (e.buttons === 2) {
+ e.preventDefault();
+ }
+ document.removeEventListener("pointermove", this.onPointerMove)
+ document.addEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp)
+ document.addEventListener("pointerup", this.onPointerUp);
+ }
+ }
}
-
- @computed
- get layoutFields(): Key[] {
- return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array<Key>());
+ onPointerMove = (e: PointerEvent): void => {
+ if (e.cancelBubble) {
+ return;
+ }
+ if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
+ document.removeEventListener("pointermove", this.onPointerMove)
+ document.removeEventListener("pointerup", this.onPointerUp)
+ if (this._mainCont.current != null && !this.topMost) {
+ const [left, top] = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+ let dragData: { [id: string]: any } = {};
+ dragData["documentView"] = this;
+ dragData["xOffset"] = e.x - left;
+ dragData["yOffset"] = e.y - top;
+ DragManager.StartDrag(this._mainCont.current, dragData, {
+ handlers: {
+ dragComplete: action(() => { }),
+ },
+ hideSource: true
+ })
+ }
+ }
+ e.stopPropagation();
+ e.preventDefault();
}
-
- //
- // returns the cumulative scaling between the document and the screen
- //
- @computed
- public get ScalingToScreenSpace(): number {
- if (this.props.ContainingCollectionView != undefined &&
- this.props.ContainingCollectionView.props.ContainingDocumentView != undefined) {
- let ss = this.props.ContainingCollectionView.props.DocumentForCollection.GetNumber(KeyStore.Scale, 1);
- return this.props.ContainingCollectionView.props.ContainingDocumentView.ScalingToScreenSpace * ss;
+ onPointerUp = (e: PointerEvent): void => {
+ document.removeEventListener("pointermove", this.onPointerMove)
+ document.removeEventListener("pointerup", this.onPointerUp)
+ e.stopPropagation();
+ if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientY - this._downY) < 4) {
+ SelectionManager.SelectDoc(this, e.ctrlKey);
}
- return 1;
}
- //
- // Converts a coordinate in the screen space of the app into a local document coordinate.
- //
- public TransformToLocalPoint(screenX: number, screenY: number) {
- // if this collection view is nested within another collection view, then
- // first transform the screen point into the parent collection's coordinate space.
- let { LocalX: parentX, LocalY: parentY } = this.props.ContainingCollectionView != undefined &&
- this.props.ContainingCollectionView.props.ContainingDocumentView != undefined ?
- this.props.ContainingCollectionView.props.ContainingDocumentView.TransformToLocalPoint(screenX, screenY) :
- { LocalX: screenX, LocalY: screenY };
- let ContainerX: number = parentX - COLLECTION_BORDER_WIDTH;
- let ContainerY: number = parentY - COLLECTION_BORDER_WIDTH;
-
- var Xx = this.props.Document.GetData(KeyStore.X, NumberField, Number(0));
- var Yy = this.props.Document.GetData(KeyStore.Y, NumberField, Number(0));
- // CollectionDockingViews change the location of their children frames without using a Dash transformation.
- // They also ignore any transformation that may have been applied to their content document.
- // NOTE: this currently assumes CollectionDockingViews aren't nested.
- if (this.props.ContainingCollectionView instanceof CollectionDockingView) {
- var { translateX: rx, translateY: ry } = Utils.GetScreenTransform(this.MainContent.current!);
- Xx = rx - COLLECTION_BORDER_WIDTH;
- Yy = ry - COLLECTION_BORDER_WIDTH;
+ deleteClicked = (): void => {
+ if (this.props.RemoveDocument) {
+ this.props.RemoveDocument(this.props.Document);
}
+ }
- let Ss = this.props.Document.GetNumber(KeyStore.Scale, 1);
- let Panxx = this.props.Document.GetData(KeyStore.PanX, NumberField, Number(0));
- let Panyy = this.props.Document.GetData(KeyStore.PanY, NumberField, Number(0));
- let LocalX = (ContainerX - (Xx + Panxx)) / Ss;
- let LocalY = (ContainerY - (Yy + Panyy)) / Ss;
-
- return { LocalX, Ss, Panxx, Xx, LocalY, Panyy, Yy, ContainerX, ContainerY };
+ fieldsClicked = (e: React.MouseEvent): void => {
+ if (this.props.AddDocument) {
+ this.props.AddDocument(Documents.KVPDocument(this.props.Document));
+ }
+ }
+ fullScreenClicked = (e: React.MouseEvent): void => {
+ CollectionDockingView.Instance.OpenFullScreen(this.props.Document);
+ ContextMenu.Instance.clearItems();
+ ContextMenu.Instance.addItem({ description: "Close Full Screen", event: this.closeFullScreenClicked });
+ ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
}
- //
- // Converts a point in the coordinate space of a document to a screen space coordinate.
- //
- public TransformToScreenPoint(localX: number, localY: number, Ss: number = 1, Panxx: number = 0, Panyy: number = 0): { ScreenX: number, ScreenY: number } {
+ closeFullScreenClicked = (e: React.MouseEvent): void => {
+ CollectionDockingView.Instance.CloseFullScreen();
+ ContextMenu.Instance.clearItems();
+ ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked })
+ ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
+ }
- var Xx = this.props.Document.GetData(KeyStore.X, NumberField, Number(0));
- var Yy = this.props.Document.GetData(KeyStore.Y, NumberField, Number(0));
- // CollectionDockingViews change the location of their children frames without using a Dash transformation.
- // They also ignore any transformation that may have been applied to their content document.
- // NOTE: this currently assumes CollectionDockingViews aren't nested.
- if (this.props.ContainingCollectionView instanceof CollectionDockingView) {
- var { translateX: rx, translateY: ry } = Utils.GetScreenTransform(this.MainContent.current!);
- Xx = rx - COLLECTION_BORDER_WIDTH;
- Yy = ry - COLLECTION_BORDER_WIDTH;
+ @action
+ onContextMenu = (e: React.MouseEvent): void => {
+ e.stopPropagation();
+ let moved = Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3;
+ if (moved || e.isDefaultPrevented()) {
+ e.preventDefault()
+ return;
+ }
+ e.preventDefault()
+
+ ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked })
+ ContextMenu.Instance.addItem({ description: "Fields", event: this.fieldsClicked })
+ ContextMenu.Instance.addItem({ description: "Center", event: () => this.props.focus(this.props.Document) })
+ ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document) })
+ //ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) })
+ ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
+ if (!this.topMost) {
+ // DocumentViews should stop propagation of this event
+ e.stopPropagation();
}
- let W = COLLECTION_BORDER_WIDTH;
- let H = COLLECTION_BORDER_WIDTH;
- let parentX = (localX - W) * Ss + (Xx + Panxx) + W;
- let parentY = (localY - H) * Ss + (Yy + Panyy) + H;
+ ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked })
+ ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
+ SelectionManager.SelectDoc(this, e.ctrlKey);
+ }
+ @computed get mainContent() {
+ return <JsxParser
+ components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox }}
+ bindings={this._documentBindings}
+ jsx={this.layout}
+ showWarnings={true}
+ onError={(test: any) => { console.log(test) }}
+ />
+ }
- // if this collection view is nested within another collection view, then
- // first transform the local point into the parent collection's coordinate space.
- let containingDocView = this.props.ContainingCollectionView != undefined ? this.props.ContainingCollectionView.props.ContainingDocumentView : undefined;
- if (containingDocView != undefined) {
- let ss = containingDocView.props.Document.GetNumber(KeyStore.Scale, 1) * this.props.Scaling;
- let panxx = containingDocView.props.Document.GetData(KeyStore.PanX, NumberField, Number(0)) + COLLECTION_BORDER_WIDTH * ss;
- let panyy = containingDocView.props.Document.GetData(KeyStore.PanY, NumberField, Number(0)) + COLLECTION_BORDER_WIDTH * ss;
- let { ScreenX, ScreenY } = containingDocView.TransformToScreenPoint(parentX, parentY, ss, panxx, panyy);
- parentX = ScreenX;
- parentY = ScreenY;
- }
- return { ScreenX: parentX, ScreenY: parentY };
+ isSelected = () => {
+ return SelectionManager.IsSelected(this);
}
+ select = (ctrlPressed: boolean) => {
+ SelectionManager.SelectDoc(this, ctrlPressed)
+ }
render() {
- let bindings = { ...this.props } as any;
+ if (!this.props.Document) return <div></div>
+ let lkeys = this.props.Document.GetT(KeyStore.LayoutKeys, ListField);
+ if (!lkeys || lkeys === "<Waiting>") {
+ return <p>Error loading layout keys</p>;
+ }
+ this._documentBindings = {
+ ...this.props,
+ isSelected: this.isSelected,
+ select: this.select,
+ focus: this.props.focus
+ };
for (const key of this.layoutKeys) {
- bindings[key.Name + "Key"] = key; // this maps string values of the form <keyname>Key to an actual key Kestore.keyname e.g, "DataKey" => KeyStore.Data
+ this._documentBindings[key.Name + "Key"] = key; // this maps string values of the form <keyname>Key to an actual key Kestore.keyname e.g, "DataKey" => KeyStore.Data
}
for (const key of this.layoutFields) {
let field = this.props.Document.Get(key);
- bindings[key.Name] = field && field != FieldWaiting ? field.GetValue() : field;
+ this._documentBindings[key.Name] = field && field != FieldWaiting ? field.GetValue() : field;
}
- if (bindings.DocumentView === undefined) {
- bindings.DocumentView = this; // set the DocumentView to this if it hasn't already been set by a sub-class during its render method.
- }
- var annotated = <JsxParser
- components={{ FormattedTextBox: FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView }}
- bindings={bindings}
- jsx={this.backgroundLayout}
- showWarnings={true}
- onError={(test: any) => { console.log(test) }}
- />;
- bindings["BackgroundView"] = this.backgroundLayout ? annotated : null;
+ this._documentBindings.bindings = this._documentBindings;
+ var scaling = this.props.ContentScaling();
+ var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
+ var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0);
return (
- <div className="node" ref={this._mainCont} style={{ width: "100%", height: "100%", }}>
- <JsxParser
- components={{ FormattedTextBox: FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView }}
- bindings={bindings}
- jsx={this.layout}
- showWarnings={true}
- onError={(test: any) => { console.log(test) }}
- />
+ <div className="documentView-node" ref={this._mainCont}
+ style={{
+ width: nativeWidth > 0 ? nativeWidth.toString() + "px" : "100%",
+ height: nativeHeight > 0 ? nativeHeight.toString() + "px" : "100%",
+ transformOrigin: "left top",
+ transform: `scale(${scaling} , ${scaling})`
+ }}
+ onContextMenu={this.onContextMenu}
+ onPointerDown={this.onPointerDown} >
+ {this.mainContent}
</div>
)
}
-}
+} \ No newline at end of file
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 12371eb2e..9e63006d1 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,16 +1,17 @@
import React = require("react")
import { observer } from "mobx-react";
import { computed } from "mobx";
-import { Field, Opt, FieldWaiting, FieldValue } from "../../../fields/Field";
+import { Field, FieldWaiting, FieldValue } from "../../../fields/Field";
import { Document } from "../../../fields/Document";
import { TextField } from "../../../fields/TextField";
import { NumberField } from "../../../fields/NumberField";
import { RichTextField } from "../../../fields/RichTextField";
import { ImageField } from "../../../fields/ImageField";
+import { WebField } from "../../../fields/WebField";
import { Key } from "../../../fields/Key";
import { FormattedTextBox } from "./FormattedTextBox";
import { ImageBox } from "./ImageBox";
-import { DocumentView } from "./DocumentView";
+import { WebBox } from "./WebBox";
//
// these properties get assigned through the render() method of the DocumentView when it creates this node.
@@ -20,12 +21,19 @@ import { DocumentView } from "./DocumentView";
export interface FieldViewProps {
fieldKey: Key;
doc: Document;
- DocumentViewForField: Opt<DocumentView>
+ isSelected: () => boolean;
+ select: () => void;
+ isTopMost: boolean;
+ selectOnLoad: boolean;
+ bindings: any;
}
@observer
export class FieldView extends React.Component<FieldViewProps> {
- public static LayoutString(fieldType: string) { return `<${fieldType} doc={Document} DocumentViewForField={DocumentView} fieldKey={DataKey} />`; }
+ public static LayoutString(fieldType: { name: string }, fieldStr: string = "DataKey") {
+ return `<${fieldType.name} doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={${fieldStr}} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost} />`;
+ }
+
@computed
get field(): FieldValue<Field> {
const { doc, fieldKey } = this.props;
@@ -45,11 +53,20 @@ export class FieldView extends React.Component<FieldViewProps> {
else if (field instanceof ImageField) {
return <ImageBox {...this.props} />
}
+ else if (field instanceof WebField) {
+ return <WebBox {...this.props} />
+ }
+ // bcz: this belongs here, but it doesn't render well so taking it out for now
+ // else if (field instanceof HtmlField) {
+ // return <WebBox {...this.props} />
+ // }
else if (field instanceof NumberField) {
return <p>{field.Data}</p>
- } else if (field != FieldWaiting) {
- return <p>{field.GetValue}</p>
- } else
+ }
+ else if (field != FieldWaiting) {
+ return <p>{JSON.stringify(field.GetValue())}</p>
+ }
+ else
return <p> {"Waiting for server..."} </p>
}
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index 492367fce..21bd43b6e 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -1,7 +1,7 @@
.ProseMirror {
- margin-top: -1em;
width: 100%;
- height: 100%;
+ height: auto;
+ min-height: 100%
}
.ProseMirror:focus {
@@ -10,5 +10,11 @@
.formattedTextBox-cont {
background: white;
- padding: 1vw;
+ padding: 1;
+ border: black;
+ border-width: 10;
+ overflow-y: scroll;
+ overflow-x: hidden;
+ color: initial;
+ height: 100%;
} \ No newline at end of file
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 0fe6befda..04eb2052d 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -3,19 +3,18 @@ import { baseKeymap } from "prosemirror-commands";
import { history, redo, undo } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
import { schema } from "prosemirror-schema-basic";
-import { EditorState, Transaction } from "prosemirror-state";
+import { EditorState, Transaction, } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
-import { Opt, FieldWaiting, FieldValue } from "../../../fields/Field";
-import { SelectionManager } from "../../util/SelectionManager";
+import { Opt, FieldWaiting } from "../../../fields/Field";
import "./FormattedTextBox.scss";
import React = require("react")
import { RichTextField } from "../../../fields/RichTextField";
import { FieldViewProps, FieldView } from "./FieldView";
-import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView";
-import { observer } from "mobx-react";
import { ContextMenu } from "../../views/ContextMenu";
+
+
// FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document
//
// HTML Markup: <FormattedTextBox Doc={Document's ID} FieldKey={Key's name + "Key"}
@@ -34,7 +33,7 @@ import { ContextMenu } from "../../views/ContextMenu";
//]
export class FormattedTextBox extends React.Component<FieldViewProps> {
- public static LayoutString() { return FieldView.LayoutString("FormattedTextBox"); }
+ public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(FormattedTextBox, fieldStr) }
private _ref: React.RefObject<HTMLDivElement>;
private _editorView: Opt<EditorView>;
private _reactionDisposer: Opt<IReactionDisposer>;
@@ -43,7 +42,6 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
super(props);
this._ref = React.createRef();
-
this.onChange = this.onChange.bind(this);
}
@@ -51,25 +49,23 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
if (this._editorView) {
const state = this._editorView.state.apply(tx);
this._editorView.updateState(state);
- const { doc, fieldKey } = this.props;
- doc.SetData(fieldKey, JSON.stringify(state.toJSON()), RichTextField);
+ this.props.doc.SetData(this.props.fieldKey, JSON.stringify(state.toJSON()), RichTextField);
}
}
componentDidMount() {
let state: EditorState;
- const { doc, fieldKey } = this.props;
const config = {
schema,
plugins: [
history(),
keymap({ "Mod-z": undo, "Mod-y": redo }),
- keymap(baseKeymap)
+ keymap(baseKeymap),
]
};
- let field = doc.GetT(fieldKey, RichTextField);
- if (field && field != FieldWaiting) { // bcz: don't think this works
+ let field = this.props.doc.GetT(this.props.fieldKey, RichTextField);
+ if (field && field != FieldWaiting) {
state = EditorState.fromJSON(config, JSON.parse(field.Data));
} else {
state = EditorState.create(config);
@@ -89,6 +85,10 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field)));
}
})
+ if (this.props.selectOnLoad) {
+ this.props.select();
+ this._editorView!.focus();
+ }
}
componentWillUnmount() {
@@ -106,31 +106,44 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
@action
onChange(e: React.ChangeEvent<HTMLInputElement>) {
- const { fieldKey, doc } = this.props;
- doc.SetData(fieldKey, e.target.value, RichTextField);
+ this.props.doc.SetData(this.props.fieldKey, e.target.value, RichTextField);
}
onPointerDown = (e: React.PointerEvent): void => {
- let me = this;
- if (e.buttons === 1 && me.props.DocumentViewForField instanceof CollectionFreeFormDocumentView && SelectionManager.IsSelected(me.props.DocumentViewForField)) {
+ if (e.buttons === 1 && this.props.isSelected()) {
e.stopPropagation();
}
}
- //REPLACE THIS WITH CAPABILITIES SPECIFC TO THIS TYPE OF NODE
+ //REPLACE THIS WITH CAPABILITIES SPECIFIC TO THIS TYPE OF NODE
textCapability = (e: React.MouseEvent): void => {
}
specificContextMenu = (e: React.MouseEvent): void => {
ContextMenu.Instance.addItem({ description: "Text Capability", event: this.textCapability });
+ // ContextMenu.Instance.addItem({
+ // description: "Submenu",
+ // items: [
+ // {
+ // description: "item 1", event:
+ // },
+ // {
+ // description: "item 2", event:
+ // }
+ // ]
+ // })
+ // e.stopPropagation()
+
+ }
+
+ onPointerWheel = (e: React.WheelEvent): void => {
+ e.stopPropagation();
}
render() {
return (<div className="formattedTextBox-cont"
- style={{
- color: "initial",
- whiteSpace: "initial"
- }}
onPointerDown={this.onPointerDown}
- ref={this._ref} onContextMenu={this.specificContextMenu} />)
+ onContextMenu={this.specificContextMenu}
+ onWheel={this.onPointerWheel}
+ ref={this._ref} />)
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 2bd8b1d3c..ea459b911 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -1,8 +1,17 @@
.imageBox-cont {
padding: 0vw;
- position: absolute;
- width: 100%
+ position: relative;
+ text-align: center;
+ width: 100%;
+ height: auto;
+ max-width: 100%;
+ max-height: 100%
+}
+
+.imageBox-cont img {
+ object-fit: contain;
+ height: 100%;
}
.imageBox-button {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index f363487c3..8c44395f4 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,23 +1,22 @@
import Lightbox from 'react-image-lightbox';
import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
-import { SelectionManager } from "../../util/SelectionManager";
import "./ImageBox.scss";
import React = require("react")
import { ImageField } from '../../../fields/ImageField';
import { FieldViewProps, FieldView } from './FieldView';
-import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView';
import { FieldWaiting } from '../../../fields/Field';
import { observer } from "mobx-react"
-import { observable, action, spy } from 'mobx';
-import { KeyStore } from '../../../fields/Key';
import { ContextMenu } from "../../views/ContextMenu";
+import { observable, action } from 'mobx';
+import { KeyStore } from '../../../fields/KeyStore';
@observer
export class ImageBox extends React.Component<FieldViewProps> {
- public static LayoutString() { return FieldView.LayoutString("ImageBox"); }
+ public static LayoutString() { return FieldView.LayoutString(ImageBox) }
private _ref: React.RefObject<HTMLDivElement>;
+ private _imgRef: React.RefObject<HTMLImageElement>;
private _downX: number = 0;
private _downY: number = 0;
private _lastTap: number = 0;
@@ -28,12 +27,20 @@ export class ImageBox extends React.Component<FieldViewProps> {
super(props);
this._ref = React.createRef();
+ this._imgRef = React.createRef();
this.state = {
photoIndex: 0,
isOpen: false,
};
}
+ @action
+ onLoad = (target: any) => {
+ var h = this._imgRef.current!.naturalHeight;
+ var w = this._imgRef.current!.naturalWidth;
+ this.props.doc.SetNumber(KeyStore.NativeHeight, this.props.doc.GetNumber(KeyStore.NativeWidth, 0) * h / w)
+ }
+
componentDidMount() {
}
@@ -42,7 +49,7 @@ export class ImageBox extends React.Component<FieldViewProps> {
onPointerDown = (e: React.PointerEvent): void => {
if (Date.now() - this._lastTap < 300) {
- if (e.buttons === 1 && this.props.DocumentViewForField instanceof CollectionFreeFormDocumentView && SelectionManager.IsSelected(this.props.DocumentViewForField)) {
+ if (e.buttons === 1 && this.props.isSelected()) {
e.stopPropagation();
this._downX = e.clientX;
this._downY = e.clientY;
@@ -64,7 +71,7 @@ export class ImageBox extends React.Component<FieldViewProps> {
lightbox = (path: string) => {
const images = [path, "http://www.cs.brown.edu/~bcz/face.gif"];
- if (this._isOpen && this.props.DocumentViewForField instanceof CollectionFreeFormDocumentView && SelectionManager.IsSelected(this.props.DocumentViewForField)) {
+ if (this._isOpen && this.props.isSelected()) {
return (<Lightbox
mainSrc={images[this._photoIndex]}
nextSrc={images[(this._photoIndex + 1) % images.length]}
@@ -82,7 +89,7 @@ export class ImageBox extends React.Component<FieldViewProps> {
}
}
- //REPLACE THIS WITH CAPABILITIES SPECIFC TO THIS TYPE OF NODE
+ //REPLACE THIS WITH CAPABILITIES SPECIFIC TO THIS TYPE OF NODE
imageCapability = (e: React.MouseEvent): void => {
}
@@ -94,10 +101,10 @@ export class ImageBox extends React.Component<FieldViewProps> {
let field = this.props.doc.Get(this.props.fieldKey);
let path = field == FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
field instanceof ImageField ? field.Data.href : "http://www.cs.brown.edu/~bcz/face.gif";
-
+ let nativeWidth = this.props.doc.GetNumber(KeyStore.NativeWidth, 1);
return (
<div className="imageBox-cont" onPointerDown={this.onPointerDown} ref={this._ref} onContextMenu={this.specificContextMenu}>
- <img src={path} width="100%" alt="Image not found" />
+ <img src={path} width={nativeWidth} alt="Image not found" ref={this._imgRef} onLoad={this.onLoad} />
{this.lightbox(path)}
</div>)
}
diff --git a/src/client/views/nodes/KeyValueBox.scss b/src/client/views/nodes/KeyValueBox.scss
new file mode 100644
index 000000000..1295266e5
--- /dev/null
+++ b/src/client/views/nodes/KeyValueBox.scss
@@ -0,0 +1,31 @@
+.keyValueBox-cont {
+ overflow-y:scroll;
+ height: 100%;
+ border: black;
+ border-width: 1px;
+ border-style: solid;
+ box-sizing: border-box;
+ display: inline-block;
+ .imageBox-cont img {
+ max-height:45px;
+ height: auto;
+ }
+}
+.keyValueBox-table {
+ position: relative;
+}
+.keyValueBox-header {
+ background:gray;
+}
+.keyValueBox-evenRow {
+ background: white;
+ .formattedTextBox-cont {
+ background: white;
+ }
+}
+.keyValueBox-oddRow {
+ background: lightGray;
+ .formattedTextBox-cont {
+ background: lightgray;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
new file mode 100644
index 000000000..e8ebd50be
--- /dev/null
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -0,0 +1,85 @@
+
+import { IReactionDisposer } from 'mobx';
+import { observer } from "mobx-react";
+import { EditorView } from 'prosemirror-view';
+import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
+import { Document } from '../../../fields/Document';
+import { Opt, FieldWaiting } from '../../../fields/Field';
+import { KeyStore } from '../../../fields/KeyStore';
+import { FieldView, FieldViewProps } from './FieldView';
+import { KeyValuePair } from "./KeyValuePair";
+import "./KeyValueBox.scss";
+import React = require("react")
+
+@observer
+export class KeyValueBox extends React.Component<FieldViewProps> {
+
+ public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(KeyValueBox, fieldStr) }
+ private _ref: React.RefObject<HTMLDivElement>;
+ private _editorView: Opt<EditorView>;
+ private _reactionDisposer: Opt<IReactionDisposer>;
+
+
+ constructor(props: FieldViewProps) {
+ super(props);
+
+ this._ref = React.createRef();
+ }
+
+
+
+ shouldComponentUpdate() {
+ return false;
+ }
+
+
+ onPointerDown = (e: React.PointerEvent): void => {
+ if (e.buttons === 1 && this.props.isSelected()) {
+ e.stopPropagation();
+ }
+ }
+ onPointerWheel = (e: React.WheelEvent): void => {
+ e.stopPropagation();
+ }
+
+ createTable = () => {
+ let doc = this.props.doc.GetT(KeyStore.Data, Document);
+ if (!doc || doc == FieldWaiting) {
+ return <tr><td>Loading...</td></tr>
+ }
+ let realDoc = doc;
+
+ let ids: { [key: string]: string } = {};
+ let protos = doc.GetAllPrototypes();
+ for (const proto of protos) {
+ proto._proxies.forEach((val, key) => {
+ if (!(key in ids)) {
+ ids[key] = key;
+ }
+ })
+ }
+
+ let rows: JSX.Element[] = [];
+ let i = 0;
+ for (let key in ids) {
+ rows.push(<KeyValuePair doc={realDoc} rowStyle={"keyValueBox-" + (i++ % 2 ? "oddRow" : "evenRow")} fieldId={key} key={key} />)
+ }
+ return rows;
+ }
+
+
+ render() {
+
+ return (<div className="keyValueBox-cont" onWheel={this.onPointerWheel}>
+ <table className="keyValueBox-table">
+ <tbody>
+ <tr className="keyValueBox-header">
+ <th>Key</th>
+ <th>Fields</th>
+ </tr>
+ {this.createTable()}
+ </tbody>
+ </table>
+ </div>)
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
new file mode 100644
index 000000000..a97e98313
--- /dev/null
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -0,0 +1,58 @@
+import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
+import "./KeyValueBox.scss";
+import React = require("react")
+import { FieldViewProps, FieldView } from './FieldView';
+import { Opt, Field } from '../../../fields/Field';
+import { observer } from "mobx-react"
+import { observable, action } from 'mobx';
+import { Document } from '../../../fields/Document';
+import { Key } from '../../../fields/Key';
+import { Server } from "../../Server"
+
+// Represents one row in a key value plane
+
+export interface KeyValuePairProps {
+ rowStyle: string;
+ fieldId: string;
+ doc: Document;
+}
+@observer
+export class KeyValuePair extends React.Component<KeyValuePairProps> {
+
+ @observable
+ private key: Opt<Key>
+
+ constructor(props: KeyValuePairProps) {
+ super(props);
+ Server.GetField(this.props.fieldId,
+ action((field: Opt<Field>) => {
+ if (field) {
+ this.key = field as Key;
+ }
+ }));
+
+ }
+
+
+ render() {
+ if (!this.key) {
+ return <tr><td>error</td><td></td></tr>
+
+ }
+ let props: FieldViewProps = {
+ doc: this.props.doc,
+ fieldKey: this.key,
+ isSelected: () => false,
+ select: () => { },
+ isTopMost: false,
+ bindings: {},
+ selectOnLoad: false,
+ }
+ return (
+ <tr className={this.props.rowStyle}>
+ <td>{this.key.Name}</td>
+ <td><FieldView {...props} /></td>
+ </tr>
+ )
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
new file mode 100644
index 000000000..e72b3c4da
--- /dev/null
+++ b/src/client/views/nodes/WebBox.scss
@@ -0,0 +1,14 @@
+
+.webBox-cont {
+ padding: 0vw;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
+
+.webBox-button {
+ padding : 0vw;
+ border: none;
+ width : 100%;
+ height: 100%;
+} \ No newline at end of file
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
new file mode 100644
index 000000000..2ca8d49ce
--- /dev/null
+++ b/src/client/views/nodes/WebBox.tsx
@@ -0,0 +1,38 @@
+import "./WebBox.scss";
+import React = require("react")
+import { WebField } from '../../../fields/WebField';
+import { FieldViewProps, FieldView } from './FieldView';
+import { FieldWaiting } from '../../../fields/Field';
+import { observer } from "mobx-react"
+import { computed } from 'mobx';
+import { KeyStore } from '../../../fields/KeyStore';
+
+@observer
+export class WebBox extends React.Component<FieldViewProps> {
+
+ public static LayoutString() { return FieldView.LayoutString(WebBox); }
+
+ constructor(props: FieldViewProps) {
+ super(props);
+ }
+
+ @computed get html(): string { return this.props.doc.GetHtml(KeyStore.Data, ""); }
+
+ render() {
+ let field = this.props.doc.Get(this.props.fieldKey);
+ let path = field == FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
+ field instanceof WebField ? field.Data.href : "https://crossorigin.me/" + "https://cs.brown.edu";
+
+ let content = this.html ?
+ <span dangerouslySetInnerHTML={{ __html: this.html }}></span> :
+ <div style={{ width: "100%", height: "100%", position: "absolute" }}>
+ <iframe src={path} style={{ position: "absolute", width: "100%", height: "100%" }}></iframe>
+ {this.props.isSelected() ? (null) : <div style={{ width: "100%", height: "100%", position: "absolute" }} />}
+ </div>;
+
+ return (
+ <div className="webBox-cont" >
+ {content}
+ </div>)
+ }
+} \ No newline at end of file