aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--.vscode/launch.json4
-rw-r--r--deploy/debug/test.html13
-rw-r--r--package-lock.json9
-rw-r--r--package.json1
-rw-r--r--src/client/documents/Documents.ts239
-rw-r--r--src/client/util/DragManager.ts30
-rw-r--r--src/client/util/Scripting.ts122
-rw-r--r--src/client/util/type_decls.d215
-rw-r--r--src/client/views/ContextMenu.scss19
-rw-r--r--src/client/views/ContextMenu.tsx19
-rw-r--r--src/client/views/ContextMenuItem.tsx10
-rw-r--r--src/client/views/DocumentDecorations.tsx2
-rw-r--r--src/client/views/EditableView.tsx30
-rw-r--r--src/client/views/Main.scss22
-rw-r--r--src/client/views/Main.tsx218
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx55
-rw-r--r--src/client/views/collections/CollectionFreeFormView.scss10
-rw-r--r--src/client/views/collections/CollectionFreeFormView.tsx56
-rw-r--r--src/client/views/collections/CollectionSchemaView.scss9
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx21
-rw-r--r--src/client/views/collections/CollectionTreeView.scss33
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx153
-rw-r--r--src/client/views/collections/CollectionView.tsx36
-rw-r--r--src/client/views/collections/CollectionViewBase.tsx11
-rw-r--r--src/client/views/nodes/DocumentView.tsx79
-rw-r--r--src/client/views/nodes/FieldView.tsx21
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx47
-rw-r--r--src/client/views/nodes/ImageBox.tsx12
-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
-rw-r--r--src/client/views/nodes/WebView.tsx22
-rw-r--r--src/debug/Test.tsx46
-rw-r--r--src/fields/Document.ts63
-rw-r--r--src/fields/KVPField0
-rw-r--r--src/fields/KVPField.ts30
-rw-r--r--src/fields/KeyStore.ts3
-rw-r--r--src/fields/WebField.ts30
-rw-r--r--src/server/Message.ts2
-rw-r--r--src/server/ServerUtil.ts3
-rw-r--r--src/server/index.ts5
-rw-r--r--tsconfig.json6
-rw-r--r--webpack.config.js1
46 files changed, 1362 insertions, 574 deletions
diff --git a/.gitignore b/.gitignore
index d26c7464b..2f9680a59 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
node_modules
-dist/ \ No newline at end of file
+dist/
+.DS_Store
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 8aa197fa5..074f9ddf0 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -12,10 +12,6 @@
"breakOnLoad": true,
"url": "http://localhost:1050",
"webRoot": "${workspaceFolder}",
- "skipFiles": [
- "${workspaceRoot}/node_modules/**/*.js",
- "${workspaceRoot}/node_modules/**/*.ts",
- ]
},
{
"type": "node",
diff --git a/deploy/debug/test.html b/deploy/debug/test.html
new file mode 100644
index 000000000..58febbc81
--- /dev/null
+++ b/deploy/debug/test.html
@@ -0,0 +1,13 @@
+<html>
+
+<head>
+ <title>Test view</title>
+ <link href="https://fonts.googleapis.com/css?family=Fjalla+One|Hind+Siliguri:300" rel="stylesheet">
+</head>
+
+<body>
+ <div id="root"></div>
+ <script src="../test.js"></script>
+</body>
+
+</html> \ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 937715315..f0111b92c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10013,6 +10013,15 @@
"unpipe": "1.0.0"
}
},
+ "raw-loader": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-1.0.0.tgz",
+ "integrity": "sha512-Uqy5AqELpytJTRxYT4fhltcKPj0TyaEpzJDcGz7DFJi+pQOOi3GjR/DOdxTkTsF+NzhnldIoG6TORaBlInUuqA==",
+ "requires": {
+ "loader-utils": "^1.1.0",
+ "schema-utils": "^1.0.0"
+ }
+ },
"rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
diff --git a/package.json b/package.json
index ad1cda94e..168d6038b 100644
--- a/package.json
+++ b/package.json
@@ -106,6 +106,7 @@
"prosemirror-transform": "^1.1.3",
"prosemirror-view": "^1.7.1",
"pug": "^2.0.3",
+ "raw-loader": "^1.0.0",
"react": "^16.5.2",
"react-dimensions": "^1.3.1",
"react-dom": "^16.7.0",
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 9770e5cdc..1d24ff7d2 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -7,10 +7,14 @@ import { ListField } from "../../fields/ListField";
import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
import { ImageField } from "../../fields/ImageField";
import { ImageBox } from "../views/nodes/ImageBox";
+import { WebField } from "../../fields/WebField";
+import { WebBox } from "../views/nodes/WebBox";
import { CollectionView, CollectionViewType } from "../views/collections/CollectionView";
-import { FieldView } from "../views/nodes/FieldView";
import { HtmlField } from "../../fields/HtmlField";
-import { WebView } from "../views/nodes/WebView";
+import { Key } from "../../fields/Key"
+import { Field } from "../../fields/Field";
+import { KeyValueBox } from "../views/nodes/KeyValueBox"
+import { KVPField } from "../../fields/KVPField";
export interface DocumentOptions {
x?: number;
@@ -20,109 +24,124 @@ export interface DocumentOptions {
nativeWidth?: number;
nativeHeight?: number;
title?: string;
+ panx?: number;
+ pany?: number;
+ scale?: number;
+ layout?: string;
+ layoutKeys?: Key[];
+ viewType?: number;
}
export namespace Documents {
- export function initProtos(callback: () => void) {
- Server.GetFields([collectionProtoId, textProtoId, imageProtoId], (fields) => {
- collectionProto = fields[collectionProtoId] as Document;
+ let textProto: Document;
+ let imageProto: Document;
+ let webProto: Document;
+ let collProto: Document;
+ let kvpProto: Document;
+ const textProtoId = "textProto";
+ const imageProtoId = "imageProto";
+ const webProtoId = "webProto";
+ const collProtoId = "collectionProto";
+ const kvpProtoId = "kvpProto";
+
+ export function initProtos(mainDocId: string, callback: (mainDoc?: Document) => void) {
+ Server.GetFields([collProtoId, textProtoId, imageProtoId, mainDocId], (fields) => {
+ collProto = fields[collProtoId] as Document;
imageProto = fields[imageProtoId] as Document;
textProto = fields[textProtoId] as Document;
- callback()
+ webProto = fields[webProtoId] as Document;
+ kvpProto = fields[kvpProtoId] as Document;
+ callback(fields[mainDocId] as Document)
});
}
+ function assignOptions(doc: Document, options: DocumentOptions): Document {
+ if (options.x !== undefined) { doc.SetNumber(KeyStore.X, options.x); }
+ if (options.y !== undefined) { doc.SetNumber(KeyStore.Y, options.y); }
+ if (options.width !== undefined) { doc.SetNumber(KeyStore.Width, options.width); }
+ if (options.height !== undefined) { doc.SetNumber(KeyStore.Height, options.height); }
+ if (options.nativeWidth !== undefined) { doc.SetNumber(KeyStore.NativeWidth, options.nativeWidth); }
+ if (options.nativeHeight !== undefined) { doc.SetNumber(KeyStore.NativeHeight, options.nativeHeight); }
+ if (options.title !== undefined) { doc.SetText(KeyStore.Title, options.title); }
+ if (options.panx !== undefined) { doc.SetNumber(KeyStore.PanX, options.panx); }
+ if (options.pany !== undefined) { doc.SetNumber(KeyStore.PanY, options.pany); }
+ if (options.scale !== undefined) { doc.SetNumber(KeyStore.Scale, options.scale); }
+ if (options.viewType !== undefined) { doc.SetNumber(KeyStore.ViewType, options.viewType); }
+ if (options.layout !== undefined) { doc.SetText(KeyStore.Layout, options.layout); }
+ if (options.layoutKeys !== undefined) { doc.Set(KeyStore.LayoutKeys, new ListField(options.layoutKeys)); }
+ return doc;
+ }
+ function setupPrototypeOptions(protoId: string, title: string, layout: string, options: DocumentOptions): Document {
+ return assignOptions(new Document(protoId), { ...options, title: title, layout: layout });
+ }
+ function SetInstanceOptions<T, U extends Field & { Data: T }>(doc: Document, options: DocumentOptions, value: T, ctor: { new(): U }, id?: string) {
+ var deleg = doc.MakeDelegate(id);
+ deleg.SetData(KeyStore.Data, value, ctor);
+ return assignOptions(deleg, options);
+ }
- function setupOptions(doc: Document, options: DocumentOptions): void {
- if (options.x !== undefined) {
- doc.SetData(KeyStore.X, options.x, NumberField);
- }
- if (options.y !== undefined) {
- doc.SetData(KeyStore.Y, options.y, NumberField);
- }
- if (options.width !== undefined) {
- doc.SetData(KeyStore.Width, options.width, NumberField);
- }
- if (options.height !== undefined) {
- doc.SetData(KeyStore.Height, options.height, NumberField);
- }
- if (options.nativeWidth !== undefined) {
- doc.SetData(KeyStore.NativeWidth, options.nativeWidth, NumberField);
- }
- if (options.nativeHeight !== undefined) {
- doc.SetData(KeyStore.NativeHeight, options.nativeHeight, NumberField);
- }
- if (options.title !== undefined) {
- doc.SetData(KeyStore.Title, options.title, TextField);
+ function GetImagePrototype(): Document {
+ if (!imageProto) {
+ imageProto = setupPrototypeOptions(imageProtoId, "IMAGE_PROTO", CollectionView.LayoutString("AnnotationsKey"),
+ { x: 0, y: 0, nativeWidth: 300, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations] });
+ imageProto.SetText(KeyStore.BackgroundLayout, ImageBox.LayoutString());
}
- doc.SetData(KeyStore.Scale, 1, NumberField);
- doc.SetData(KeyStore.PanX, 0, NumberField);
- doc.SetData(KeyStore.PanY, 0, NumberField);
+ return imageProto;
}
-
- let textProto: Document;
- const textProtoId = "textProto";
function GetTextPrototype(): Document {
- if (!textProto) {
- textProto = new Document(textProtoId);
- textProto.Set(KeyStore.X, new NumberField(0));
- textProto.Set(KeyStore.Y, new NumberField(0));
- textProto.Set(KeyStore.Width, new NumberField(300));
- textProto.Set(KeyStore.Height, new NumberField(150));
- textProto.Set(KeyStore.Layout, new TextField(FormattedTextBox.LayoutString()));
- textProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data]));
- }
- return textProto;
+ return textProto ? textProto :
+ textProto = setupPrototypeOptions(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(),
+ { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] });
}
-
- export function TextDocument(options: DocumentOptions = {}): Document {
- let doc = GetTextPrototype().MakeDelegate();
- setupOptions(doc, options);
- // doc.SetField(KeyStore.Data, new RichTextField());
- return doc;
+ function GetWebPrototype(): Document {
+ return webProto ? webProto :
+ webProto = setupPrototypeOptions(webProtoId, "WEB_PROTO", WebBox.LayoutString(),
+ { x: 0, y: 0, width: 300, height: 300, layoutKeys: [KeyStore.Data] });
+ }
+ function GetCollectionPrototype(): Document {
+ return collProto ? collProto :
+ collProto = setupPrototypeOptions(collProtoId, "COLLECTION_PROTO", CollectionView.LayoutString("DataKey"),
+ { panx: 0, pany: 0, scale: 1, layoutKeys: [KeyStore.Data] });
}
- let htmlProto: Document;
- const htmlProtoId = "htmlProto";
- function GetHtmlPrototype(): Document {
- if (!htmlProto) {
- htmlProto = new Document(htmlProtoId);
- htmlProto.Set(KeyStore.X, new NumberField(0));
- htmlProto.Set(KeyStore.Y, new NumberField(0));
- htmlProto.Set(KeyStore.Width, new NumberField(300));
- htmlProto.Set(KeyStore.Height, new NumberField(150));
- htmlProto.Set(KeyStore.Layout, new TextField(WebView.LayoutString()));
- htmlProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data]));
- }
- return htmlProto;
+ function GetKVPPrototype(): Document {
+ return kvpProto ? kvpProto :
+ kvpProto = setupPrototypeOptions(kvpProtoId, "KVP_PROTO", KeyValueBox.LayoutString(),
+ { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] })
}
- export function HtmlDocument(html: string, options: DocumentOptions = {}): Document {
- let doc = GetHtmlPrototype().MakeDelegate();
- setupOptions(doc, options);
- doc.Set(KeyStore.Data, new HtmlField(html));
+ export function ImageDocument(url: string, options: DocumentOptions = {}) {
+ let doc = SetInstanceOptions(GetImagePrototype(), { ...options, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] },
+ new URL(url), ImageField);
+ doc.SetText(KeyStore.Caption, "my caption...");
+ doc.SetText(KeyStore.BackgroundLayout, EmbeddedCaption());
+ doc.SetText(KeyStore.OverlayLayout, FixedCaption());
return doc;
}
+ export function TextDocument(options: DocumentOptions = {}) {
+ return SetInstanceOptions(GetTextPrototype(), options, "", TextField);
+ }
+ export function WebDocument(url: string, options: DocumentOptions = {}) {
+ return SetInstanceOptions(GetWebPrototype(), options, new URL(url), WebField);
+ }
+ export function HtmlDocument(html: string, options: DocumentOptions = {}) {
+ return SetInstanceOptions(GetWebPrototype(), options, html, HtmlField);
+ }
+ export function FreeformDocument(documents: Array<Document>, options: DocumentOptions, id?: string) {
+ return SetInstanceOptions(GetCollectionPrototype(), { ...options, viewType: CollectionViewType.Freeform }, documents, ListField, id)
+ }
+ export function SchemaDocument(documents: Array<Document>, options: DocumentOptions, id?: string) {
+ return SetInstanceOptions(GetCollectionPrototype(), { ...options, viewType: CollectionViewType.Schema }, documents, ListField, id)
+ }
+ export function DockDocument(config: string, options: DocumentOptions, id?: string) {
+ return SetInstanceOptions(GetCollectionPrototype(), { ...options, viewType: CollectionViewType.Docking }, config, TextField, id)
+ }
+ export function KVPDocument(document: Document, options: DocumentOptions = {}, id?: string) {
+ var deleg = GetKVPPrototype().MakeDelegate(id);
+ deleg.Set(KeyStore.Data, document);
+ return assignOptions(deleg, options);
+ }
- let imageProto: Document;
- const imageProtoId = "imageProto";
- function GetImagePrototype(): Document {
- if (!imageProto) {
- imageProto = new Document(imageProtoId);
- imageProto.Set(KeyStore.Title, new TextField("IMAGE PROTO"));
- imageProto.Set(KeyStore.X, new NumberField(0));
- imageProto.Set(KeyStore.Y, new NumberField(0));
- imageProto.Set(KeyStore.NativeWidth, new NumberField(300));
- imageProto.Set(KeyStore.Width, new NumberField(300));
- imageProto.Set(KeyStore.Layout, new TextField(CollectionView.LayoutString("AnnotationsKey")));
- imageProto.SetNumber(KeyStore.ViewType, CollectionViewType.Freeform)
- imageProto.Set(KeyStore.BackgroundLayout, new TextField(ImageBox.LayoutString()));
- // imageProto.SetField(KeyStore.Layout, new TextField('<div style={"background-image: " + {Data}} />'));
- imageProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data, KeyStore.Annotations]));
- return imageProto;
- }
- return imageProto;
- }
// example of custom display string for an image that shows a caption.
function EmbeddedCaption() {
@@ -140,54 +159,4 @@ export namespace Documents {
+ FormattedTextBox.LayoutString("CaptionKey") +
`</div>
</div>` };
-
- export function ImageDocument(url: string, options: DocumentOptions = {}): Document {
- let doc = GetImagePrototype().MakeDelegate();
- setupOptions(doc, options);
- doc.Set(KeyStore.Data, new ImageField(new URL(url)));
- doc.Set(KeyStore.Caption, new TextField("my caption..."));
- doc.Set(KeyStore.BackgroundLayout, new TextField(EmbeddedCaption()));
- doc.Set(KeyStore.OverlayLayout, new TextField(FixedCaption()));
- doc.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data, KeyStore.Annotations, KeyStore.Caption]));
- console.log("" + doc.GetNumber(KeyStore.Height, 311));
- return doc;
- }
-
- let collectionProto: Document;
- const collectionProtoId = "collectionProto";
- function GetCollectionPrototype(): Document {
- if (!collectionProto) {
- collectionProto = new Document(collectionProtoId);
- collectionProto.Set(KeyStore.Scale, new NumberField(1));
- collectionProto.Set(KeyStore.PanX, new NumberField(0));
- collectionProto.Set(KeyStore.PanY, new NumberField(0));
- collectionProto.Set(KeyStore.Layout, new TextField(CollectionView.LayoutString("DataKey")));
- collectionProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data]));
- }
- return collectionProto;
- }
-
- export function CollectionDocument(data: Array<Document> | string, viewType: CollectionViewType, options: DocumentOptions = {}, id?: string): Document {
- let doc = GetCollectionPrototype().MakeDelegate(id);
- setupOptions(doc, options);
- if (typeof data === "string") {
- doc.SetText(KeyStore.Data, data);
- } else {
- doc.SetData(KeyStore.Data, data, ListField);
- }
- doc.SetNumber(KeyStore.ViewType, viewType);
- return doc;
- }
-
- export function FreeformDocument(documents: Array<Document>, options: DocumentOptions, id?: string) {
- return CollectionDocument(documents, CollectionViewType.Freeform, options, id)
- }
-
- export function SchemaDocument(documents: Array<Document>, options: DocumentOptions, id?: string) {
- return CollectionDocument(documents, CollectionViewType.Schema, options, id)
- }
-
- export function DockDocument(config: string, options: DocumentOptions, id?: string) {
- return CollectionDocument(config, CollectionViewType.Docking, options, id)
- }
} \ No newline at end of file
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 8adadee0f..60910a40b 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -2,15 +2,16 @@ import { DocumentDecorations } from "../views/DocumentDecorations";
import { CollectionDockingView } from "../views/collections/CollectionDockingView";
import { Document } from "../../fields/Document"
import { action } from "mobx";
+import { DocumentView } from "../views/nodes/DocumentView";
-export function setupDrag(_reference: React.RefObject<HTMLDivElement>, _dragDocument: Document) {
+export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Document) {
let onRowMove = action((e: PointerEvent): void => {
e.stopPropagation();
e.preventDefault();
document.removeEventListener("pointermove", onRowMove);
document.removeEventListener('pointerup', onRowUp);
- DragManager.StartDrag(_reference.current!, { document: _dragDocument });
+ DragManager.StartDrag(_reference.current!, { document: docFunc() });
});
let onRowUp = action((e: PointerEvent): void => {
document.removeEventListener("pointermove", onRowMove);
@@ -20,9 +21,8 @@ export function setupDrag(_reference: React.RefObject<HTMLDivElement>, _dragDocu
// if (this.props.isSelected() || this.props.isTopMost) {
if (e.button == 0) {
e.stopPropagation();
- e.preventDefault();
if (e.shiftKey) {
- CollectionDockingView.Instance.StartOtherDrag(_reference.current!, _dragDocument);
+ CollectionDockingView.Instance.StartOtherDrag(docFunc(), e);
} else {
document.addEventListener("pointermove", onRowMove);
document.addEventListener('pointerup', onRowUp);
@@ -133,29 +133,43 @@ export namespace DragManager {
if (hideSource) {
ele.hidden = true;
}
-
const moveHandler = (e: PointerEvent) => {
e.stopPropagation();
e.preventDefault();
x += e.movementX;
y += e.movementY;
+ if (e.shiftKey) {
+ abortDrag();
+ const docView: DocumentView = dragData["documentView"];
+ const doc: Document = docView ? docView.props.Document : dragData["document"];
+ CollectionDockingView.Instance.StartOtherDrag(doc, { pageX: e.pageX, pageY: e.pageY, preventDefault: () => { }, button: 0 });
+ }
dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
};
- const upHandler = (e: PointerEvent) => {
+
+ const abortDrag = () => {
document.removeEventListener("pointermove", moveHandler, true);
document.removeEventListener("pointerup", upHandler);
- FinishDrag(dragElement, e, dragData, options);
+ dragDiv.removeChild(dragElement);
if (hideSource && !wasHidden) {
ele.hidden = false;
}
+ }
+ const upHandler = (e: PointerEvent) => {
+ abortDrag();
+ FinishDrag(ele, e, dragData, options);
};
document.addEventListener("pointermove", moveHandler, true);
document.addEventListener("pointerup", upHandler);
}
function FinishDrag(dragEle: HTMLElement, e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions) {
- dragDiv.removeChild(dragEle);
+ let parent = dragEle.parentElement;
+ if (parent)
+ parent.removeChild(dragEle);
const target = document.elementFromPoint(e.x, e.y);
+ if (parent)
+ parent.appendChild(dragEle);
if (!target) {
return;
}
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index befb9df4c..44f36fe19 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -1,12 +1,21 @@
-// import * as ts from "typescript"
-let ts = (window as any).ts;
+import * as ts from "typescript"
+// let ts = (window as any).ts;
import { Opt, Field } from "../../fields/Field";
-import { Document as DocumentImport } from "../../fields/Document";
-import { NumberField as NumberFieldImport, NumberField } from "../../fields/NumberField";
-import { ImageField as ImageFieldImport } from "../../fields/ImageField";
-import { TextField as TextFieldImport, TextField } from "../../fields/TextField";
-import { RichTextField as RichTextFieldImport } from "../../fields/RichTextField";
-import { KeyStore as KeyStoreImport } from "../../fields/KeyStore";
+import { Document } from "../../fields/Document";
+import { NumberField } from "../../fields/NumberField";
+import { ImageField } from "../../fields/ImageField";
+import { TextField } from "../../fields/TextField";
+import { RichTextField } from "../../fields/RichTextField";
+import { KeyStore } from "../../fields/KeyStore";
+import { ListField } from "../../fields/ListField";
+// // @ts-ignore
+// import * as typescriptlib from '!!raw-loader!../../../node_modules/typescript/lib/lib.d.ts'
+// // @ts-ignore
+// import * as typescriptes5 from '!!raw-loader!../../../node_modules/typescript/lib/lib.es5.d.ts'
+
+// @ts-ignore
+import * as typescriptlib from '!!raw-loader!./type_decls.d'
+
export interface ExecutableScript {
(): any;
@@ -14,23 +23,25 @@ export interface ExecutableScript {
compiled: boolean;
}
-function ExecScript(script: string, diagnostics: Opt<any[]>): ExecutableScript {
+function Compile(script: string | undefined, diagnostics: Opt<any[]>, scope: { [name: string]: any }): ExecutableScript {
const compiled = !(diagnostics && diagnostics.some(diag => diag.category == ts.DiagnosticCategory.Error));
let func: () => Opt<Field>;
- if (compiled) {
+ if (compiled && script) {
+ let fieldTypes = [Document, NumberField, TextField, ImageField, RichTextField, ListField];
+ let paramNames = ["KeyStore", ...fieldTypes.map(fn => fn.name)];
+ let params: any[] = [KeyStore, ...fieldTypes]
+ for (let prop in scope) {
+ if (prop === "this") {
+ continue;
+ }
+ paramNames.push(prop);
+ params.push(scope[prop]);
+ }
+ let thisParam = scope["this"];
+ let compiledFunction = new Function(...paramNames, script);
func = function (): Opt<Field> {
- let KeyStore = KeyStoreImport;
- let Document = DocumentImport;
- let NumberField = NumberFieldImport;
- let TextField = TextFieldImport;
- let ImageField = ImageFieldImport;
- let RichTextField = RichTextFieldImport;
- let window = undefined;
- let document = undefined;
- let retVal = eval(script);
-
- return retVal;
+ return compiledFunction.apply(thisParam, params)
};
} else {
func = () => undefined;
@@ -42,10 +53,73 @@ function ExecScript(script: string, diagnostics: Opt<any[]>): ExecutableScript {
});
}
-export function CompileScript(script: string): ExecutableScript {
- let result = (window as any).ts.transpileModule(script, {})
+interface File {
+ fileName: string;
+ content: string;
+}
+
+// class ScriptingCompilerHost implements ts.CompilerHost {
+class ScriptingCompilerHost {
+ files: File[] = [];
+
+ // getSourceFile(fileName: string, languageVersion: ts.ScriptTarget, onError?: ((message: string) => void) | undefined, shouldCreateNewSourceFile?: boolean | undefined): ts.SourceFile | undefined {
+ getSourceFile(fileName: string, languageVersion: any, onError?: ((message: string) => void) | undefined, shouldCreateNewSourceFile?: boolean | undefined): any | undefined {
+ let contents = this.readFile(fileName);
+ if (contents !== undefined) {
+ return ts.createSourceFile(fileName, contents, languageVersion, true);
+ }
+ return undefined;
+ }
+ // getDefaultLibFileName(options: ts.CompilerOptions): string {
+ getDefaultLibFileName(options: any): string {
+ return 'node_modules/typescript/lib/lib.d.ts' // No idea what this means...
+ }
+ writeFile(fileName: string, content: string) {
+ const file = this.files.find(file => file.fileName === fileName);
+ if (file) {
+ file.content = content;
+ } else {
+ this.files.push({ fileName, content })
+ }
+ }
+ getCurrentDirectory(): string {
+ return '';
+ }
+ getCanonicalFileName(fileName: string): string {
+ return this.useCaseSensitiveFileNames() ? fileName : fileName.toLowerCase();
+ }
+ useCaseSensitiveFileNames(): boolean {
+ return true;
+ }
+ getNewLine(): string {
+ return '\n';
+ }
+ fileExists(fileName: string): boolean {
+ return this.files.some(file => file.fileName === fileName);
+ }
+ readFile(fileName: string): string | undefined {
+ let file = this.files.find(file => file.fileName === fileName);
+ if (file) {
+ return file.content;
+ }
+ return undefined;
+ }
+}
+
+export function CompileScript(script: string, scope?: { [name: string]: any }, addReturn: boolean = false): ExecutableScript {
+ let host = new ScriptingCompilerHost;
+ let funcScript = `(function() {
+ ${addReturn ? `return ${script};` : script}
+ })()`
+ host.writeFile("file.ts", funcScript);
+ host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib);
+ let program = ts.createProgram(["file.ts"], {}, host);
+ let testResult = program.emit();
+ let outputText = "return " + host.readFile("file.js");
+
+ let diagnostics = ts.getPreEmitDiagnostics(program).concat(testResult.diagnostics);
- return ExecScript(result.outputText, result.diagnostics);
+ return Compile(outputText, diagnostics, scope || {});
}
export function ToField(data: any): Opt<Field> {
diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d
new file mode 100644
index 000000000..679f73f42
--- /dev/null
+++ b/src/client/util/type_decls.d
@@ -0,0 +1,215 @@
+//@ts-ignore
+declare type PropertyKey = string | number | symbol;
+interface Array<T> {
+ length: number;
+ toString(): string;
+ toLocaleString(): string;
+ pop(): T | undefined;
+ push(...items: T[]): number;
+ concat(...items: ConcatArray<T>[]): T[];
+ concat(...items: (T | ConcatArray<T>)[]): T[];
+ join(separator?: string): string;
+ reverse(): T[];
+ shift(): T | undefined;
+ slice(start?: number, end?: number): T[];
+ sort(compareFn?: (a: T, b: T) => number): this;
+ splice(start: number, deleteCount?: number): T[];
+ splice(start: number, deleteCount: number, ...items: T[]): T[];
+ unshift(...items: T[]): number;
+ indexOf(searchElement: T, fromIndex?: number): number;
+ lastIndexOf(searchElement: T, fromIndex?: number): number;
+ every(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): boolean;
+ some(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): boolean;
+ forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void;
+ map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
+ filter<S extends T>(callbackfn: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[];
+ filter(callbackfn: (value: T, index: number, array: T[]) => any, thisArg?: any): T[];
+ reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T;
+ reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
+ reduce<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U;
+ reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T;
+ reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
+ reduceRight<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U;
+
+ [n: number]: T;
+}
+
+interface Function {
+ apply(this: Function, thisArg: any, argArray?: any): any;
+ call(this: Function, thisArg: any, ...argArray: any[]): any;
+ bind(this: Function, thisArg: any, ...argArray: any[]): any;
+ toString(): string;
+
+ prototype: any;
+ readonly length: number;
+
+ // Non-standard extensions
+ arguments: any;
+ caller: Function;
+}
+interface Boolean {
+ valueOf(): boolean;
+}
+interface Number {
+ toString(radix?: number): string;
+ toFixed(fractionDigits?: number): string;
+ toExponential(fractionDigits?: number): string;
+ toPrecision(precision?: number): string;
+ valueOf(): number;
+}
+interface IArguments {
+ [index: number]: any;
+ length: number;
+ callee: Function;
+}
+interface RegExp {
+ readonly flags: string;
+ readonly sticky: boolean;
+ readonly unicode: boolean;
+}
+interface String {
+ codePointAt(pos: number): number | undefined;
+ includes(searchString: string, position?: number): boolean;
+ endsWith(searchString: string, endPosition?: number): boolean;
+ normalize(form: "NFC" | "NFD" | "NFKC" | "NFKD"): string;
+ normalize(form?: string): string;
+ repeat(count: number): string;
+ startsWith(searchString: string, position?: number): boolean;
+ anchor(name: string): string;
+ big(): string;
+ blink(): string;
+ bold(): string;
+ fixed(): string;
+ fontcolor(color: string): string;
+ fontsize(size: number): string;
+ fontsize(size: string): string;
+ italics(): string;
+ link(url: string): string;
+ small(): string;
+ strike(): string;
+ sub(): string;
+ sup(): string;
+}
+interface Object {
+ constructor: Function;
+ toString(): string;
+ toLocaleString(): string;
+ valueOf(): Object;
+ hasOwnProperty(v: PropertyKey): boolean;
+ isPrototypeOf(v: Object): boolean;
+ propertyIsEnumerable(v: PropertyKey): boolean;
+}
+interface ConcatArray<T> {
+ readonly length: number;
+ readonly [n: number]: T;
+ join(separator?: string): string;
+ slice(start?: number, end?: number): T[];
+}
+interface URL {
+ hash: string;
+ host: string;
+ hostname: string;
+ href: string;
+ readonly origin: string;
+ password: string;
+ pathname: string;
+ port: string;
+ protocol: string;
+ search: string;
+ username: string;
+ toJSON(): string;
+}
+
+declare type FieldId = string;
+
+declare abstract class Field {
+ Id: FieldId;
+ abstract ToScriptString(): string;
+ abstract TrySetValue(value: any): boolean;
+ abstract GetValue(): any;
+ abstract Copy(): Field;
+}
+
+declare abstract class BasicField<T> extends Field {
+ constructor(data: T);
+ Data: T;
+ TrySetValue(value: any): boolean;
+ GetValue(): any;
+}
+
+declare class TextField extends BasicField<string>{
+ constructor();
+ constructor(data: string);
+ ToScriptString(): string;
+ Copy(): Field;
+}
+declare class ImageField extends BasicField<URL>{
+ constructor();
+ constructor(data: URL);
+ ToScriptString(): string;
+ Copy(): Field;
+}
+declare class HtmlField extends BasicField<string>{
+ constructor();
+ constructor(data: string);
+ ToScriptString(): string;
+ Copy(): Field;
+}
+declare class NumberField extends BasicField<number>{
+ constructor();
+ constructor(data: number);
+ ToScriptString(): string;
+ Copy(): Field;
+}
+declare class WebField extends BasicField<URL>{
+ constructor();
+ constructor(data: URL);
+ ToScriptString(): string;
+ Copy(): Field;
+}
+declare class ListField<T> extends BasicField<T[]>{
+ constructor();
+ constructor(data: T[]);
+ ToScriptString(): string;
+ Copy(): Field;
+}
+declare class Key extends Field {
+ Name: string;
+ TrySetValue(value: any): boolean;
+ GetValue(): any;
+ Copy(): Field;
+ ToScriptString(): string;
+}
+declare type FIELD_WAITING = "<Waiting>";
+declare type Opt<T> = T | undefined;
+declare type FieldValue<T> = Opt<T> | FIELD_WAITING;
+// @ts-ignore
+declare class Document extends Field {
+ TrySetValue(value: any): boolean;
+ GetValue(): any;
+ Copy(): Field;
+ ToScriptString(): string;
+
+ Width(): number;
+ Height(): number;
+ Scale(): number;
+ Title: string;
+
+ Get(key: Key): FieldValue<Field>;
+ GetAsync(key: Key, callback: (field: Field) => void): boolean;
+ GetOrCreateAsync<T extends Field>(key: Key, ctor: { new(): T }, callback: (field: T) => void): void;
+ GetT<T extends Field>(key: Key, ctor: { new(): T }): FieldValue<T>;
+ GetOrCreate<T extends Field>(key: Key, ctor: { new(): T }): T;
+ GetData<T, U extends Field & { Data: T }>(key: Key, ctor: { new(): U }, defaultVal: T): T;
+ GetHtml(key: Key, defaultVal: string): string;
+ GetNumber(key: Key, defaultVal: number): number;
+ GetText(key: Key, defaultVal: string): string;
+ GetList<T extends Field>(key: Key, defaultVal: T[]): T[];
+ Set(key: Key, field: Field | undefined): void;
+ SetData<T, U extends Field & { Data: T }>(key: Key, value: T, ctor: { new(): U }): void;
+ SetText(key: Key, value: string): void;
+ SetNumber(key: Key, value: number): void;
+ GetPrototype(): FieldValue<Document>;
+ GetAllPrototypes(): Document[];
+ MakeDelegate(): Document;
+}
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index 234f82eb9..ea40c8e99 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -3,16 +3,16 @@
display: flex;
z-index: 1000;
box-shadow: #AAAAAA .2vw .2vw .4vw;
+ flex-direction: column;
}
.contextMenu-item {
- width: 10vw;
- height: 4vh;
- background: #DDDDDD;
+ width: auto;
+ height: auto;
+ background: #F0F8FF;
display: flex;
- justify-content: center;
+ justify-content: left;
align-items: center;
- flex-direction: column;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
@@ -20,11 +20,18 @@
-ms-user-select: none;
user-select: none;
transition: all .1s;
+ border-width: .11px;
+ border-style: none;
+ border-color: rgb(187, 186, 186);
+ border-bottom-style: solid;
+ padding: 10px;
+ white-space: nowrap;
+ font-size: 1.5vw;
}
.contextMenu-item:hover {
transition: all .1s;
- background: #AAAAAA
+ background: #B0E0E6;
}
.contextMenu-description {
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 4f26a75d2..fcb934860 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -1,6 +1,6 @@
import React = require("react");
import { ContextMenuItem, ContextMenuProps } from "./ContextMenuItem";
-import { observable } from "mobx";
+import { observable, action } from "mobx";
import { observer } from "mobx-react";
import "./ContextMenu.scss"
@@ -12,6 +12,8 @@ export class ContextMenu extends React.Component {
@observable private _pageX: number = 0;
@observable private _pageY: number = 0;
@observable private _display: string = "none";
+ @observable private _searchString: string = "";
+
private ref: React.RefObject<HTMLDivElement>;
@@ -23,11 +25,13 @@ export class ContextMenu extends React.Component {
ContextMenu.Instance = this;
}
+ @action
clearItems() {
this._items = []
this._display = "none"
}
+ @action
addItem(item: ContextMenuProps) {
if (this._items.indexOf(item) === -1) {
this._items.push(item);
@@ -38,10 +42,13 @@ export class ContextMenu extends React.Component {
return this._items;
}
+ @action
displayMenu(x: number, y: number) {
this._pageX = x
this._pageY = y
+ this._searchString = "";
+
this._display = "flex"
}
@@ -59,10 +66,18 @@ export class ContextMenu extends React.Component {
render() {
return (
<div className="contextMenu-cont" style={{ left: this._pageX, top: this._pageY, display: this._display }} ref={this.ref}>
- {this._items.map(prop => {
+ <input className="contextMenu-item" type="text" placeholder="Search . . ." value={this._searchString} onChange={this.onChange}></input>
+ {this._items.filter(prop => {
+ return prop.description.toLowerCase().indexOf(this._searchString.toLowerCase()) !== -1;
+ }).map(prop => {
return <ContextMenuItem {...prop} key={prop.description} />
})}
</div>
)
}
+
+ @action
+ onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this._searchString = e.target.value;
+ }
} \ No newline at end of file
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 8f00f8b3d..4801c1555 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -1,11 +1,19 @@
import React = require("react");
-import { ContextMenu } from "./ContextMenu";
export interface ContextMenuProps {
description: string;
event: (e: React.MouseEvent<HTMLDivElement>) => void;
}
+export interface SubmenuProps {
+ description: string;
+ subitems: ContextMenuProps[];
+}
+
+export interface ContextMenuItemProps {
+ type: ContextMenuProps | SubmenuProps
+}
+
export class ContextMenuItem extends React.Component<ContextMenuProps> {
render() {
return (
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 975a125f7..9fd73a33b 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,4 +1,4 @@
-import { observable, computed, action } from "mobx";
+import { observable, computed } from "mobx";
import React = require("react");
import { SelectionManager } from "../util/SelectionManager";
import { observer } from "mobx-react";
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 8d9a47eaa..84b1b91c3 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -3,12 +3,30 @@ import { observer } from 'mobx-react';
import { observable, action } from 'mobx';
export interface EditableProps {
+ /**
+ * Called to get the initial value for editing
+ * */
GetValue(): string;
+
+ /**
+ * Called to apply changes
+ * @param value - The string entered by the user to set the value to
+ * @returns `true` if setting the value was successful, `false` otherwise
+ * */
SetValue(value: string): boolean;
+
+ /**
+ * The contents to render when not editing
+ */
contents: any;
height: number
}
+/**
+ * Customizable view that can be given an arbitrary view to render normally,
+ * but can also be edited with customizable functions to get a string version
+ * of the content, and set the value based on the entered string.
+ */
@observer
export class EditableView extends React.Component<EditableProps> {
@observable
@@ -17,8 +35,9 @@ export class EditableView extends React.Component<EditableProps> {
@action
onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key == "Enter" && !e.ctrlKey) {
- this.props.SetValue(e.currentTarget.value);
- this.editing = false;
+ if (this.props.SetValue(e.currentTarget.value)) {
+ this.editing = false;
+ }
} else if (e.key == "Escape") {
this.editing = false;
}
@@ -27,12 +46,11 @@ export class EditableView extends React.Component<EditableProps> {
render() {
if (this.editing) {
return <input defaultValue={this.props.GetValue()} onKeyDown={this.onKeyDown} autoFocus onBlur={action(() => this.editing = false)}
- style={{ width: "100%" }}></input>
+ style={{ display: "inline" }}></input>
} else {
return (
- <div className="editableView-container-editing" style={{ display: "flex", height: "100%", maxHeight: `${this.props.height}` }}
- onClick={action(() => this.editing = true)}
- >
+ <div className="editableView-container-editing" style={{ display: "inline", height: "100%", maxHeight: `${this.props.height}` }}
+ onClick={action(() => this.editing = true)}>
{this.props.contents}
</div>
)
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index e73f62904..4334ed299 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -28,4 +28,24 @@ h1 {
p {
margin: 0px;
padding: 0px;
-} \ No newline at end of file
+}
+::-webkit-scrollbar {
+ -webkit-appearance: none;
+ height:5px;
+ width:5px;
+}
+::-webkit-scrollbar-thumb {
+ border-radius: 2px;
+ background-color: rgba(0,0,0,.5);
+}
+
+.main-buttonDiv {
+ position: absolute;
+ width: 150px;
+ left: 0px;
+}
+.main-undoButtons {
+ position: absolute;
+ width: 150px;
+ right: 0px;
+}
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 17dda899d..d845fa7a3 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -1,167 +1,101 @@
-import { action, configure, reaction, computed } from 'mobx';
+import { action, configure } from 'mobx';
import "normalize.css";
import * as React from 'react';
import * as ReactDOM from 'react-dom';
-import { DocumentDecorations } from './DocumentDecorations';
-import { Documents } from '../documents/Documents';
import { Document } from '../../fields/Document';
import { KeyStore } from '../../fields/KeyStore';
import "./Main.scss";
-import { ContextMenu } from './ContextMenu';
-import { DocumentView } from './nodes/DocumentView';
-import { Server } from '../Server';
+import { MessageStore } from '../../server/Message';
import { Utils } from '../../Utils';
-import { ServerUtils } from '../../server/ServerUtil';
-import { MessageStore, DocumentTransfer } from '../../server/Message';
+import { Documents } from '../documents/Documents';
+import { Server } from '../Server';
+import { setupDrag } from '../util/DragManager';
import { Transform } from '../util/Transform';
-import { CollectionDockingView } from './collections/CollectionDockingView';
-import { FieldWaiting } from '../../fields/Field';
import { UndoManager } from '../util/UndoManager';
-import { DragManager } from '../util/DragManager';
+import { CollectionDockingView } from './collections/CollectionDockingView';
+import { ContextMenu } from './ContextMenu';
+import { DocumentDecorations } from './DocumentDecorations';
+import { DocumentView } from './nodes/DocumentView';
+import "./Main.scss";
-configure({
- enforceActions: "observed"
-});
-window.addEventListener("drop", function (e) {
- e.preventDefault();
-}, false)
-window.addEventListener("dragover", function (e) {
- e.preventDefault();
-}, false)
+configure({ enforceActions: "observed" }); // causes errors to be generated when modifying an observable outside of an action
+window.addEventListener("drop", (e) => e.preventDefault(), false)
+window.addEventListener("dragover", (e) => e.preventDefault(), false)
document.addEventListener("pointerdown", action(function (e: PointerEvent) {
- console.log(ContextMenu);
if (!ContextMenu.Instance.intersects(e.pageX, e.pageY)) {
ContextMenu.Instance.clearItems()
}
}), true)
-//runInAction(() =>
-// let doc1 = Documents.TextDocument({ title: "hello" });
-// let doc2 = doc1.MakeDelegate();
-// doc2.Set(KS.X, new NumberField(150));
-// doc2.Set(KS.Y, new NumberField(20));
-// let doc3 = Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", {
-// x: 450, y: 100, title: "cat 1"
-// });
-// doc3.Set(KeyStore.Data, new ImageField);
-// const schemaDocs = Array.from(Array(5).keys()).map(v => Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", {
-// x: 50 + 100 * v, y: 50, width: 100, height: 100, title: "cat" + v
-// }));
-// schemaDocs[0].SetData(KS.Author, "Tyler", TextField);
-// schemaDocs[4].SetData(KS.Author, "Bob", TextField);
-// schemaDocs.push(doc2);
-// const doc7 = Documents.SchemaDocument(schemaDocs)
-
const mainDocId = "mainDoc";
-Documents.initProtos(() => {
- Utils.EmitCallback(Server.Socket, MessageStore.GetField, mainDocId, (res: any) => {
- console.log("HELLO WORLD")
- console.log("RESPONSE: " + res)
- let mainContainer: Document;
- let mainfreeform: Document;
- if (res) {
- mainContainer = ServerUtils.FromJson(res) as Document;
- mainContainer.GetAsync(KeyStore.ActiveFrame, field => mainfreeform = field as Document);
- }
- else {
- mainContainer = Documents.DockDocument(JSON.stringify({ content: [{ type: 'row', content: [] }] }), { title: "main container" }, mainDocId);
- Utils.Emit(Server.Socket, MessageStore.AddDocument, new DocumentTransfer(mainContainer.ToJson()))
+let mainContainer: Document;
+let mainfreeform: Document;
+Documents.initProtos(mainDocId, (res?: Document) => {
+ if (res instanceof Document) {
+ mainContainer = res;
+ mainContainer.GetAsync(KeyStore.ActiveFrame, field => mainfreeform = field as Document);
+ }
+ else {
+ mainContainer = Documents.DockDocument(JSON.stringify({ content: [{ type: 'row', content: [] }] }), { title: "main container" }, mainDocId);
- setTimeout(() => {
- mainfreeform = Documents.FreeformDocument([], { x: 0, y: 400, title: "mini collection" });
- Utils.Emit(Server.Socket, MessageStore.AddDocument, new DocumentTransfer(mainfreeform.ToJson()));
+ // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
+ setTimeout(() => {
+ mainfreeform = Documents.FreeformDocument([], { x: 0, y: 400, title: "mini collection" });
- var docs = [mainfreeform].map(doc => CollectionDockingView.makeDocumentConfig(doc));
- mainContainer.SetText(KeyStore.Data, JSON.stringify({ content: [{ type: 'row', content: docs }] }));
- mainContainer.Set(KeyStore.ActiveFrame, mainfreeform);
- }, 0);
- }
+ var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(mainfreeform)] }] };
+ mainContainer.SetText(KeyStore.Data, JSON.stringify(dockingLayout));
+ mainContainer.Set(KeyStore.ActiveFrame, mainfreeform);
+ }, 0);
+ }
- let clearDatabase = action(() => Utils.Emit(Server.Socket, MessageStore.DeleteAll, {}))
- let addTextNode = action(() => Documents.TextDocument({ width: 200, height: 200, title: "a text note" }))
- let addColNode = action(() => Documents.FreeformDocument([], { width: 200, height: 200, title: "a feeform collection" }));
- let addSchemaNode = action(() => Documents.SchemaDocument([Documents.TextDocument()], { width: 200, height: 200, title: "a schema collection" }));
- let addImageNode = action(() => Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", {
- width: 200, height: 200, title: "an image of a cat"
- }));
+ let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg";
+ let weburl = "https://cs.brown.edu/courses/cs166/";
+ let clearDatabase = action(() => Utils.Emit(Server.Socket, MessageStore.DeleteAll, {}))
+ let addTextNode = action(() => Documents.TextDocument({ width: 200, height: 200, title: "a text note" }))
+ let addColNode = action(() => Documents.FreeformDocument([], { width: 200, height: 200, title: "a freeform collection" }));
+ let addSchemaNode = action(() => Documents.SchemaDocument([Documents.TextDocument()], { width: 200, height: 200, title: "a schema collection" }));
+ let addImageNode = action(() => Documents.ImageDocument(imgurl, { width: 200, height: 200, title: "an image of a cat" }));
+ let addWebNode = action(() => Documents.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" }));
- let addClick = (creator: any) => action(() => {
- var img = creator();
- img.SetNumber(KeyStore.X, 0);
- img.SetNumber(KeyStore.Y, 0);
- mainfreeform.GetList<Document>(KeyStore.Data, []).push(img);
- });
+ let addClick = (creator: () => Document) => action(() =>
+ mainfreeform.GetList<Document>(KeyStore.Data, []).push(creator())
+ );
- let imgRef = React.createRef<HTMLDivElement>();
- let textRef = React.createRef<HTMLDivElement>();
- let schemaRef = React.createRef<HTMLDivElement>();
- let colRef = React.createRef<HTMLDivElement>();
- let curMoveListener: any = null
- let onRowMove = (creator: any, dragRef: any) => action((e: PointerEvent): void => {
- e.stopPropagation();
- e.preventDefault();
+ let imgRef = React.createRef<HTMLDivElement>();
+ let webRef = React.createRef<HTMLDivElement>();
+ let textRef = React.createRef<HTMLDivElement>();
+ let schemaRef = React.createRef<HTMLDivElement>();
+ let colRef = React.createRef<HTMLDivElement>();
- document.removeEventListener("pointermove", curMoveListener);
- document.removeEventListener('pointerup', onRowUp);
- DragManager.StartDrag(dragRef.current!, { document: creator() });
- });
- let onRowUp = action((e: PointerEvent): void => {
- document.removeEventListener("pointermove", curMoveListener);
- document.removeEventListener('pointerup', onRowUp);
- });
- let onRowDown = (creator: any, dragRef: any) => (e: React.PointerEvent) => {
- if (e.shiftKey) {
- CollectionDockingView.Instance.StartOtherDrag(dragRef.current!, creator());
- e.stopPropagation();
- } else {
- document.addEventListener("pointermove", curMoveListener = onRowMove(creator, dragRef));
- document.addEventListener('pointerup', onRowUp);
- }
- }
- ReactDOM.render((
- <div style={{ position: "absolute", width: "100%", height: "100%" }}>
- <DocumentView Document={mainContainer}
- AddDocument={undefined} RemoveDocument={undefined} ScreenToLocalTransform={() => Transform.Identity}
- ContentScaling={() => 1}
- PanelWidth={() => 0}
- PanelHeight={() => 0}
- isTopMost={true}
- ContainingCollectionView={undefined} />
- <DocumentDecorations />
- <ContextMenu />
- <div style={{ position: 'absolute', bottom: '0px', left: '0px', width: '150px' }} ref={imgRef} >
- <button onPointerDown={onRowDown(addImageNode, imgRef)} onClick={addClick(addImageNode)}>Add Image</button></div>
- <div style={{ position: 'absolute', bottom: '25px', left: '0px', width: '150px' }} ref={textRef}>
- <button onPointerDown={onRowDown(addTextNode, textRef)} onClick={addClick(addTextNode)}>Add Text</button></div>
- <div style={{ position: 'absolute', bottom: '50px', left: '0px', width: '150px' }} ref={colRef}>
- <button onPointerDown={onRowDown(addColNode, colRef)} onClick={addClick(addColNode)}>Add Collection</button></div>
- <div style={{ position: 'absolute', bottom: '75px', left: '0px', width: '150px' }} ref={schemaRef}>
- <button onPointerDown={onRowDown(addSchemaNode, schemaRef)} onClick={addClick(addSchemaNode)}>Add Schema</button></div>
- <button style={{ position: 'absolute', bottom: '100px', left: '0px', width: '150px' }} onClick={clearDatabase}>Clear Database</button>
- <button style={{ position: 'absolute', bottom: '25', right: '0px', width: '150px' }} onClick={() => UndoManager.Undo()}>Undo</button>
- <button style={{ position: 'absolute', bottom: '0', right: '0px', width: '150px' }} onClick={() => UndoManager.Redo()}>Redo</button>
- </div>),
- document.getElementById('root'));
- })
-});
-// let doc5 = Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", {
-// x: 650, y: 500, width: 600, height: 600, title: "cat 2"
-// });
-// let docset2 = new Array<Document>(doc4);//, doc1, doc3);
-// let doc6 = Documents.CollectionDocument(docset2, {
-// x: 350, y: 100, width: 600, height: 600, title: "docking collection"
-// });
-// let mainNodes = mainContainer.GetOrCreate(KeyStore.Data, ListField);
-// mainNodes.Data.push(doc6);
-// mainNodes.Data.push(doc2);
-// mainNodes.Data.push(doc4);
-// mainNodes.Data.push(doc3);
-// mainNodes.Data.push(doc5);
-// mainNodes.Data.push(doc1);
-//mainNodes.Data.push(doc2);
-//mainNodes.Data.push(doc6);
-// mainContainer.Set(KeyStore.Data, mainNodes);
-//}
-//);
+ ReactDOM.render((
+ <div style={{ position: "absolute", width: "100%", height: "100%" }}>
+ <DocumentView Document={mainContainer}
+ AddDocument={undefined} RemoveDocument={undefined} ScreenToLocalTransform={() => Transform.Identity}
+ ContentScaling={() => 1}
+ PanelWidth={() => 0}
+ PanelHeight={() => 0}
+ isTopMost={true}
+ SelectOnLoad={false}
+ focus={() => { }}
+ ContainingCollectionView={undefined} />
+ <DocumentDecorations />
+ <ContextMenu />
+ <div className="main-buttonDiv" style={{ bottom: '0px' }} ref={imgRef} >
+ <button onPointerDown={setupDrag(imgRef, addImageNode)} onClick={addClick(addImageNode)}>Add Image</button></div>
+ <div className="main-buttonDiv" style={{ bottom: '25px' }} ref={webRef} >
+ <button onPointerDown={setupDrag(webRef, addWebNode)} onClick={addClick(addWebNode)}>Add Web</button></div>
+ <div className="main-buttonDiv" style={{ bottom: '50px' }} ref={textRef}>
+ <button onPointerDown={setupDrag(textRef, addTextNode)} onClick={addClick(addTextNode)}>Add Text</button></div>
+ <div className="main-buttonDiv" style={{ bottom: '75px' }} ref={colRef}>
+ <button onPointerDown={setupDrag(colRef, addColNode)} onClick={addClick(addColNode)}>Add Collection</button></div>
+ <div className="main-buttonDiv" style={{ bottom: '100px' }} ref={schemaRef}>
+ <button onPointerDown={setupDrag(schemaRef, addSchemaNode)} onClick={addClick(addSchemaNode)}>Add Schema</button></div>
+ <div className="main-buttonDiv" style={{ bottom: '125px' }} >
+ <button onClick={clearDatabase}>Clear Database</button></div>
+ <button className="main-undoButtons" style={{ bottom: '25px' }} onClick={() => UndoManager.Undo()}>Undo</button>
+ <button className="main-undoButtons" style={{ bottom: '0px' }} onClick={() => UndoManager.Redo()}>Redo</button>
+ </div>),
+ document.getElementById('root'));
+})
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 5fb632469..c51fba908 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -1,16 +1,15 @@
import * as GoldenLayout from "golden-layout";
import 'golden-layout/src/css/goldenlayout-base.css';
import 'golden-layout/src/css/goldenlayout-dark-theme.css';
-import { action, computed, observable, reaction } from "mobx";
+import { action, observable, reaction } from "mobx";
import { observer } from "mobx-react";
import * as ReactDOM from 'react-dom';
-import Measure from "react-measure";
import { Document } from "../../../fields/Document";
-import { FieldId, Opt, Field } from "../../../fields/Field";
import { KeyStore } from "../../../fields/KeyStore";
+import Measure from "react-measure";
+import { FieldId, Opt, Field } from "../../../fields/Field";
import { Utils } from "../../../Utils";
import { Server } from "../../Server";
-import { DragManager } from "../../util/DragManager";
import { undoBatch } from "../../util/UndoManager";
import { DocumentView } from "../nodes/DocumentView";
import "./CollectionDockingView.scss";
@@ -34,10 +33,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
}
private _goldenLayout: any = null;
- private _dragDiv: any = null;
- private _dragParent: HTMLElement | null = null;
- private _dragElement: HTMLElement | undefined;
- private _dragFakeElement: HTMLElement | undefined;
private _containerRef = React.createRef<HTMLDivElement>();
private _fullScreen: any = null;
@@ -47,28 +42,8 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
(window as any).React = React;
(window as any).ReactDOM = ReactDOM;
}
-
- public StartOtherDrag(dragElement: HTMLElement, dragDoc: Document) {
- this._dragElement = dragElement;
- this._dragParent = dragElement.parentElement;
- // bcz: we want to copy this document into the header, not move it there.
- // However, GoldenLayout is setup to move things, so we have to do some kludgy stuff:
-
- // - create a temporary invisible div and register that as a DragSource with GoldenLayout
- this._dragDiv = document.createElement("div");
- this._dragDiv.style.opacity = 0;
- DragManager.Root().appendChild(this._dragDiv);
- this._goldenLayout.createDragSource(this._dragDiv, CollectionDockingView.makeDocumentConfig(dragDoc));
-
- // - add our document to that div so that GoldenLayout will get the move events its listening for
- this._dragDiv.appendChild(this._dragElement);
-
- // - add a duplicate of our document to the original document's container
- // (GoldenLayout will be removing our original one)
- this._dragFakeElement = dragElement.cloneNode(true) as HTMLElement;
- this._dragParent!.appendChild(this._dragFakeElement);
-
- // all of this must be undone when the document has been dropped (see tabCreated)
+ public StartOtherDrag(dragDoc: Document, e: any) {
+ this.AddRightSplit(dragDoc, true).contentItems[0].tab._dragListener.onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: () => { }, button: e.button })
}
@action
@@ -98,7 +73,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
// Creates a vertical split on the right side of the docking view, and then adds the Document to that split
//
@action
- public AddRightSplit(document: Document) {
+ public AddRightSplit(document: Document, minimize: boolean = false) {
this._goldenLayout.emit('stateChanged');
let newItemStackConfig = {
type: 'stack',
@@ -121,10 +96,15 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
collayout.config["width"] = 50;
newContentItem.config["width"] = 50;
}
+ if (minimize) {
+ newContentItem.config["width"] = 10;
+ newContentItem.config["height"] = 10;
+ }
newContentItem.callDownwards('_$init');
this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]);
this._goldenLayout.emit('stateChanged');
this.stateChanged();
+ return newContentItem;
}
setupGoldenLayout() {
@@ -218,13 +198,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
this.stateChanged();
}
tabCreated = (tab: any) => {
- if (this._dragDiv) {
- this._dragDiv.removeChild(this._dragElement);
- this._dragParent!.removeChild(this._dragFakeElement!);
- this._dragParent!.appendChild(this._dragElement!);
- DragManager.Root().removeChild(this._dragDiv);
- this._dragDiv = null;
- }
tab.closeElement.off('click') //unbind the current click handler
.click(function () {
tab.contentItem.remove();
@@ -245,7 +218,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
render() {
return (
<div className="collectiondockingview-container" id="menuContainer"
- onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} onContextMenu={(e) => e.preventDefault()} ref={this._containerRef}
+ onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef}
style={{
width: "100%",
height: "100%",
@@ -263,7 +236,7 @@ interface DockedFrameProps {
@observer
export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
- @observable private _mainCont = React.createRef<HTMLDivElement>();
+ private _mainCont = React.createRef<HTMLDivElement>();
@observable private _panelWidth = 0;
@observable private _panelHeight = 0;
@observable private _document: Opt<Document>;
@@ -295,6 +268,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
PanelHeight={this._nativeHeight}
ScreenToLocalTransform={this.ScreenToLocalTransform}
isTopMost={true}
+ SelectOnLoad={false}
+ focus={(doc: Document) => { }}
ContainingCollectionView={undefined} />
</div>
diff --git a/src/client/views/collections/CollectionFreeFormView.scss b/src/client/views/collections/CollectionFreeFormView.scss
index f7b2e6449..f432e8cc3 100644
--- a/src/client/views/collections/CollectionFreeFormView.scss
+++ b/src/client/views/collections/CollectionFreeFormView.scss
@@ -1,15 +1,5 @@
.collectionfreeformview-container {
- ::-webkit-scrollbar {
- -webkit-appearance: none;
- height:5px;
- width:5px;
- }
- ::-webkit-scrollbar-thumb {
- border-radius: 2px;
- background-color: rgba(0,0,0,.5);
- }
-
.collectionfreeformview > .jsx-parser{
position:absolute;
height: 100%;
diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx
index 63ecec0df..f71f2791c 100644
--- a/src/client/views/collections/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/CollectionFreeFormView.tsx
@@ -13,9 +13,10 @@ import { CollectionSchemaView } from "../collections/CollectionSchemaView";
import { CollectionView } from "../collections/CollectionView";
import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
import { DocumentView } from "../nodes/DocumentView";
-import { WebView } from "../nodes/WebView";
import { FormattedTextBox } from "../nodes/FormattedTextBox";
import { ImageBox } from "../nodes/ImageBox";
+import { WebBox } from "../nodes/WebBox";
+import { KeyValueBox } from "../nodes/KeyValueBox"
import "./CollectionFreeFormView.scss";
import { COLLECTION_BORDER_WIDTH } from "./CollectionView";
import { CollectionViewBase } from "./CollectionViewBase";
@@ -28,6 +29,7 @@ export class CollectionFreeFormView extends CollectionViewBase {
private _canvasRef = React.createRef<HTMLDivElement>();
private _lastX: number = 0;
private _lastY: number = 0;
+ private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type)
@observable
private _downX: number = 0;
@@ -96,13 +98,12 @@ export class CollectionFreeFormView extends CollectionViewBase {
@action
onPointerMove = (e: PointerEvent): void => {
if (!e.cancelBubble && this.props.active()) {
- e.preventDefault();
e.stopPropagation();
let x = this.props.Document.GetNumber(KeyStore.PanX, 0);
let y = this.props.Document.GetNumber(KeyStore.PanY, 0);
- let [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
+ let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
this._previewCursorVisible = false;
- this.SetPan(x + dx, y + dy);
+ this.SetPan(x - dx, y - dy);
}
this._lastX = e.pageX;
this._lastY = e.pageY;
@@ -133,11 +134,12 @@ export class CollectionFreeFormView extends CollectionViewBase {
deltaScale = 1 / this.zoomScaling;
let [x, y] = transform.transformPoint(e.clientX, e.clientY);
- let localTransform = this.getLocalTransform();
+ let localTransform = this.getLocalTransform()
localTransform = localTransform.inverse().scaleAbout(deltaScale, x, y)
+ console.log(localTransform)
this.props.Document.SetNumber(KeyStore.Scale, localTransform.Scale);
- this.SetPan(localTransform.TranslateX, localTransform.TranslateY);
+ this.SetPan(-localTransform.TranslateX / localTransform.Scale, -localTransform.TranslateY / localTransform.Scale);
}
}
@@ -161,14 +163,15 @@ export class CollectionFreeFormView extends CollectionViewBase {
@action
onKeyDown = (e: React.KeyboardEvent<Element>) => {
//if not these keys, make a textbox if preview cursor is active!
- if (!e.ctrlKey && !e.altKey && !e.shiftKey) {
+ if (!e.ctrlKey && !e.altKey) {
if (this._previewCursorVisible) {
//make textbox and add it to this collection
let [x, y] = this.getTransform().transformPoint(this._downX, this._downY); (this._downX, this._downY);
let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "new" });
+ // mark this collection so that when the text box is created we can send it the SelectOnLoad prop to focus itself
+ this._selectOnLoaded = newBox.Id;
//set text to be the typed key and get focus on text box
this.props.CollectionView.addDocument(newBox);
- newBox.SetNumber(KeyStore.SelectOnLoaded, 1);
//remove cursor from screen
this._previewCursorVisible = false;
}
@@ -193,7 +196,6 @@ export class CollectionFreeFormView extends CollectionViewBase {
});
}
-
@computed get backgroundLayout(): string | undefined {
let field = this.props.Document.GetT(KeyStore.BackgroundLayout, TextField);
if (field && field !== "<Waiting>") {
@@ -206,10 +208,18 @@ export class CollectionFreeFormView extends CollectionViewBase {
return field.Data;
}
}
+
+ focusDocument = (doc: Document) => {
+ let x = doc.GetNumber(KeyStore.X, 0) + doc.GetNumber(KeyStore.Width, 0) / 2;
+ let y = doc.GetNumber(KeyStore.Y, 0) + doc.GetNumber(KeyStore.Height, 0) / 2;
+ this.SetPan(x, y);
+ this.props.focus(this.props.Document);
+ }
+
+
@computed
get views() {
- const { fieldKey, Document } = this.props;
- const lvalue = Document.GetT<ListField<Document>>(fieldKey, ListField);
+ const lvalue = this.props.Document.GetT<ListField<Document>>(this.props.fieldKey, ListField);
if (lvalue && lvalue != FieldWaiting) {
return lvalue.Data.map(doc => {
return (<CollectionFreeFormDocumentView key={doc.Id} Document={doc}
@@ -217,10 +227,13 @@ export class CollectionFreeFormView extends CollectionViewBase {
RemoveDocument={this.props.removeDocument}
ScreenToLocalTransform={this.getTransform}
isTopMost={false}
+ SelectOnLoad={doc.Id === this._selectOnLoaded}
ContentScaling={this.noScaling}
PanelWidth={doc.Width}
PanelHeight={doc.Height}
- ContainingCollectionView={this.props.CollectionView} />);
+ ContainingCollectionView={this.props.CollectionView}
+ focus={this.focusDocument}
+ />);
})
}
return null;
@@ -230,7 +243,7 @@ export class CollectionFreeFormView extends CollectionViewBase {
get backgroundView() {
return !this.backgroundLayout ? (null) :
(<JsxParser
- components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebView }}
+ components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox }}
bindings={this.props.bindings}
jsx={this.backgroundLayout}
showWarnings={true}
@@ -241,7 +254,7 @@ export class CollectionFreeFormView extends CollectionViewBase {
get overlayView() {
return !this.overlayLayout ? (null) :
(<JsxParser
- components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView }}
+ components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox }}
bindings={this.props.bindings}
jsx={this.overlayLayout}
showWarnings={true}
@@ -249,13 +262,13 @@ export class CollectionFreeFormView extends CollectionViewBase {
/>);
}
- getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH - this.centeringShiftX, -COLLECTION_BORDER_WIDTH - this.centeringShiftY).transform(this.getLocalTransform())
- getLocalTransform = (): Transform => Transform.Identity.translate(-this.panX, -this.panY).scale(1 / this.scale);
+ getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform())
+ getLocalTransform = (): Transform => Transform.Identity.scale(1 / this.scale).translate(this.panX, this.panY);
noScaling = () => 1;
//when focus is lost, this will remove the preview cursor
@action
- onBlur = (e: React.FocusEvent<HTMLInputElement>): void => {
+ onBlur = (e: React.FocusEvent<HTMLDivElement>): void => {
this._previewCursorVisible = false;
}
@@ -269,15 +282,16 @@ export class CollectionFreeFormView extends CollectionViewBase {
cursor = <div id="prevCursor" onKeyPress={this.onKeyDown} style={{ color: "black", position: "absolute", transformOrigin: "left top", transform: `translate(${x}px, ${y}px)` }}>I</div>
}
- const panx: number = this.props.Document.GetNumber(KeyStore.PanX, 0) + this.centeringShiftX;
- const pany: number = this.props.Document.GetNumber(KeyStore.PanY, 0) + this.centeringShiftY;
+ let [dx, dy] = [this.centeringShiftX, this.centeringShiftY];
+
+ const panx: number = -this.props.Document.GetNumber(KeyStore.PanX, 0);
+ const pany: number = -this.props.Document.GetNumber(KeyStore.PanY, 0);
return (
<div className="collectionfreeformview-container"
onPointerDown={this.onPointerDown}
onKeyPress={this.onKeyDown}
onWheel={this.onPointerWheel}
- onContextMenu={(e) => e.preventDefault()}
onDrop={this.onDrop.bind(this)}
onDragOver={this.onDragOver}
onBlur={this.onBlur}
@@ -285,7 +299,7 @@ export class CollectionFreeFormView extends CollectionViewBase {
tabIndex={0}
ref={this.createDropTarget}>
<div className="collectionfreeformview"
- style={{ transformOrigin: "left top", transform: ` translate(${panx}px, ${pany}px) scale(${this.zoomScaling}, ${this.zoomScaling})` }}
+ style={{ transformOrigin: "left top", transform: `translate(${dx}px, ${dy}px) scale(${this.zoomScaling}, ${this.zoomScaling}) translate(${panx}px, ${pany}px)` }}
ref={this._canvasRef}>
{this.backgroundView}
{cursor}
diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss
index 995d60f74..d40e6d314 100644
--- a/src/client/views/collections/CollectionSchemaView.scss
+++ b/src/client/views/collections/CollectionSchemaView.scss
@@ -6,15 +6,6 @@
position: absolute;
width: 100%;
height: 100%;
- ::-webkit-scrollbar {
- -webkit-appearance: none;
- height:5px;
- width:5px;
- }
- ::-webkit-scrollbar-thumb {
- border-radius: 2px;
- background-color: rgba(0,0,0,.5);
- }
.collectionSchemaView-previewRegion {
position: relative;
background: black;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 106a5c4f5..49f95c014 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -1,14 +1,15 @@
import React = require("react")
-import { action, observable, trace } from "mobx";
+import { action, observable } from "mobx";
import { observer } from "mobx-react";
import Measure from "react-measure";
import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults } from "react-table";
import "react-table/react-table.css";
import { Document } from "../../../fields/Document";
-import { Field, FieldWaiting } from "../../../fields/Field";
+import { Field } from "../../../fields/Field";
import { KeyStore } from "../../../fields/KeyStore";
import { CompileScript, ToField } from "../../util/Scripting";
import { Transform } from "../../util/Transform";
+import { ContextMenu } from "../ContextMenu";
import { EditableView } from "../EditableView";
import { DocumentView } from "../nodes/DocumentView";
import { FieldView, FieldViewProps } from "../nodes/FieldView";
@@ -39,15 +40,16 @@ export class CollectionSchemaView extends CollectionViewBase {
isSelected: () => false,
select: () => { },
isTopMost: false,
- bindings: {}
+ bindings: {},
+ selectOnLoad: false,
}
let contents = (
<FieldView {...props} />
)
let reference = React.createRef<HTMLDivElement>();
- let onItemDown = setupDrag(reference, props.doc);
+ let onItemDown = setupDrag(reference, () => props.doc);
return (
- <div onPointerDown={onItemDown} ref={reference}>
+ <div onPointerDown={onItemDown} key={props.doc.Id} ref={reference}>
<EditableView contents={contents}
height={36} GetValue={() => {
let field = props.doc.Get(props.fieldKey);
@@ -57,7 +59,7 @@ export class CollectionSchemaView extends CollectionViewBase {
return field || "";
}}
SetValue={(value: string) => {
- let script = CompileScript(value);
+ let script = CompileScript(value, undefined, true);
if (!script.compiled) {
return false;
}
@@ -174,6 +176,8 @@ export class CollectionSchemaView extends CollectionViewBase {
return this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling);
}
+ focusDocument = (doc: Document) => { }
+
render() {
const columns = this.props.Document.GetList(KeyStore.ColumnsKey, [KeyStore.Title, KeyStore.Data, KeyStore.Author])
const children = this.props.Document.GetList<Document>(this.props.fieldKey, []);
@@ -185,11 +189,14 @@ export class CollectionSchemaView extends CollectionViewBase {
<DocumentView Document={selected}
AddDocument={this.props.addDocument} RemoveDocument={this.props.removeDocument}
isTopMost={false}
+ SelectOnLoad={false}
ScreenToLocalTransform={this.getTransform}
ContentScaling={this.getContentScaling}
PanelWidth={this.getPanelWidth}
PanelHeight={this.getPanelHeight}
- ContainingCollectionView={this.props.CollectionView} />
+ ContainingCollectionView={this.props.CollectionView}
+ focus={this.focusDocument}
+ />
</div>
}
</Measure>
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index c488e2894..f8d580a7b 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -1,3 +1,8 @@
+#body {
+ padding: 20px;
+ background: #bbbbbb;
+}
+
ul {
list-style: none;
}
@@ -10,25 +15,23 @@ li {
padding-left: 0;
}
-/* ALL THESE SPACINGS ARE SUPER HACKY RIGHT NOW HANNAH PLS HELP */
-
-li:before {
- content: '\2014';
- margin-right: 0.7em;
+.bullet {
+ width: 1.5em;
+ display: inline-block;
}
-.collapsed:before {
- content: '\25b6';
- margin-right: 0.65em;
+.collectionTreeView-dropTarget {
+ border-style: solid;
+ box-sizing: border-box;
+ height: 100%;
}
-.uncollapsed:before {
- content: '\25bc';
- margin-right: 0.5em;
+.docContainer {
+ display: inline-table;
}
-.collectionTreeView-dropTarget {
- border-style: solid;
- box-sizing: border-box;
- height:100%;
+.delete-button {
+ color: #999999;
+ float: right;
+ margin-left: 1em;
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 64f4c0970..8b06d9ac4 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -7,12 +7,20 @@ import React = require("react")
import { TextField } from "../../../fields/TextField";
import { observable, action } from "mobx";
import "./CollectionTreeView.scss";
+import { EditableView } from "../EditableView";
import { setupDrag } from "../../util/DragManager";
import { FieldWaiting } from "../../../fields/Field";
import { COLLECTION_BORDER_WIDTH } from "./CollectionView";
export interface TreeViewProps {
document: Document;
+ deleteDoc: (doc: Document) => void;
+}
+
+export enum BulletType {
+ Collapsed,
+ Collapsible,
+ List
}
@observer
@@ -24,41 +32,101 @@ class TreeView extends React.Component<TreeViewProps> {
@observable
collapsed: boolean = false;
+ delete = () => {
+ this.props.deleteDoc(this.props.document);
+ }
+
+
+ @action
+ remove = (document: Document) => {
+ var children = this.props.document.GetT<ListField<Document>>(KeyStore.Data, ListField);
+ if (children && children !== FieldWaiting) {
+ children.Data.splice(children.Data.indexOf(document), 1);
+ }
+ }
+
+ renderBullet(type: BulletType) {
+ let onClicked = action(() => this.collapsed = !this.collapsed);
+
+ switch (type) {
+ case BulletType.Collapsed:
+ return <div className="bullet" onClick={onClicked}>&#9654;</div>
+ case BulletType.Collapsible:
+ return <div className="bullet" onClick={onClicked}>&#9660;</div>
+ case BulletType.List:
+ return <div className="bullet">&mdash;</div>
+ }
+ }
+
/**
- * Renders a single child document. If this child is a collection, it will call renderTreeView again. Otherwise, it will just append a list element.
- * @param childDocument The document to render.
+ * Renders the EditableView title element for placement into the tree.
*/
- renderChild(childDocument: Document) {
+ renderTitle() {
+ let title = this.props.document.GetT<TextField>(KeyStore.Title, TextField);
+
+ // if the title hasn't loaded, immediately return the div
+ if (!title || title === "<Waiting>") {
+ return <div key={this.props.document.Id}></div>;
+ }
+
+ return <div className="docContainer"> <EditableView contents={title.Data}
+ height={36} GetValue={() => {
+ let title = this.props.document.GetT<TextField>(KeyStore.Title, TextField);
+ if (title && title !== "<Waiting>")
+ return title.Data;
+ return "";
+ }} SetValue={(value: string) => {
+ this.props.document.SetData(KeyStore.Title, value, TextField);
+ return true;
+ }} />
+ <div className="delete-button" onClick={this.delete}>x</div>
+ </div >
+ }
+
+ render() {
+ var children = this.props.document.GetT<ListField<Document>>(KeyStore.Data, ListField);
+
let reference = React.createRef<HTMLDivElement>();
+ let onItemDown = setupDrag(reference, () => this.props.document);
+ let titleElement = this.renderTitle();
- var children = childDocument.GetT<ListField<Document>>(KeyStore.Data, ListField);
- let title = childDocument.GetT<TextField>(KeyStore.Title, TextField);
- let onItemDown = setupDrag(reference, childDocument);
+ // check if this document is a collection
+ if (children && children !== FieldWaiting) {
+ let subView;
- if (title && title != FieldWaiting) {
- let subView = !children || this.collapsed || children === FieldWaiting ? (null) :
- <ul>
- <TreeView document={childDocument} />
- </ul>;
- return <div className="treeViewItem-container" onPointerDown={onItemDown} ref={reference}>
- <li className={!children ? "leaf" : this.collapsed ? "collapsed" : "uncollapsed"}
- onClick={action(() => this.collapsed = !this.collapsed)} >
- {title.Data}
- {subView}
+ // if uncollapsed, then add the children elements
+ if (!this.collapsed) {
+ // render all children elements
+ let childrenElement = (children.Data.map(value =>
+ <TreeView document={value} deleteDoc={this.remove} />)
+ )
+ subView =
+ <li key={this.props.document.Id} >
+ {this.renderBullet(BulletType.Collapsible)}
+ {titleElement}
+ <ul key={this.props.document.Id}>
+ {childrenElement}
+ </ul>
+ </li>
+ } else {
+ subView = <li key={this.props.document.Id}>
+ {this.renderBullet(BulletType.Collapsed)}
+ {titleElement}
</li>
+ }
+
+ return <div className="treeViewItem-container" onPointerDown={onItemDown} ref={reference}>
+ {subView}
</div>
}
- return (null);
- }
- render() {
- var children = this.props.document.GetT<ListField<Document>>(KeyStore.Data, ListField);
- return !children || children === FieldWaiting ? (null) :
- (children.Data.map(value =>
- <div key={value.Id}>
- {this.renderChild(value)}
- </div>)
- )
+ // otherwise this is a normal leaf node
+ else {
+ return <li key={this.props.document.Id}>
+ {this.renderBullet(BulletType.List)}
+ {titleElement}
+ </li>;
+ }
}
}
@@ -66,21 +134,42 @@ class TreeView extends React.Component<TreeViewProps> {
@observer
export class CollectionTreeView extends CollectionViewBase {
+ @action
+ remove = (document: Document) => {
+ var children = this.props.Document.GetT<ListField<Document>>(KeyStore.Data, ListField);
+ if (children && children !== FieldWaiting) {
+ children.Data.splice(children.Data.indexOf(document), 1);
+ }
+ }
+
render() {
let titleStr = "";
let title = this.props.Document.GetT<TextField>(KeyStore.Title, TextField);
if (title && title !== FieldWaiting) {
titleStr = title.Data;
}
+
+ var children = this.props.Document.GetT<ListField<Document>>(KeyStore.Data, ListField);
+ let childrenElement = !children || children === FieldWaiting ? (null) :
+ (children.Data.map(value =>
+ <TreeView document={value} key={value.Id} deleteDoc={this.remove} />)
+ )
+
return (
- <div className="collectionTreeView-dropTarget" onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} >
- <h3>{titleStr}</h3>
+ <div id="body" className="collectionTreeView-dropTarget" onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}>
+ <h3>
+ <EditableView contents={titleStr}
+ height={72} GetValue={() => {
+ return this.props.Document.Title;
+ }} SetValue={(value: string) => {
+ this.props.Document.SetData(KeyStore.Title, value, TextField);
+ return true;
+ }} />
+ </h3>
<ul className="no-indent">
- <TreeView
- document={this.props.Document}
- />
+ {childrenElement}
</ul>
- </div>
+ </div >
);
}
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 35ac48177..31824763d 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable } from "mobx";
+import { action } from "mobx";
import { observer } from "mobx-react";
import { Document } from "../../../fields/Document";
import { ListField } from "../../../fields/ListField";
@@ -30,7 +30,7 @@ export class CollectionView extends React.Component<CollectionViewProps> {
public static LayoutString(fieldKey: string = "DataKey") {
return `<CollectionView Document={Document}
ScreenToLocalTransform={ScreenToLocalTransform} fieldKey={${fieldKey}} panelWidth={PanelWidth} panelHeight={PanelHeight} isSelected={isSelected} select={select} bindings={bindings}
- isTopMost={isTopMost} BackgroundView={BackgroundView} />`;
+ isTopMost={isTopMost} SelectOnLoad={selectOnLoad} BackgroundView={BackgroundView} focus={focus}/>`;
}
public active = () => {
var isSelected = this.props.isSelected();
@@ -49,6 +49,7 @@ export class CollectionView extends React.Component<CollectionViewProps> {
}
}
+
@action
removeDocument = (doc: Document): boolean => {
//TODO This won't create the field if it doesn't already exist
@@ -60,6 +61,7 @@ export class CollectionView extends React.Component<CollectionViewProps> {
break;
}
}
+
if (index !== -1) {
value.splice(index, 1)
@@ -87,29 +89,45 @@ export class CollectionView extends React.Component<CollectionViewProps> {
Document.SetData(KeyStore.ViewType, type, NumberField);
}
+ specificContextMenu = (e: React.MouseEvent): void => {
+ if (!e.isPropagationStopped) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
+ ContextMenu.Instance.addItem({ description: "Freeform", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Freeform) })
+ ContextMenu.Instance.addItem({ description: "Schema", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Schema) })
+ ContextMenu.Instance.addItem({ description: "Treeview", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Tree) })
+ ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) })
+ }
+ }
render() {
let viewType = this.collectionViewType;
-
+ let subView: JSX.Element;
switch (viewType) {
case CollectionViewType.Freeform:
- return (<CollectionFreeFormView {...this.props}
+ subView = (<CollectionFreeFormView {...this.props}
addDocument={this.addDocument} removeDocument={this.removeDocument} active={this.active}
- CollectionView={this} />);
+ CollectionView={this} />)
+ break;
case CollectionViewType.Schema:
- return (<CollectionSchemaView {...this.props}
+ subView = (<CollectionSchemaView {...this.props}
addDocument={this.addDocument} removeDocument={this.removeDocument} active={this.active}
CollectionView={this} />)
+ break;
case CollectionViewType.Docking:
- return (<CollectionDockingView {...this.props}
+ subView = (<CollectionDockingView {...this.props}
addDocument={this.addDocument} removeDocument={this.removeDocument} active={this.active}
CollectionView={this} />)
+ break;
case CollectionViewType.Tree:
- return (<CollectionTreeView {...this.props}
+ subView = (<CollectionTreeView {...this.props}
addDocument={this.addDocument} removeDocument={this.removeDocument} active={this.active}
CollectionView={this} />)
+ break;
default:
- return <div></div>
+ subView = <div></div>
+ break;
}
+ return (<div onContextMenu={this.specificContextMenu}>
+ {subView}
+ </div>)
}
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx
index 217536e2b..0a3b965f2 100644
--- a/src/client/views/collections/CollectionViewBase.tsx
+++ b/src/client/views/collections/CollectionViewBase.tsx
@@ -1,16 +1,16 @@
-import { action, computed } from "mobx";
+import { action } from "mobx";
import { Document } from "../../../fields/Document";
import { ListField } from "../../../fields/ListField";
import React = require("react");
import { KeyStore } from "../../../fields/KeyStore";
-import { Opt, FieldWaiting } from "../../../fields/Field";
+import { FieldWaiting } from "../../../fields/Field";
import { undoBatch } from "../../util/UndoManager";
import { DragManager } from "../../util/DragManager";
import { DocumentView } from "../nodes/DocumentView";
import { Documents, DocumentOptions } from "../../documents/Documents";
import { Key } from "../../../fields/Key";
import { Transform } from "../../util/Transform";
-
+import { CollectionView } from "./CollectionView";
export interface CollectionViewProps {
fieldKey: Key;
@@ -22,12 +22,13 @@ export interface CollectionViewProps {
bindings: any;
panelWidth: () => number;
panelHeight: () => number;
+ focus: (doc: Document) => void;
}
export interface SubCollectionViewProps extends CollectionViewProps {
active: () => boolean;
addDocument: (doc: Document) => void;
removeDocument: (doc: Document) => boolean;
- CollectionView: any;
+ CollectionView: CollectionView;
}
export class CollectionViewBase extends React.Component<SubCollectionViewProps> {
@@ -67,7 +68,7 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
let html = e.dataTransfer.getData("text/html");
let text = e.dataTransfer.getData("text/plain");
if (html && html.indexOf("<img") != 0) {
- let htmlDoc = Documents.HtmlDocument(html, { ...options });
+ let htmlDoc = Documents.HtmlDocument(html, { ...options, width: 300, height: 300 });
htmlDoc.SetText(KeyStore.DocumentText, text);
this.props.addDocument(htmlDoc);
return;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 50dc9ddc1..e01e1d4cd 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -12,26 +12,29 @@ import { CollectionDockingView } from "../collections/CollectionDockingView";
import { CollectionFreeFormView } from "../collections/CollectionFreeFormView";
import { CollectionSchemaView } from "../collections/CollectionSchemaView";
import { CollectionView, CollectionViewType } from "../collections/CollectionView";
-import { WebView } from "./WebView";
import { ContextMenu } from "../ContextMenu";
import { FormattedTextBox } from "../nodes/FormattedTextBox";
import { ImageBox } from "../nodes/ImageBox";
+import { Documents } from "../../documents/Documents"
+import { KeyValueBox } from "./KeyValueBox"
+import { WebBox } from "../nodes/WebBox";
import "./DocumentView.scss";
import React = require("react");
-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 {
ContainingCollectionView: Opt<CollectionView>;
-
Document: Document;
AddDocument?: (doc: Document) => void;
RemoveDocument?: (doc: Document) => boolean;
ScreenToLocalTransform: () => Transform;
isTopMost: boolean;
- //tfs: This shouldn't be necessary I don't think
ContentScaling: () => number;
PanelWidth: () => number;
PanelHeight: () => number;
+ focus: (doc: Document) => void;
+ SelectOnLoad: boolean;
}
export interface JsxArgs extends DocumentViewProps {
Keys: { [name: string]: Key }
@@ -77,29 +80,23 @@ export function FakeJsxArgs(keys: string[], fields: string[] = []): JsxArgs {
@observer
export class DocumentView extends React.Component<DocumentViewProps> {
-
private _mainCont = React.createRef<HTMLDivElement>();
private _documentBindings: any = null;
- private _contextMenuCanOpen = false;
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._mainCont.current!, this.props.Document);
+ CollectionDockingView.Instance.StartOtherDrag(this.props.Document, e);
e.stopPropagation();
} else {
- this._contextMenuCanOpen = true;
if (this.active && !e.isDefaultPrevented()) {
e.stopPropagation();
if (e.buttons === 2) {
@@ -112,16 +109,14 @@ export class DocumentView extends React.Component<DocumentViewProps> {
}
}
}
-
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;
+ document.removeEventListener("pointermove", this.onPointerMove)
+ document.removeEventListener("pointerup", this.onPointerUp)
if (this._mainCont.current != null && !this.topMost) {
- this._contextMenuCanOpen = false;
const [left, top] = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
let dragData: { [id: string]: any } = {};
dragData["documentView"] = this;
@@ -129,7 +124,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
dragData["yOffset"] = e.y - top;
DragManager.StartDrag(this._mainCont.current, dragData, {
handlers: {
- dragComplete: action((e: DragManager.DragCompleteEvent) => { }),
+ dragComplete: action(() => { }),
},
hideSource: true
})
@@ -138,7 +133,6 @@ export class DocumentView extends React.Component<DocumentViewProps> {
e.stopPropagation();
e.preventDefault();
}
-
onPointerUp = (e: PointerEvent): void => {
document.removeEventListener("pointermove", this.onPointerMove)
document.removeEventListener("pointerup", this.onPointerUp)
@@ -148,17 +142,24 @@ export class DocumentView extends React.Component<DocumentViewProps> {
}
}
- deleteClicked = (e: React.MouseEvent): void => {
+ deleteClicked = (): void => {
if (this.props.RemoveDocument) {
this.props.RemoveDocument(this.props.Document);
}
}
+
+ 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)
}
+
closeFullScreenClicked = (e: React.MouseEvent): void => {
CollectionDockingView.Instance.CloseFullScreen();
ContextMenu.Instance.clearItems();
@@ -168,18 +169,18 @@ export class DocumentView extends React.Component<DocumentViewProps> {
@action
onContextMenu = (e: React.MouseEvent): void => {
- e.preventDefault()
e.stopPropagation();
- if (!SelectionManager.IsSelected(this) || !this._contextMenuCanOpen) {
+ 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.clearItems()
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: "Freeform", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Freeform) })
- ContextMenu.Instance.addItem({ description: "Schema", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Schema) })
- ContextMenu.Instance.addItem({ description: "Treeview", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Tree) })
//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) {
@@ -191,11 +192,9 @@ export class DocumentView extends React.Component<DocumentViewProps> {
ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
SelectionManager.SelectDoc(this, e.ctrlKey);
}
-
@computed get mainContent() {
- var val = this.props.Document.Id;
return <JsxParser
- components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebView }}
+ components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox }}
bindings={this._documentBindings}
jsx={this.layout}
showWarnings={true}
@@ -203,27 +202,34 @@ export class DocumentView extends React.Component<DocumentViewProps> {
/>
}
+ isSelected = () => {
+ return SelectionManager.IsSelected(this);
+ }
+
+ select = (ctrlPressed: boolean) => {
+ SelectionManager.SelectDoc(this, ctrlPressed)
+ }
+
render() {
- if (!this.props.Document)
- return <div></div>
+ 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: () => SelectionManager.IsSelected(this),
- select: (ctrlPressed: boolean) => SelectionManager.SelectDoc(this, ctrlPressed)
+ isSelected: this.isSelected,
+ select: this.select,
+ focus: this.props.focus
};
for (const key of this.layoutKeys) {
- 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
+ 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);
this._documentBindings[key.Name] = field && field != FieldWaiting ? field.GetValue() : field;
}
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);
@@ -233,13 +239,12 @@ export class DocumentView extends React.Component<DocumentViewProps> {
width: nativeWidth > 0 ? nativeWidth.toString() + "px" : "100%",
height: nativeHeight > 0 ? nativeHeight.toString() + "px" : "100%",
transformOrigin: "left top",
- transform: `scale(${scaling},${scaling})`
+ transform: `scale(${scaling} , ${scaling})`
}}
onContextMenu={this.onContextMenu}
- onPointerDown={this.onPointerDown}
- >
+ 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 b71309bf5..9e63006d1 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,17 +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 { HtmlField } from "../../../fields/HtmlField";
-import { WebView } from "./WebView";
+import { WebBox } from "./WebBox";
//
// these properties get assigned through the render() method of the DocumentView when it creates this node.
@@ -24,12 +24,15 @@ export interface FieldViewProps {
isSelected: () => boolean;
select: () => void;
isTopMost: boolean;
+ selectOnLoad: boolean;
bindings: any;
}
@observer
export class FieldView extends React.Component<FieldViewProps> {
- public static LayoutString(fieldType: { name: string }, fieldStr: string = "DataKey") { return `<${fieldType.name} doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={${fieldStr}} isSelected={isSelected} select={select} isTopMost={isTopMost} />`; }
+ 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> {
@@ -50,12 +53,16 @@ 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 instanceof HtmlField) {
- return <WebView {...this.props} />
- }
else if (field != FieldWaiting) {
return <p>{JSON.stringify(field.GetValue())}</p>
}
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index a0f662f18..a6cee9957 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -5,15 +5,15 @@ import { keymap } from "prosemirror-keymap";
import { schema } from "../../util/RichTextSchema";
import { EditorState, Transaction, } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
-import { Opt, FieldWaiting, FieldValue } from "../../../fields/Field";
+import { Opt, FieldWaiting } from "../../../fields/Field";
import "./FormattedTextBox.scss";
-import { KeyStore } from "../../../fields/KeyStore";
import React = require("react")
import { RichTextField } from "../../../fields/RichTextField";
import { FieldViewProps, FieldView } from "./FieldView";
import { Plugin } from 'prosemirror-state'
import { Decoration, DecorationSet } from 'prosemirror-view'
import { TooltipTextMenu } from "../../util/TooltipTextMenu"
+import { ContextMenu } from "../../views/ContextMenu";
@@ -45,7 +45,6 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
super(props);
this._ref = React.createRef();
-
this.onChange = this.onChange.bind(this);
}
@@ -53,16 +52,12 @@ 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;
-
- // let mySchema = new Schema({ nodes, marks })
const config = {
schema,
plugins: [
@@ -71,11 +66,10 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
keymap(baseKeymap),
this.tooltipMenuPlugin()
]
-
};
- 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);
@@ -95,9 +89,7 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field)));
}
})
-
- //if tagged to be selected when created, then select & focus
- if (this.props.doc.GetNumber(KeyStore.SelectOnLoaded, 0)) {
+ if (this.props.selectOnLoad) {
this.props.select();
this._editorView!.focus();
}
@@ -118,15 +110,35 @@ 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 && this.props.isSelected()) {
e.stopPropagation();
}
}
+
+ //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();
}
@@ -142,6 +154,7 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
render() {
return (<div className="formattedTextBox-cont"
onPointerDown={this.onPointerDown}
+ onContextMenu={this.specificContextMenu}
onWheel={this.onPointerWheel}
ref={this._ref} />)
}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 4fe73fb8d..8c44395f4 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -7,9 +7,9 @@ import { ImageField } from '../../../fields/ImageField';
import { FieldViewProps, FieldView } from './FieldView';
import { FieldWaiting } from '../../../fields/Field';
import { observer } from "mobx-react"
+import { ContextMenu } from "../../views/ContextMenu";
import { observable, action } from 'mobx';
import { KeyStore } from '../../../fields/KeyStore';
-import { element } from 'prop-types';
@observer
export class ImageBox extends React.Component<FieldViewProps> {
@@ -89,13 +89,21 @@ export class ImageBox extends React.Component<FieldViewProps> {
}
}
+ //REPLACE THIS WITH CAPABILITIES SPECIFIC TO THIS TYPE OF NODE
+ imageCapability = (e: React.MouseEvent): void => {
+ }
+
+ specificContextMenu = (e: React.MouseEvent): void => {
+ ContextMenu.Instance.addItem({ description: "Image Capability", event: this.imageCapability });
+ }
+
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 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} >
+ <div className="imageBox-cont" onPointerDown={this.onPointerDown} ref={this._ref} onContextMenu={this.specificContextMenu}>
<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
diff --git a/src/client/views/nodes/WebView.tsx b/src/client/views/nodes/WebView.tsx
deleted file mode 100644
index 717aa8bf5..000000000
--- a/src/client/views/nodes/WebView.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { FieldViewProps, FieldView } from "./FieldView";
-import { computed } from "mobx";
-import { observer } from "mobx-react";
-import { KeyStore } from "../../../fields/KeyStore";
-import React = require('react')
-import { TextField } from "../../../fields/TextField";
-import { HtmlField } from "../../../fields/HtmlField";
-import { RichTextField } from "../../../fields/RichTextField";
-
-@observer
-export class WebView extends React.Component<FieldViewProps> {
- public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(WebView, fieldStr) }
-
- @computed
- get html(): string {
- return this.props.doc.GetData(KeyStore.Data, HtmlField, "" as string);
- }
-
- render() {
- return <span dangerouslySetInnerHTML={{ __html: this.html }}></span>
- }
-} \ No newline at end of file
diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
new file mode 100644
index 000000000..7bc70615f
--- /dev/null
+++ b/src/debug/Test.tsx
@@ -0,0 +1,46 @@
+import * as React from 'react';
+import * as ReactDOM from 'react-dom';
+
+class TestInternal extends React.Component {
+ onContextMenu = (e: React.MouseEvent) => {
+ console.log("Internal");
+ e.stopPropagation();
+ }
+
+ onPointerDown = (e: React.MouseEvent) => {
+ console.log("pointer down")
+ e.preventDefault();
+ }
+
+ render() {
+ return <div onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown}
+ onPointerUp={this.onPointerDown}>Hello world</div>
+ }
+}
+
+class TestChild extends React.Component {
+ onContextMenu = () => {
+ console.log("Child");
+ }
+
+ render() {
+ return <div onContextMenu={this.onContextMenu}><TestInternal /></div>
+ }
+}
+
+class TestParent extends React.Component {
+ onContextMenu = () => {
+ console.log("Parent");
+ }
+
+ render() {
+ return <div onContextMenu={this.onContextMenu}><TestChild /></div>
+ }
+}
+
+ReactDOM.render((
+ <div style={{ position: "absolute", width: "100%", height: "100%" }}>
+ <TestParent />
+ </div>),
+ document.getElementById('root')
+); \ No newline at end of file
diff --git a/src/fields/Document.ts b/src/fields/Document.ts
index 6193ea56c..2e873439c 100644
--- a/src/fields/Document.ts
+++ b/src/fields/Document.ts
@@ -8,6 +8,7 @@ import { ListField } from "./ListField";
import { Server } from "../client/Server";
import { Types } from "../server/Message";
import { UndoManager } from "../client/util/UndoManager";
+import { HtmlField } from "./HtmlField";
export class Document extends Field {
public fields: ObservableMap<string, { key: Key, field: Field }> = new ObservableMap();
@@ -37,6 +38,22 @@ export class Document extends Field {
return this.GetText(KeyStore.Title, "<untitled>");
}
+ /**
+ * Get the field in the document associated with the given key. If the
+ * associated field has not yet been filled in from the server, a request
+ * to the server will automatically be sent, the value will be filled in
+ * when the request is completed, and {@link Field.ts#FieldWaiting} will be returned.
+ * @param key - The key of the value to get
+ * @param ignoreProto - If true, ignore any prototype this document
+ * might have and only search for the value on this immediate document.
+ * If false (default), search up the prototype chain, starting at this document,
+ * for a document that has a field associated with the given key, and return the first
+ * one found.
+ *
+ * @returns If the document does not have a field associated with the given key, returns `undefined`.
+ * If the document does have an associated field, but the field has not been fetched from the server, returns {@link Field.ts#FieldWaiting}.
+ * If the document does have an associated field, and the field has not been fetched from the server, returns the associated field.
+ */
Get(key: Key, ignoreProto: boolean = false): FieldValue<Field> {
let field: FieldValue<Field>;
if (ignoreProto) {
@@ -92,7 +109,17 @@ export class Document extends Field {
return field;
}
+ /**
+ * Tries to get the field associated with the given key, and if there is an
+ * associated field, calls the given callback with that field.
+ * @param key - The key of the value to get
+ * @param callback - A function that will be called with the associated field, if it exists,
+ * once it is fetched from the server (this may be immediately if the field has already been fetched).
+ * Note: The callback will not be called if there is no associated field.
+ * @returns `true` if the field exists on the document and `callback` will be called, and `false` otherwise
+ */
GetAsync(key: Key, callback: (field: Field) => void): boolean {
+ //TODO: This should probably check if this.fields contains the key before calling Server.GetDocumentField
//This currently doesn't deal with prototypes
if (this._proxies.has(key.Id)) {
Server.GetDocumentField(this, key, callback);
@@ -101,6 +128,38 @@ export class Document extends Field {
return false;
}
+ /**
+ * Same as {@link Document#GetAsync}, except a field of the given type
+ * will be created if there is no field associated with the given key,
+ * or the field associated with the given key is not of the given type.
+ * @param ctor - Constructor of the field type to get. E.g., TextField, ImageField, etc.
+ */
+ GetOrCreateAsync<T extends Field>(key: Key, ctor: { new(): T }, callback: (field: T) => void): void {
+ //This currently doesn't deal with prototypes
+ if (this._proxies.has(key.Id)) {
+ Server.GetDocumentField(this, key, (field) => {
+ if (field && field instanceof ctor) {
+ callback(field);
+ } else {
+ let newField = new ctor();
+ this.Set(key, newField);
+ callback(newField);
+ }
+ });
+ } else {
+ let newField = new ctor();
+ this.Set(key, newField);
+ callback(newField);
+ }
+ }
+
+ /**
+ * Same as {@link Document#Get}, except that it will additionally
+ * check if the field is of the given type.
+ * @param ctor - Constructor of the field type to get. E.g., `TextField`, `ImageField`, etc.
+ * @returns Same as {@link Document#Get}, except will return `undefined`
+ * if there is an associated field but it is of the wrong type.
+ */
GetT<T extends Field = Field>(key: Key, ctor: { new(...args: any[]): T }, ignoreProto: boolean = false): FieldValue<T> {
var getfield = this.Get(key, ignoreProto);
if (getfield != FieldWaiting) {
@@ -125,6 +184,10 @@ export class Document extends Field {
return vval;
}
+ GetHtml(key: Key, defaultVal: string): string {
+ return this.GetData(key, HtmlField, defaultVal);
+ }
+
GetNumber(key: Key, defaultVal: number): number {
return this.GetData(key, NumberField, defaultVal);
}
diff --git a/src/fields/KVPField b/src/fields/KVPField
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/fields/KVPField
diff --git a/src/fields/KVPField.ts b/src/fields/KVPField.ts
new file mode 100644
index 000000000..a7ecc0768
--- /dev/null
+++ b/src/fields/KVPField.ts
@@ -0,0 +1,30 @@
+import { BasicField } from "./BasicField"
+import { FieldId } from "./Field";
+import { Types } from "../server/Message";
+import { Document } from "./Document"
+
+export class KVPField extends BasicField<Document> {
+ constructor(data: Document | undefined = undefined, id?: FieldId, save: boolean = true) {
+ super(data == undefined ? new Document() : data, save, id);
+ }
+
+ toString(): string {
+ return this.Data.Title;
+ }
+
+ ToScriptString(): string {
+ return `new KVPField("${this.Data}")`;
+ }
+
+ Copy() {
+ return new KVPField(this.Data);
+ }
+
+ ToJson(): { type: Types, data: Document, _id: string } {
+ return {
+ type: Types.Text,
+ data: this.Data,
+ _id: this.Id
+ }
+ }
+} \ No newline at end of file
diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts
index 662574396..a3b39735d 100644
--- a/src/fields/KeyStore.ts
+++ b/src/fields/KeyStore.ts
@@ -26,7 +26,4 @@ export namespace KeyStore {
export const Caption = new Key("Caption");
export const ActiveFrame = new Key("ActiveFrame");
export const DocumentText = new Key("DocumentText");
- //determines whether doc views will be selected when they are first loaded
- //currently only implemented for FormattedTextView
- export const SelectOnLoaded = new Key("SelectOnLoaded");
}
diff --git a/src/fields/WebField.ts b/src/fields/WebField.ts
new file mode 100644
index 000000000..8f945d686
--- /dev/null
+++ b/src/fields/WebField.ts
@@ -0,0 +1,30 @@
+import { BasicField } from "./BasicField";
+import { Field, FieldId } from "./Field";
+import { Types } from "../server/Message";
+
+export class WebField extends BasicField<URL> {
+ constructor(data: URL | undefined = undefined, id?: FieldId, save: boolean = true) {
+ super(data == undefined ? new URL("https://crossorigin.me/" + "https://cs.brown.edu/") : data, save, id);
+ }
+
+ toString(): string {
+ return this.Data.href;
+ }
+
+ ToScriptString(): string {
+ return `new WebField("${this.Data}")`;
+ }
+
+ Copy(): Field {
+ return new WebField(this.Data);
+ }
+
+ ToJson(): { type: Types, data: URL, _id: string } {
+ return {
+ type: Types.Web,
+ data: this.Data,
+ _id: this.Id
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/server/Message.ts b/src/server/Message.ts
index 80fc9a80d..148e6e723 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -45,7 +45,7 @@ export class GetFieldArgs {
}
export enum Types {
- Number, List, Key, Image, Document, Text, RichText, DocumentReference, Html
+ Number, List, Key, Image, Web, Document, Text, RichText, DocumentReference, Html
}
export class DocumentTransfer implements Transferable {
diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts
index 08e72fdae..a53fb5d2b 100644
--- a/src/server/ServerUtil.ts
+++ b/src/server/ServerUtil.ts
@@ -10,6 +10,7 @@ import { Server } from './../client/Server';
import { Types } from './Message';
import { Utils } from '../Utils';
import { HtmlField } from '../fields/HtmlField';
+import { WebField } from '../fields/WebField';
export class ServerUtils {
public static FromJson(json: any): Field {
@@ -30,6 +31,8 @@ export class ServerUtils {
return new TextField(data, id, false)
case Types.Html:
return new HtmlField(data, id, false)
+ case Types.Web:
+ return new WebField(new URL(data), id, false)
case Types.RichText:
return new RichTextField(data, id, false)
case Types.Key:
diff --git a/src/server/index.ts b/src/server/index.ts
index eb0527ee7..4c2e09661 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -85,6 +85,10 @@ app.get("/hello", (req, res) => {
res.send("<p>Hello</p>");
})
+app.use("/corsProxy", (req, res) => {
+ req.pipe(request(req.url.substring(1))).pipe(res);
+});
+
app.get("/delete", (req, res) => {
deleteAll();
res.redirect("/");
@@ -125,7 +129,6 @@ function deleteAll() {
function barReceived(guid: String) {
clients[guid.toString()] = new Client(guid.toString());
- // Database.Instance.print()
}
function addDocument(document: Document) {
diff --git a/tsconfig.json b/tsconfig.json
index 823fc2732..e2538bc39 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -11,9 +11,5 @@
"dom",
"es2015"
]
- },
- "exclude": [
- "node_modules",
- "static"
- ]
+ }
} \ No newline at end of file
diff --git a/webpack.config.js b/webpack.config.js
index 2a29844e8..900802d7d 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -7,6 +7,7 @@ module.exports = {
entry: {
bundle: ["./src/client/views/Main.tsx", 'webpack-hot-middleware/client?reload=true'],
viewer: ["./src/debug/Viewer.tsx", 'webpack-hot-middleware/client?reload=true'],
+ test: ["./src/debug/Test.tsx", 'webpack-hot-middleware/client?reload=true'],
},
devtool: "source-map",
node: {