aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSam Wilkins <abdullah_ahmed@brown.edu>2019-03-02 17:43:55 -0500
committerSam Wilkins <abdullah_ahmed@brown.edu>2019-03-02 17:43:55 -0500
commit950c656d06d7c6b25b82030feaa7c5c70102fe6a (patch)
treef5ff6112a6d0c34c225479e94a85450559b3f769 /src
parentbf26137803c09807b6e7e3d9d9ad086a7ead32ac (diff)
parentbde8aabad7e5745b4797e73b564e4efb19faeca9 (diff)
Merge with master and initial pass at converting main to a component
Diffstat (limited to 'src')
-rw-r--r--src/.DS_Storebin0 -> 6148 bytes
-rw-r--r--src/client/documents/Documents.ts247
-rw-r--r--src/client/util/DragManager.ts75
-rw-r--r--src/client/util/Transform.ts6
-rw-r--r--src/client/views/ContextMenu.tsx5
-rw-r--r--src/client/views/DocumentDecorations.tsx23
-rw-r--r--src/client/views/Main.scss22
-rw-r--r--src/client/views/Main.tsx303
-rw-r--r--src/client/views/collections/CollectionDockingView.scss4
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx59
-rw-r--r--src/client/views/collections/CollectionFreeFormView.scss31
-rw-r--r--src/client/views/collections/CollectionFreeFormView.tsx165
-rw-r--r--src/client/views/collections/CollectionSchemaView.scss37
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx165
-rw-r--r--src/client/views/collections/CollectionTreeView.scss34
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx86
-rw-r--r--src/client/views/collections/CollectionView.tsx39
-rw-r--r--src/client/views/collections/CollectionViewBase.tsx22
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx33
-rw-r--r--src/client/views/nodes/DocumentView.tsx79
-rw-r--r--src/client/views/nodes/FieldView.tsx26
-rw-r--r--src/client/views/nodes/FormattedTextBox.scss6
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx30
-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/debug/Viewer.tsx2
-rw-r--r--src/fields/Document.ts28
-rw-r--r--src/fields/Field.ts3
-rw-r--r--src/fields/KVPField0
-rw-r--r--src/fields/KVPField.ts30
-rw-r--r--src/fields/Key.ts1
-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.ts6
41 files changed, 1259 insertions, 649 deletions
diff --git a/src/.DS_Store b/src/.DS_Store
new file mode 100644
index 000000000..4d6acb95a
--- /dev/null
+++ b/src/.DS_Store
Binary files differ
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2dfff6235..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,111 +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.NativeHeight, new NumberField(300));
- imageProto.Set(KeyStore.Width, new NumberField(300));
- imageProto.Set(KeyStore.Height, 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() {
@@ -132,66 +149,14 @@ export namespace Documents {
<div style="position:relative; margin:auto; height:85%;" >`
+ ImageBox.LayoutString() +
`</div>
- <div style="position:relative; overflow:auto; height:15%; text-align:center; ">`
+ <div style="position:relative; height:15%; text-align:center; ">`
+ FormattedTextBox.LayoutString("CaptionKey") +
`</div>
</div>` };
function FixedCaption() {
return `<div style="position:absolute; height:30px; bottom:0; width:100%">
- <div style="position:absolute; width:100%; height:100%; overflow:auto;text-align:center;bottom:0;">`
+ <div style="position:absolute; width:100%; height:100%; text-align:center;bottom:0;">`
+ 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]));
-
- let annotation = Documents.TextDocument({ title: "hello" });
- doc.Set(KeyStore.Annotations, new ListField([annotation]));
- 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 eb4b3aeaa..60910a40b 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,4 +1,37 @@
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>, 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: docFunc() });
+ });
+ let onRowUp = action((e: PointerEvent): void => {
+ document.removeEventListener("pointermove", onRowMove);
+ document.removeEventListener('pointerup', onRowUp);
+ });
+ let onItemDown = (e: React.PointerEvent) => {
+ // if (this.props.isSelected() || this.props.isTopMost) {
+ if (e.button == 0) {
+ e.stopPropagation();
+ if (e.shiftKey) {
+ CollectionDockingView.Instance.StartOtherDrag(docFunc(), e);
+ } else {
+ document.addEventListener("pointermove", onRowMove);
+ document.addEventListener('pointerup', onRowUp);
+ }
+ }
+ //}
+ }
+ return onItemDown;
+}
export namespace DragManager {
export function Root() {
@@ -61,7 +94,7 @@ export namespace DragManager {
};
}
- export function StartDrag(ele: HTMLElement, dragData: { [id: string]: any }, options: DragOptions) {
+ export function StartDrag(ele: HTMLElement, dragData: { [id: string]: any }, options?: DragOptions) {
DocumentDecorations.Instance.Hidden = true;
if (!dragDiv) {
dragDiv = document.createElement("div");
@@ -75,6 +108,8 @@ export namespace DragManager {
let dragElement = ele.cloneNode(true) as HTMLElement;
dragElement.style.opacity = "0.7";
dragElement.style.position = "absolute";
+ dragElement.style.bottom = "";
+ dragElement.style.left = "";
dragElement.style.transformOrigin = "0 0";
dragElement.style.zIndex = "1000";
dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
@@ -87,38 +122,54 @@ export namespace DragManager {
dragDiv.appendChild(dragElement);
let hideSource = false;
- if (typeof options.hideSource === "boolean") {
- hideSource = options.hideSource;
- } else {
- hideSource = options.hideSource();
+ if (options) {
+ if (typeof options.hideSource === "boolean") {
+ hideSource = options.hideSource;
+ } else {
+ hideSource = options.hideSource();
+ }
}
const wasHidden = ele.hidden;
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, options, dragData);
+ 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, options: DragOptions, dragData: { [index: string]: any }) {
- dragDiv.removeChild(dragEle);
+ function FinishDrag(dragEle: HTMLElement, e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions) {
+ 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;
}
@@ -130,7 +181,9 @@ export namespace DragManager {
data: dragData
}
}));
- options.handlers.dragComplete({});
+ if (options) {
+ options.handlers.dragComplete({});
+ }
DocumentDecorations.Instance.Hidden = false;
}
} \ No newline at end of file
diff --git a/src/client/util/Transform.ts b/src/client/util/Transform.ts
index 9fd4f7bef..3e1039166 100644
--- a/src/client/util/Transform.ts
+++ b/src/client/util/Transform.ts
@@ -102,6 +102,12 @@ export class Transform {
return [x * this._scale, y * this._scale];
}
+ transformBounds(x: number, y: number, width: number, height: number): { x: number, y: number, width: number, height: number } {
+ [x, y] = this.transformPoint(x, y);
+ [width, height] = this.transformDirection(width, height);
+ return { x, y, width, height };
+ }
+
inverse = () => {
return new Transform(-this._translateX / this._scale, -this._translateY / this._scale, 1 / this._scale)
}
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 4f26a75d2..9459d45f8 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"
@@ -23,11 +23,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,6 +40,7 @@ export class ContextMenu extends React.Component {
return this._items;
}
+ @action
displayMenu(x: number, y: number) {
this._pageX = x
this._pageY = y
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 2f012913d..975a125f7 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -104,25 +104,26 @@ export class DocumentDecorations extends React.Component {
const rect = element.screenRect();
if (rect.width !== 0) {
let doc = element.props.Document;
- let width = doc.GetOrCreate(KeyStore.Width, NumberField);
- let height = doc.GetOrCreate(KeyStore.Height, NumberField);
+ let width = doc.GetNumber(KeyStore.Width, 0);
+ let nwidth = doc.GetNumber(KeyStore.NativeWidth, 0);
+ let nheight = doc.GetNumber(KeyStore.NativeHeight, 0);
+ let height = doc.GetNumber(KeyStore.Height, nwidth ? nheight / nwidth * width : 0);
let x = doc.GetOrCreate(KeyStore.X, NumberField);
let y = doc.GetOrCreate(KeyStore.Y, NumberField);
- let scale = width.Data / rect.width;
- let actualdW = Math.max(width.Data + (dW * scale), 20);
- let actualdH = Math.max(height.Data + (dH * scale), 20);
- x.Data += dX * (actualdW - width.Data);
- y.Data += dY * (actualdH - height.Data);
+ let scale = width / rect.width;
+ let actualdW = Math.max(width + (dW * scale), 20);
+ let actualdH = Math.max(height + (dH * scale), 20);
+ x.Data += dX * (actualdW - width);
+ y.Data += dY * (actualdH - height);
var nativeWidth = doc.GetNumber(KeyStore.NativeWidth, 0);
var nativeHeight = doc.GetNumber(KeyStore.NativeHeight, 0);
if (nativeWidth > 0 && nativeHeight > 0) {
if (Math.abs(dW) > Math.abs(dH))
actualdH = nativeHeight / nativeWidth * actualdW;
- else
- actualdW = nativeWidth / nativeHeight * actualdH;
+ else actualdW = nativeWidth / nativeHeight * actualdH;
}
- width.Data = actualdW;
- height.Data = actualdH;
+ doc.SetNumber(KeyStore.Width, actualdW);
+ doc.SetNumber(KeyStore.Height, actualdH);
}
})
}
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 db7138965..88bf0934c 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -1,216 +1,143 @@
-import { action, configure, reaction, computed, observable } from 'mobx';
+import { action, configure, observable } 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 { Utils } from '../../Utils';
import { ServerUtils } from '../../server/ServerUtil';
import { MessageStore, DocumentTransfer } from '../../server/Message';
import { Database } from '../../server/database';
import * as request from 'request'
+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 { WorkspacesMenu } from '../../server/authentication/controllers/WorkspacesMenu';
+import { CollectionDockingView } from './collections/CollectionDockingView';
+import { ContextMenu } from './ContextMenu';
+import { DocumentDecorations } from './DocumentDecorations';
+import { DocumentView } from './nodes/DocumentView';
+import "./Main.scss";
+import { observer } from 'mobx-react';
+@observer
+export class Main extends React.Component {
+ @observable private mainDocId = "mainDoc";
+ // dummy document initializations keep the compiler happy
+ @observable private mainContainer: Document = new Document;
+ @observable private mainfreeform: Document = new Document;
-configure({
- enforceActions: "observed"
-});
-window.addEventListener("drop", function (e) {
- e.preventDefault();
-}, false)
-window.addEventListener("dragover", function (e) {
- e.preventDefault();
-}, false)
-document.addEventListener("pointerdown", action(function (e: PointerEvent) {
- if (!ContextMenu.Instance.intersects(e.pageX, e.pageY)) {
- ContextMenu.Instance.clearItems()
+ constructor(props: Readonly<{}>) {
+ super(props);
+ // causes errors to be generated when modifying an observable outside of an action
+ configure({ enforceActions: "observed" });
+ this.initEventListeners();
+ this.initAuthenticationRouters();
}
-}), true)
-// Load the user's active workspace, or create a new one if initial session after signup
-request.get(window.location.origin + "/getActiveWorkspaceId", (error, response, body) => {
- init(body ? body : getNewWorkspace());
-});
+ initEventListeners = () => {
+ window.addEventListener("drop", (e) => e.preventDefault(), false) // drop event handler
+ window.addEventListener("dragover", (e) => e.preventDefault(), false) // drag event handler
+ // click interactions for the context menu
+ document.addEventListener("pointerdown", action(function (e: PointerEvent) {
+ if (!ContextMenu.Instance.intersects(e.pageX, e.pageY)) {
+ ContextMenu.Instance.clearItems();
+ }
+ }), true);
+ }
-function getNewWorkspace(): string {
- let newId = Utils.GenerateGuid();
- const here = window.location.origin;
- request.post(here + "/addWorkspaceId", {
- body: {
- target: newId
- },
- json: true
- })
- request.post(here + "/setActiveWorkspaceId", {
- body: {
- target: newId
- },
- json: true
- })
- return newId;
-}
+ initAuthenticationRouters = () => {
+ // Load the user's active workspace, or create a new one if initial session after signup
+ request.get(window.location.origin + "/getActiveWorkspaceId", (error, response, body) => {
+ this.initRender(body ? body : this.getNewWorkspace());
+ });
+ }
-//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)
-function init(mainDocId: string) {
- 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);
+ getNewWorkspace = (): string => {
+ let newId = Utils.GenerateGuid();
+ const here = window.location.origin;
+ request.post(here + "/addWorkspaceId", {
+ body: { target: newId },
+ json: true
+ })
+ request.post(here + "/setActiveWorkspaceId", {
+ body: { target: newId },
+ json: true
+ })
+ return newId;
+ }
+
+ initRender = (activeWorkspaceId: string) => {
+ Documents.initProtos(activeWorkspaceId, (res?: Document) => {
+ if (res instanceof Document) {
+ this.mainContainer = res;
+ this.mainContainer.GetAsync(KeyStore.ActiveFrame, field => this.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()))
+ this.mainContainer = Documents.DockDocument(JSON.stringify({ content: [{ type: 'row', content: [] }] }), { title: "main container" }, this.mainDocId);
- 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(() => {
+ this.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);
+ var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(this.mainfreeform)] }] };
+ this.mainContainer.SetText(KeyStore.Data, JSON.stringify(dockingLayout));
+ this.mainContainer.Set(KeyStore.ActiveFrame, this.mainfreeform);
+ }, 0);
}
+ });
+ }
- let addImageNode = action(() => {
- mainfreeform.GetList<Document>(KeyStore.Data, []).push(Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", {
- x: 0, y: 300, width: 200, height: 200, title: "added note"
- }));
- })
- let addTextNode = action(() => {
- mainfreeform.GetList<Document>(KeyStore.Data, []).push(Documents.TextDocument({
- x: 0, y: 300, width: 200, height: 200, title: "added note"
- }));
- })
- let addColNode = action(() => {
- mainfreeform.GetList<Document>(KeyStore.Data, []).push(Documents.FreeformDocument([], {
- x: 0, y: 300, width: 200, height: 200, title: "added note"
- }));
- })
- let addSchemaNode = action(() => {
- mainfreeform.GetList<Document>(KeyStore.Data, []).push(Documents.SchemaDocument([Documents.TextDocument()], {
- x: 0, y: 300, width: 200, height: 200, title: "added note"
- }));
- })
+ render() {
+ 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>();
- let clearDatabase = action(() => {
- Utils.Emit(Server.Socket, MessageStore.DeleteAll, {});
- })
+ 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 feeform 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" }));
- 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 />
- <button style={{
- position: 'absolute',
- bottom: '0px',
- left: '0px',
- width: '150px'
- }} onClick={addImageNode}>Add Image</button>
- <button style={{
- position: 'absolute',
- bottom: '25px',
- left: '0px',
- width: '150px'
- }} onClick={addTextNode}>Add Text</button>
- <button style={{
- position: 'absolute',
- bottom: '50px',
- left: '0px',
- width: '150px'
- }} onClick={addColNode}>Add Collection</button>
- <button style={{
- position: 'absolute',
- bottom: '100',
- left: '0px',
- width: '150px'
- }} onClick={addSchemaNode}>Add Schema</button>
- <button style={{
- position: 'absolute',
- bottom: '75px',
- 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>
- <button style={{
- position: 'absolute',
- top: '24px',
- right: '0px',
- width: '150px'
- }} onClick={() => window.location.pathname = "/logout"}>Logout</button>
- <button style={{
- position: 'absolute',
- top: '24px',
- left: '4px',
- width: '150px'
- }} onClick={() => WorkspacesMenu.Instance.toggle()}>Workspaces</button>
- <WorkspacesMenu load={init} new={getNewWorkspace} active={mainDocId} />
- </div>),
- document.getElementById('root'));
- })
- });
+ let addClick = (creator: () => Document) => action(() => this.mainfreeform.GetList<Document>(KeyStore.Data, []).push(creator()));
+
+ return (
+ <div style={{ position: "absolute", width: "100%", height: "100%" }}>
+ <DocumentView Document={this.mainContainer}
+ AddDocument={undefined} RemoveDocument={undefined} ScreenToLocalTransform={() => Transform.Identity}
+ ContentScaling={() => 1}
+ PanelWidth={() => 0}
+ PanelHeight={() => 0}
+ isTopMost={true}
+ SelectOnLoad={false}
+ 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>
+ <WorkspacesMenu active={this.mainDocId} load={this.initRender} new={this.getNewWorkspace} />
+ </div>
+ );
+ }
}
-// 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(<Main />, document.getElementById('root')); \ No newline at end of file
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index 7c0b512a7..2706c3272 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -1,3 +1,7 @@
+.collectiondockingview-content {
+ height: 100%;
+}
+
.collectiondockingview-container {
position: relative;
top: 0;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index ac8bac22e..40a6213dd 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -10,7 +10,6 @@ import { FieldId, Opt, Field } from "../../../fields/Field";
import { KeyStore } from "../../../fields/KeyStore";
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: HTMLDivElement | undefined;
- private _dragFakeElement: HTMLDivElement | 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: HTMLDivElement, 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 HTMLDivElement;
- 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();
@@ -235,17 +208,17 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
//stack.header.controlsContainer.find('.lm_popout').hide();
stack.header.controlsContainer.find('.lm_close') //get the close icon
.off('click') //unbind the current click handler
- .click(function () {
+ .click(action(function () {
//if (confirm('really close this?')) {
stack.remove();
//}
- });
+ }));
}
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,8 +236,9 @@ 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>;
constructor(props: any) {
@@ -272,8 +246,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
Server.GetField(this.props.documentId, action((f: Opt<Field>) => this._document = f as Document));
}
- private _nativeWidth = () => { return this._document!.GetNumber(KeyStore.NativeWidth, 0); }
- private _nativeHeight = () => { return this._document!.GetNumber(KeyStore.NativeHeight, 0); }
+ private _nativeWidth = () => { return this._document!.GetNumber(KeyStore.NativeWidth, this._panelWidth); }
+ private _nativeHeight = () => { return this._document!.GetNumber(KeyStore.NativeHeight, this._panelHeight); }
private _contentScaling = () => { return this._panelWidth / (this._nativeWidth() ? this._nativeWidth() : this._panelWidth); }
ScreenToLocalTransform = () => {
@@ -294,10 +268,11 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
PanelHeight={this._nativeHeight}
ScreenToLocalTransform={this.ScreenToLocalTransform}
isTopMost={true}
+ SelectOnLoad={false}
ContainingCollectionView={undefined} />
</div>
- return <Measure onResize={action((r: any) => this._panelWidth = r.entry.width)}>
+ return <Measure onResize={action((r: any) => { this._panelWidth = r.entry.width; this._panelHeight = r.entry.height; })}>
{({ measureRef }) => <div ref={measureRef}> {content} </div>}
</Measure>
}
diff --git a/src/client/views/collections/CollectionFreeFormView.scss b/src/client/views/collections/CollectionFreeFormView.scss
index 5f930d135..2ec22367f 100644
--- a/src/client/views/collections/CollectionFreeFormView.scss
+++ b/src/client/views/collections/CollectionFreeFormView.scss
@@ -1,14 +1,7 @@
.collectionfreeformview-container {
- ::-webkit-scrollbar {
- -webkit-appearance: none;
- width: 10px;
- }
- ::-webkit-scrollbar-thumb {
- border-radius: 5px;
- background-color: rgba(0, 0, 0, .5);
- }
- .collectionfreeformview>.jsx-parser {
- position: absolute;
+
+ .collectionfreeformview > .jsx-parser{
+ position:absolute;
height: 100%;
}
border-style: solid;
@@ -26,4 +19,22 @@
width: 100%;
height: 100%
}
+}
+
+.border {
+ border-style: solid;
+ box-sizing: border-box;
+ width: 100%;
+ height: 100%;
+}
+
+//this is an animation for the blinking cursor!
+@keyframes blink {
+ 0% {opacity: 0}
+ 49%{opacity: 0}
+ 50% {opacity: 1}
+}
+
+#prevCursor {
+ animation: blink 1s infinite;
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx
index c40da6eaa..5e9dcd5d5 100644
--- a/src/client/views/collections/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/CollectionFreeFormView.tsx
@@ -1,4 +1,4 @@
-import { action, computed } from "mobx";
+import { observable, action, computed } from "mobx";
import { observer } from "mobx-react";
import { Document } from "../../../fields/Document";
import { FieldWaiting } from "../../../fields/Field";
@@ -13,14 +13,15 @@ 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";
-import React = require("react");
import { Documents } from "../../documents/Documents";
+import React = require("react");
const JsxParser = require('react-jsx-parser').default;//TODO Why does this need to be imported like this?
@observer
@@ -28,9 +29,16 @@ 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;
+ @observable
private _downY: number = 0;
+ //determines whether the blinking cursor for indicating whether a text will be made on key down is visible
+ @observable
+ private _previewCursorVisible: boolean = false;
@computed get panX(): number { return this.props.Document.GetNumber(KeyStore.PanX, 0) }
@computed get panY(): number { return this.props.Document.GetNumber(KeyStore.PanY, 0) }
@@ -39,21 +47,20 @@ export class CollectionFreeFormView extends CollectionViewBase {
@computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
@computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
@computed get zoomScaling() { return this.props.Document.GetNumber(KeyStore.Scale, 1); }
+ @computed get centeringShiftX() { return !this.props.Document.GetNumber(KeyStore.NativeWidth, 0) ? this.props.panelWidth() / 2 : 0; } // shift so pan position is at center of window for non-overlay collections
+ @computed get centeringShiftY() { return !this.props.Document.GetNumber(KeyStore.NativeHeight, 0) ? this.props.panelHeight() / 2 : 0; }// shift so pan position is at center of window for non-overlay collections
@undoBatch
@action
drop = (e: Event, de: DragManager.DropEvent) => {
super.drop(e, de);
- const doc: DocumentView = de.data["document"];
- const xOffset = de.data["xOffset"] as number || 0;
- const yOffset = de.data["yOffset"] as number || 0;
- //this should be able to use translate and scale methods on an Identity transform, no?
- const transform = this.getTransform();
- const screenX = de.x - xOffset;
- const screenY = de.y - yOffset;
- const [x, y] = transform.transformPoint(screenX, screenY);
- doc.props.Document.SetNumber(KeyStore.X, x);
- doc.props.Document.SetNumber(KeyStore.Y, y);
+ const docView: DocumentView = de.data["documentView"];
+ let doc: Document = docView ? docView.props.Document : de.data["document"];
+ let screenX = de.x - (de.data["xOffset"] as number || 0);
+ let screenY = de.y - (de.data["yOffset"] as number || 0);
+ const [x, y] = this.getTransform().transformPoint(screenX, screenY);
+ doc.SetNumber(KeyStore.X, x);
+ doc.SetNumber(KeyStore.Y, y);
this.bringToFront(doc);
}
@@ -65,8 +72,10 @@ export class CollectionFreeFormView extends CollectionViewBase {
document.addEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
document.addEventListener("pointerup", this.onPointerUp);
- this._downX = this._lastX = e.pageX;
- this._downY = this._lastY = e.pageY;
+ this._lastX = e.pageX;
+ this._lastY = e.pageY;
+ this._downX = e.pageX;
+ this._downY = e.pageY;
}
}
@@ -76,22 +85,25 @@ export class CollectionFreeFormView extends CollectionViewBase {
document.removeEventListener("pointerup", this.onPointerUp);
e.stopPropagation();
if (Math.abs(this._downX - e.clientX) < 3 && Math.abs(this._downY - e.clientY) < 3) {
+ //show preview text cursor on tap
+ this._previewCursorVisible = true;
+ //select is not already selected
if (!this.props.isSelected()) {
this.props.select(false);
}
}
+
}
@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);
-
- this.SetPan(x + dx, y + dy);
+ let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
+ this._previewCursorVisible = false;
+ this.SetPan(x - dx, y - dy);
}
this._lastX = e.pageX;
this._lastY = e.pageY;
@@ -102,35 +114,46 @@ export class CollectionFreeFormView extends CollectionViewBase {
e.stopPropagation();
e.preventDefault();
let coefficient = 1000;
- // if (modes[e.deltaMode] == 'pixels') coefficient = 50;
- // else if (modes[e.deltaMode] == 'lines') coefficient = 1000; // This should correspond to line-height??
- let transform = this.getTransform();
- let deltaScale = (1 - (e.deltaY / coefficient));
- let [x, y] = transform.transformPoint(e.clientX, e.clientY);
+ if (e.ctrlKey) {
+ var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
+ var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0);
+ const coefficient = 1000;
+ let deltaScale = (1 - (e.deltaY / coefficient));
+ this.props.Document.SetNumber(KeyStore.NativeWidth, nativeWidth * deltaScale);
+ this.props.Document.SetNumber(KeyStore.NativeHeight, nativeHeight * deltaScale);
+ e.stopPropagation();
+ e.preventDefault();
+ } else {
+ // if (modes[e.deltaMode] == 'pixels') coefficient = 50;
+ // else if (modes[e.deltaMode] == 'lines') coefficient = 1000; // This should correspond to line-height??
+ let transform = this.getTransform();
+
+ let deltaScale = (1 - (e.deltaY / coefficient));
+ if (deltaScale * this.zoomScaling < 1 && this.isAnnotationOverlay)
+ deltaScale = 1 / this.zoomScaling;
+ let [x, y] = transform.transformPoint(e.clientX, e.clientY);
- let localTransform = this.getLocalTransform();
- localTransform = localTransform.inverse().scaleAbout(deltaScale, x, y)
+ 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.props.Document.SetNumber(KeyStore.Scale, localTransform.Scale);
+ this.SetPan(-localTransform.TranslateX / localTransform.Scale, -localTransform.TranslateY / localTransform.Scale);
+ }
}
@action
private SetPan(panX: number, panY: number) {
const newPanX = Math.max((1 - this.zoomScaling) * this.nativeWidth, Math.min(0, panX));
const newPanY = Math.max((1 - this.zoomScaling) * this.nativeHeight, Math.min(0, panY));
- this.props.Document.SetNumber(KeyStore.PanX, false && this.isAnnotationOverlay ? newPanX : panX);
- this.props.Document.SetNumber(KeyStore.PanY, false && this.isAnnotationOverlay ? newPanY : panY);
+ this.props.Document.SetNumber(KeyStore.PanX, this.isAnnotationOverlay ? newPanX : panX);
+ this.props.Document.SetNumber(KeyStore.PanY, this.isAnnotationOverlay ? newPanY : panY);
}
@action
onDrop = (e: React.DragEvent): void => {
- const panx: number = this.props.Document.GetNumber(KeyStore.PanX, 0);
- const pany: number = this.props.Document.GetNumber(KeyStore.PanY, 0);
- let transform = this.getTransform();
-
- var pt = transform.transformPoint(e.pageX, e.pageY);
+ var pt = this.getTransform().transformPoint(e.pageX, e.pageY);
super.onDrop(e, { x: pt[0], y: pt[1] });
}
@@ -138,15 +161,33 @@ export class CollectionFreeFormView extends CollectionViewBase {
}
@action
- bringToFront(doc: DocumentView) {
+ 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 (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);
+ //remove cursor from screen
+ this._previewCursorVisible = false;
+ }
+ }
+ }
+
+ @action
+ bringToFront(doc: Document) {
const { fieldKey: fieldKey, Document: Document } = this.props;
const value: Document[] = Document.GetList<Document>(fieldKey, []).slice();
value.sort((doc1, doc2) => {
- if (doc1 === doc.props.Document) {
+ if (doc1 === doc) {
return 1;
}
- if (doc2 === doc.props.Document) {
+ if (doc2 === doc) {
return -1;
}
return doc1.GetNumber(KeyStore.ZIndex, 0) - doc2.GetNumber(KeyStore.ZIndex, 0);
@@ -155,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>") {
@@ -170,15 +210,15 @@ export class CollectionFreeFormView extends CollectionViewBase {
}
@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}
+ return (<CollectionFreeFormDocumentView key={doc.Id} Document={doc} ref={focus}
AddDocument={this.props.addDocument}
RemoveDocument={this.props.removeDocument}
ScreenToLocalTransform={this.getTransform}
isTopMost={false}
+ SelectOnLoad={doc.Id === this._selectOnLoaded}
ContentScaling={this.noScaling}
PanelWidth={doc.Width}
PanelHeight={doc.Height}
@@ -192,7 +232,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}
@@ -203,33 +243,58 @@ 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}
onError={(test: any) => console.log(test)}
/>);
}
- getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).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 => {
+ this._previewCursorVisible = false;
+ }
+
render() {
- const panx: number = this.props.Document.GetNumber(KeyStore.PanX, 0);
- const pany: number = this.props.Document.GetNumber(KeyStore.PanY, 0);
+
+ //determines whether preview text cursor should be visible (ie when user taps this collection it should)
+ let cursor = null;
+ if (this._previewCursorVisible) {
+ //get local position and place cursor there!
+ let [x, y] = this.getTransform().transformPoint(this._downX, this._downY);
+ cursor = <div id="prevCursor" onKeyPress={this.onKeyDown} style={{ color: "black", position: "absolute", transformOrigin: "left top", transform: `translate(${x}px, ${y}px)` }}>I</div>
+ }
+
+ 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);
+ // const panx: number = this.props.Document.GetNumber(KeyStore.PanX, 0) + this.centeringShiftX;
+ // const pany: number = this.props.Document.GetNumber(KeyStore.PanY, 0) + this.centeringShiftY;
+ console.log("center:", this.getLocalTransform().transformPoint(this.centeringShiftX, this.centeringShiftY));
+
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}
style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px`, }}
+ 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}
{this.views}
</div>
{this.overlayView}
diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss
index b02d528a0..88a3b73d4 100644
--- a/src/client/views/collections/CollectionSchemaView.scss
+++ b/src/client/views/collections/CollectionSchemaView.scss
@@ -4,13 +4,35 @@
position: absolute;
width: 100%;
height: 100%;
- ::-webkit-scrollbar {
- -webkit-appearance: none;
- width: 10px;
+ .collectionSchemaView-previewRegion {
+ position: relative;
+ background: black;
+ float: left;
+ height: 100%;
+ }
+ .collectionSchemaView-previewHandle {
+ position: absolute;
+ height: 37px;
+ width: 20px;
+ z-index: 20;
+ right: 0;
+ top: 0;
+ background: Black ;
+ }
+ .collectionSchemaView-dividerDragger{
+ position: relative;
+ background: black;
+ float: left;
+ height: 100%;
}
::-webkit-scrollbar-thumb {
border-radius: 5px;
background-color: rgba(0, 0, 0, .5);
+ }
+ .collectionSchemaView-tableContainer {
+ position: relative;
+ float: left;
+ height: 100%;
}
.ReactTable {
position: absolute; // display: inline-block;
@@ -33,6 +55,7 @@
}
.rt-tr-group {
direction: ltr;
+ max-height: 44px;
}
.rt-td {
border-width: 1;
@@ -68,11 +91,11 @@
direction: ltr;
}
.ReactTable .rt-thead.-header {
- background: grey;
+ background:grey;
}
- .ReactTable .rt-th,
- .ReactTable .rt-td {
- max-height: 75px;
+ .ReactTable .rt-th, .ReactTable .rt-td {
+ max-height: 44;
+ padding: 3px 7px;
}
.ReactTable .rt-tbody .rt-tr-group:last-child {
border-bottom: grey;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index dc952ef82..5bcd501cc 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -15,9 +15,11 @@ import { FieldView, FieldViewProps } from "../nodes/FieldView";
import "./CollectionSchemaView.scss";
import { COLLECTION_BORDER_WIDTH } from "./CollectionView";
import { CollectionViewBase } from "./CollectionViewBase";
+import { setupDrag } from "../../util/DragManager";
// bcz: need to add drag and drop of rows and columns. This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657
+
@observer
export class CollectionSchemaView extends CollectionViewBase {
private _mainCont = React.createRef<HTMLDivElement>();
@@ -37,36 +39,44 @@ 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);
return (
- <EditableView contents={contents} height={36} GetValue={() => {
- let field = props.doc.Get(props.fieldKey);
- if (field && field instanceof Field) {
- return field.ToScriptString();
- }
- return field || "";
- }} SetValue={(value: string) => {
- let script = CompileScript(value);
- if (!script.compiled) {
- return false;
- }
- let field = script();
- if (field instanceof Field) {
- props.doc.Set(props.fieldKey, field);
- return true;
- } else {
- let dataField = ToField(field);
- if (dataField) {
- props.doc.Set(props.fieldKey, dataField);
- return true;
- }
- }
- return false;
- }}></EditableView>
+ <div onPointerDown={onItemDown} key={props.doc.Id} ref={reference}>
+ <EditableView contents={contents}
+ height={36} GetValue={() => {
+ let field = props.doc.Get(props.fieldKey);
+ if (field && field instanceof Field) {
+ return field.ToScriptString();
+ }
+ return field || "";
+ }}
+ SetValue={(value: string) => {
+ let script = CompileScript(value);
+ if (!script.compiled) {
+ return false;
+ }
+ let field = script();
+ if (field instanceof Field) {
+ props.doc.Set(props.fieldKey, field);
+ return true;
+ } else {
+ let dataField = ToField(field);
+ if (dataField) {
+ props.doc.Set(props.fieldKey, dataField);
+ return true;
+ }
+ }
+ return false;
+ }}>
+ </EditableView>
+ </div>
)
}
@@ -85,27 +95,55 @@ export class CollectionSchemaView extends CollectionViewBase {
}
}),
style: {
- background: rowInfo.index == this._selectedIndex ? "#00afec" : "white",
- color: rowInfo.index == this._selectedIndex ? "white" : "black"
+ background: rowInfo.index == this._selectedIndex ? "lightGray" : "white",
+ //color: rowInfo.index == this._selectedIndex ? "white" : "black"
}
};
}
+ _startSplitPercent = 0;
@action
onDividerMove = (e: PointerEvent): void => {
let nativeWidth = this._mainCont.current!.getBoundingClientRect();
this._splitPercentage = Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100);
}
+ @action
onDividerUp = (e: PointerEvent): void => {
document.removeEventListener("pointermove", this.onDividerMove);
document.removeEventListener('pointerup', this.onDividerUp);
+ if (this._startSplitPercent == this._splitPercentage) {
+ this._splitPercentage = this._splitPercentage == 1 ? 66 : 100;
+ }
}
onDividerDown = (e: React.PointerEvent) => {
+ this._startSplitPercent = this._splitPercentage;
e.stopPropagation();
e.preventDefault();
document.addEventListener("pointermove", this.onDividerMove);
document.addEventListener('pointerup', this.onDividerUp);
}
+ @action
+ onExpanderMove = (e: PointerEvent): void => {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ @action
+ onExpanderUp = (e: PointerEvent): void => {
+ e.stopPropagation();
+ e.preventDefault();
+ document.removeEventListener("pointermove", this.onExpanderMove);
+ document.removeEventListener('pointerup', this.onExpanderUp);
+ if (this._startSplitPercent == this._splitPercentage) {
+ this._splitPercentage = this._splitPercentage == 100 ? 66 : 100;
+ }
+ }
+ onExpanderDown = (e: React.PointerEvent) => {
+ this._startSplitPercent = this._splitPercentage;
+ e.stopPropagation();
+ e.preventDefault();
+ document.addEventListener("pointermove", this.onExpanderMove);
+ document.addEventListener('pointerup', this.onExpanderUp);
+ }
onPointerDown = (e: React.PointerEvent) => {
// if (e.button === 2 && this.active) {
@@ -113,8 +151,10 @@ export class CollectionSchemaView extends CollectionViewBase {
// e.preventDefault();
// } else
{
- if (e.buttons === 1 && this.props.active()) {
- e.stopPropagation();
+ if (e.buttons === 1) {
+ if (this.props.isSelected()) {
+ e.stopPropagation();
+ }
}
}
}
@@ -146,6 +186,7 @@ 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}
@@ -155,40 +196,42 @@ export class CollectionSchemaView extends CollectionViewBase {
}
</Measure>
)
+ let previewHandle = !this.props.active() ? (null) : (
+ <div className="collectionSchemaView-previewHandle" onPointerDown={this.onExpanderDown} />);
return (
- <div onPointerDown={this.onPointerDown} ref={this._mainCont} className="collectionSchemaView-container" style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} >
- <Measure onResize={action((r: any) => {
- this._dividerX = r.entry.width;
- this._panelHeight = r.entry.height;
- })}>
- {({ measureRef }) =>
- <div ref={measureRef} className="collectionSchemaView-tableContainer" style={{ position: "relative", float: "left", width: `${this._splitPercentage}%`, height: "100%" }}>
- <ReactTable
- data={children}
- pageSize={children.length}
- page={0}
- showPagination={false}
- columns={columns.map(col => ({
- Header: col.Name,
- accessor: (doc: Document) => [doc, col],
- id: col.Id
- }))}
- column={{
- ...ReactTableDefaults.column,
- Cell: this.renderCell,
+ <div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} ref={this._mainCont} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} >
+ <div className="collectionSchemaView-dropTarget" onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}>
+ <Measure onResize={action((r: any) => {
+ this._dividerX = r.entry.width;
+ this._panelHeight = r.entry.height;
+ })}>
+ {({ measureRef }) =>
+ <div ref={measureRef} className="collectionSchemaView-tableContainer" style={{ width: `${this._splitPercentage}%` }}>
+ <ReactTable
+ data={children}
+ pageSize={children.length}
+ page={0}
+ showPagination={false}
+ columns={columns.map(col => ({
+ Header: col.Name,
+ accessor: (doc: Document) => [doc, col],
+ id: col.Id
+ }))}
+ column={{
+ ...ReactTableDefaults.column,
+ Cell: this.renderCell,
- }}
- getTrProps={this.getTrProps}
- />
- </div>
- }
- </Measure>
- <div className="collectionSchemaView-dividerDragger" style={{ position: "relative", background: "black", float: "left", width: `${this.DIVIDER_WIDTH}px`, height: "100%" }} onPointerDown={this.onDividerDown} />
- <div className="collectionSchemaView-previewRegion"
- onDrop={(e: React.DragEvent) => this.onDrop(e, {})}
- ref={this.createDropTarget}
- style={{ position: "relative", float: "left", width: `calc(${100 - this._splitPercentage}% - ${this.DIVIDER_WIDTH}px)`, height: "100%" }}>
- {content}
+ }}
+ getTrProps={this.getTrProps}
+ />
+ </div>
+ }
+ </Measure>
+ <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} style={{ width: `${this.DIVIDER_WIDTH}px` }} />
+ <div className="collectionSchemaView-previewRegion" style={{ width: `calc(${100 - this._splitPercentage}% - ${this.DIVIDER_WIDTH}px)` }}>
+ {content}
+ </div>
+ {previewHandle}
</div>
</div >
)
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
new file mode 100644
index 000000000..c488e2894
--- /dev/null
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -0,0 +1,34 @@
+ul {
+ list-style: none;
+}
+
+li {
+ margin: 5px 0;
+}
+
+.no-indent {
+ padding-left: 0;
+}
+
+/* ALL THESE SPACINGS ARE SUPER HACKY RIGHT NOW HANNAH PLS HELP */
+
+li:before {
+ content: '\2014';
+ margin-right: 0.7em;
+}
+
+.collapsed:before {
+ content: '\25b6';
+ margin-right: 0.65em;
+}
+
+.uncollapsed:before {
+ content: '\25bc';
+ margin-right: 0.5em;
+}
+
+.collectionTreeView-dropTarget {
+ border-style: solid;
+ box-sizing: border-box;
+ height:100%;
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
new file mode 100644
index 000000000..55c804337
--- /dev/null
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -0,0 +1,86 @@
+import { observer } from "mobx-react";
+import { CollectionViewBase } from "./CollectionViewBase";
+import { Document } from "../../../fields/Document";
+import { KeyStore } from "../../../fields/KeyStore";
+import { ListField } from "../../../fields/ListField";
+import React = require("react")
+import { TextField } from "../../../fields/TextField";
+import { observable, action } from "mobx";
+import "./CollectionTreeView.scss";
+import { setupDrag } from "../../util/DragManager";
+import { FieldWaiting } from "../../../fields/Field";
+import { COLLECTION_BORDER_WIDTH } from "./CollectionView";
+
+export interface TreeViewProps {
+ document: Document;
+}
+
+@observer
+/**
+ * Component that takes in a document prop and a boolean whether it's collapsed or not.
+ */
+class TreeView extends React.Component<TreeViewProps> {
+
+ @observable
+ collapsed: boolean = false;
+
+ /**
+ * 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.
+ */
+ renderChild(childDocument: Document) {
+ let reference = React.createRef<HTMLDivElement>();
+
+ var children = childDocument.GetT<ListField<Document>>(KeyStore.Data, ListField);
+ let title = childDocument.GetT<TextField>(KeyStore.Title, TextField);
+ let onItemDown = setupDrag(reference, () => childDocument);
+
+ 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}
+ </li>
+ </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>)
+ )
+ }
+}
+
+
+@observer
+export class CollectionTreeView extends CollectionViewBase {
+
+ render() {
+ let titleStr = "";
+ let title = this.props.Document.GetT<TextField>(KeyStore.Title, TextField);
+ if (title && title !== FieldWaiting) {
+ titleStr = title.Data;
+ }
+ return (
+ <div className="collectionTreeView-dropTarget" onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} >
+ <h3>{titleStr}</h3>
+ <ul className="no-indent">
+ <TreeView
+ document={this.props.Document}
+ />
+ </ul>
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 90080ab43..f938d2237 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,4 +1,4 @@
-import { action, computed } from "mobx";
+import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
import { Document } from "../../../fields/Document";
import { ListField } from "../../../fields/ListField";
@@ -11,14 +11,15 @@ import { CollectionFreeFormView } from "./CollectionFreeFormView";
import { CollectionDockingView } from "./CollectionDockingView";
import { CollectionSchemaView } from "./CollectionSchemaView";
import { CollectionViewProps } from "./CollectionViewBase";
-
-
+import { CollectionTreeView } from "./CollectionTreeView";
+import { Field } from "../../../fields/Field";
export enum CollectionViewType {
Invalid,
Freeform,
Schema,
Docking,
+ Tree
}
export const COLLECTION_BORDER_WIDTH = 2;
@@ -28,8 +29,8 @@ export class CollectionView extends React.Component<CollectionViewProps> {
public static LayoutString(fieldKey: string = "DataKey") {
return `<CollectionView Document={Document}
- ScreenToLocalTransform={ScreenToLocalTransform} fieldKey={${fieldKey}} isSelected={isSelected} select={select} bindings={bindings}
- isTopMost={isTopMost} BackgroundView={BackgroundView} />`;
+ ScreenToLocalTransform={ScreenToLocalTransform} fieldKey={${fieldKey}} panelWidth={PanelWidth} panelHeight={PanelHeight} isSelected={isSelected} select={select} bindings={bindings}
+ isTopMost={isTopMost} SelectOnLoad={selectOnLoad} BackgroundView={BackgroundView} />`;
}
public active = () => {
var isSelected = this.props.isSelected();
@@ -39,16 +40,28 @@ export class CollectionView extends React.Component<CollectionViewProps> {
}
@action
addDocument = (doc: Document): void => {
- //TODO This won't create the field if it doesn't already exist
- const value = this.props.Document.GetData(this.props.fieldKey, ListField, new Array<Document>())
- value.push(doc);
+ if (this.props.Document.Get(this.props.fieldKey) instanceof Field) {
+ //TODO This won't create the field if it doesn't already exist
+ const value = this.props.Document.GetData(this.props.fieldKey, ListField, new Array<Document>())
+ value.push(doc);
+ } else {
+ this.props.Document.SetData(this.props.fieldKey, [doc], ListField);
+ }
}
+
@action
removeDocument = (doc: Document): boolean => {
//TODO This won't create the field if it doesn't already exist
const value = this.props.Document.GetData(this.props.fieldKey, ListField, new Array<Document>())
- let index = value.indexOf(doc);
+ let index = -1;
+ for (let i = 0; i < value.length; i++) {
+ if (value[i].Id == doc.Id) {
+ index = i;
+ break;
+ }
+ }
+
if (index !== -1) {
value.splice(index, 1)
@@ -76,13 +89,15 @@ export class CollectionView extends React.Component<CollectionViewProps> {
Document.SetData(KeyStore.ViewType, type, NumberField);
}
+
render() {
let viewType = this.collectionViewType;
+
switch (viewType) {
case CollectionViewType.Freeform:
return (<CollectionFreeFormView {...this.props}
addDocument={this.addDocument} removeDocument={this.removeDocument} active={this.active}
- CollectionView={this} />)
+ CollectionView={this} />);
case CollectionViewType.Schema:
return (<CollectionSchemaView {...this.props}
addDocument={this.addDocument} removeDocument={this.removeDocument} active={this.active}
@@ -91,6 +106,10 @@ export class CollectionView extends React.Component<CollectionViewProps> {
return (<CollectionDockingView {...this.props}
addDocument={this.addDocument} removeDocument={this.removeDocument} active={this.active}
CollectionView={this} />)
+ case CollectionViewType.Tree:
+ return (<CollectionTreeView {...this.props}
+ addDocument={this.addDocument} removeDocument={this.removeDocument} active={this.active}
+ CollectionView={this} />)
default:
return <div></div>
}
diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx
index 7e269caf1..7067724c8 100644
--- a/src/client/views/collections/CollectionViewBase.tsx
+++ b/src/client/views/collections/CollectionViewBase.tsx
@@ -20,6 +20,8 @@ export interface CollectionViewProps {
isTopMost: boolean;
select: (ctrlPressed: boolean) => void;
bindings: any;
+ panelWidth: () => number;
+ panelHeight: () => number;
}
export interface SubCollectionViewProps extends CollectionViewProps {
active: () => boolean;
@@ -42,12 +44,16 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
@undoBatch
@action
protected drop(e: Event, de: DragManager.DropEvent) {
- const doc: DocumentView = de.data["document"];
- if (doc.props.ContainingCollectionView && doc.props.ContainingCollectionView !== this.props.CollectionView) {
- if (doc.props.RemoveDocument) {
- doc.props.RemoveDocument(doc.props.Document);
+ const docView: DocumentView = de.data["documentView"];
+ const doc: Document = de.data["document"];
+ if (docView && docView.props.ContainingCollectionView && docView.props.ContainingCollectionView !== this.props.CollectionView) {
+ if (docView.props.RemoveDocument) {
+ docView.props.RemoveDocument(docView.props.Document);
}
- this.props.addDocument(doc.props.Document);
+ this.props.addDocument(docView.props.Document);
+ } else if (doc) {
+ this.props.removeDocument(doc);
+ this.props.addDocument(doc);
}
e.stopPropagation();
}
@@ -60,8 +66,8 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
let html = e.dataTransfer.getData("text/html");
let text = e.dataTransfer.getData("text/plain");
- if (html) {
- let htmlDoc = Documents.HtmlDocument(html, { ...options });
+ if (html && html.indexOf("<img") != 0) {
+ let htmlDoc = Documents.HtmlDocument(html, { ...options, width: 300, height: 300 });
htmlDoc.SetText(KeyStore.DocumentText, text);
this.props.addDocument(htmlDoc);
return;
@@ -72,7 +78,7 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
if (item.kind === "string" && item.type.indexOf("uri") != -1) {
e.dataTransfer.items[i].getAsString(function (s) {
action(() => {
- var img = Documents.ImageDocument(s, { ...options, nativeWidth: 300, nativeHeight: 300, width: 300, height: 300 })
+ var img = Documents.ImageDocument(s, { ...options, nativeWidth: 300, width: 300, })
let docs = that.props.Document.GetT(KeyStore.Data, ListField);
if (docs != FieldWaiting) {
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index d7243421a..9edad1f64 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -27,44 +27,26 @@ export class CollectionFreeFormDocumentView extends React.Component<DocumentView
return `scale(${this.props.ContentScaling()}, ${this.props.ContentScaling()}) translate(${this.props.Document.GetNumber(KeyStore.X, 0)}px, ${this.props.Document.GetNumber(KeyStore.Y, 0)}px)`;
}
- @computed
- get width(): number {
- return this.props.Document.GetNumber(KeyStore.Width, 0);
- }
-
- @computed
- get nativeWidth(): number {
- return this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
- }
+ @computed get zIndex(): number { return this.props.Document.GetNumber(KeyStore.ZIndex, 0); }
+ @computed get width(): number { return this.props.Document.Width(); }
+ @computed get height(): number { return this.props.Document.Height(); }
+ @computed get nativeWidth(): number { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
+ @computed get nativeHeight(): number { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
set width(w: number) {
this.props.Document.SetData(KeyStore.Width, w, NumberField)
- if (this.nativeWidth > 0 && this.nativeHeight > 0) {
+ if (this.nativeWidth && this.nativeHeight) {
this.props.Document.SetNumber(KeyStore.Height, this.nativeHeight / this.nativeWidth * w)
}
}
- @computed
- get height(): number {
- return this.props.Document.GetNumber(KeyStore.Height, 0);
- }
- @computed
- get nativeHeight(): number {
- return this.props.Document.GetNumber(KeyStore.NativeHeight, 0);
- }
-
set height(h: number) {
this.props.Document.SetData(KeyStore.Height, h, NumberField);
- if (this.nativeWidth > 0 && this.nativeHeight > 0) {
+ if (this.nativeWidth && this.nativeHeight) {
this.props.Document.SetNumber(KeyStore.Width, this.nativeWidth / this.nativeHeight * h)
}
}
- @computed
- get zIndex(): number {
- return this.props.Document.GetNumber(KeyStore.ZIndex, 0);
- }
-
set zIndex(h: number) {
this.props.Document.SetData(KeyStore.ZIndex, h, NumberField)
}
@@ -87,6 +69,7 @@ export class CollectionFreeFormDocumentView extends React.Component<DocumentView
}
render() {
+ console.log(this.transform);
return (
<div className="collectionFreeFormDocumentView-container" ref={this._mainCont} style={{
transformOrigin: "left top",
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index ad1328e5d..19a455b74 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -12,14 +12,18 @@ 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");
+import { CollectionViewProps } from "../collections/CollectionViewBase";
const JsxParser = require('react-jsx-parser').default;//TODO Why does this need to be imported like this?
+
export interface DocumentViewProps {
ContainingCollectionView: Opt<CollectionView>;
@@ -32,6 +36,7 @@ export interface DocumentViewProps {
ContentScaling: () => number;
PanelWidth: () => number;
PanelHeight: () => number;
+ SelectOnLoad: boolean;
}
export interface JsxArgs extends DocumentViewProps {
Keys: { [name: string]: Key }
@@ -80,7 +85,6 @@ 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;
@@ -96,10 +100,9 @@ export class DocumentView extends React.Component<DocumentViewProps> {
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) {
@@ -115,21 +118,20 @@ 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["document"] = this;
+ dragData["documentView"] = this;
dragData["xOffset"] = e.x - left;
dragData["yOffset"] = e.y - top;
DragManager.StartDrag(this._mainCont.current, dragData, {
handlers: {
- dragComplete: action((e: DragManager.DragCompleteEvent) => { }),
+ dragComplete: action(() => { }),
},
hideSource: true
})
@@ -148,17 +150,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,42 +177,51 @@ 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()
- if (this.topMost) {
- ContextMenu.Instance.clearItems()
- ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked })
- ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
- }
- else {
+ ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked })
+ ContextMenu.Instance.addItem({ description: "Fields", event: this.fieldsClicked })
+ 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: "center", event: () => {
+ if (this.props.ContainingCollectionView) {
+ let doc = this.props.ContainingCollectionView.props.Document;
+ doc.SetNumber(KeyStore.PanX, this.props.Document.GetNumber(KeyStore.X, 0) + (this.props.Document.GetNumber(KeyStore.Width, 0) / 2))
+ doc.SetNumber(KeyStore.PanY, this.props.Document.GetNumber(KeyStore.Y, 0) + (this.props.Document.GetNumber(KeyStore.Height, 0) / 2))
+ }
+ }
+ })
+ //ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) })
+ ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
+ if (!this.topMost) {
// DocumentViews should stop propagation of this event
e.stopPropagation();
-
- ContextMenu.Instance.clearItems();
- ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked })
- ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document) })
- ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked })
- 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: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) })
- ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
- SelectionManager.SelectDoc(this, e.ctrlKey);
}
+
+ ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked })
+ ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
+ SelectionManager.SelectDoc(this, e.ctrlKey);
}
@computed get mainContent() {
return <JsxParser
- components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebView }}
+ components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox }}
bindings={this._documentBindings}
jsx={this.layout}
showWarnings={true}
onError={(test: any) => { console.log(test) }}
/>
}
+
render() {
if (!this.props.Document)
return <div></div>
@@ -237,7 +255,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
transform: `scale(${scaling},${scaling})`
}}
onContextMenu={this.onContextMenu}
- onPointerDown={this.onPointerDown} >
+ onPointerDown={this.onPointerDown}
+ >
{this.mainContent}
</div>
)
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 1a9d325db..f372258f8 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -7,11 +7,11 @@ 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,13 +53,20 @@ export class FieldView extends React.Component<FieldViewProps> {
else if (field instanceof ImageField) {
return <ImageBox {...this.props} />
}
+ else if (field instanceof WebField) {
+ return <WebBox {...this.props} />
+ }
+ // bcz: this belongs here, but it doesn't render well so taking it out for now
+ // else if (field instanceof HtmlField) {
+ // return <WebBox {...this.props} />
+ // }
else if (field instanceof NumberField) {
return <p>{field.Data}</p>
- } else if (field instanceof HtmlField) {
- return <WebView {...this.props} />
- } else if (field != FieldWaiting) {
- return <p>{field.GetValue}</p>
- } else
+ }
+ else if (field != FieldWaiting) {
+ return <p>{JSON.stringify(field.GetValue())}</p>
+ }
+ else
return <p> {"Waiting for server..."} </p>
}
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index 0389a3f85..21bd43b6e 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -9,8 +9,10 @@
}
.formattedTextBox-cont {
- background: beige;
- padding: 0;
+ background: white;
+ padding: 1;
+ border: black;
+ border-width: 10;
overflow-y: scroll;
overflow-x: hidden;
color: initial;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 60ee0b5e1..e65615af4 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -3,15 +3,17 @@ import { baseKeymap } from "prosemirror-commands";
import { history, redo, undo } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
import { schema } from "prosemirror-schema-basic";
-import { EditorState, Transaction } from "prosemirror-state";
+import { EditorState, Transaction, } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
-import { Opt, FieldWaiting, FieldValue } from "../../../fields/Field";
+import { Opt, FieldWaiting } from "../../../fields/Field";
import "./FormattedTextBox.scss";
import React = require("react")
import { RichTextField } from "../../../fields/RichTextField";
import { FieldViewProps, FieldView } from "./FieldView";
+
+
// FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document
//
// HTML Markup: <FormattedTextBox Doc={Document's ID} FieldKey={Key's name + "Key"}
@@ -39,7 +41,6 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
super(props);
this._ref = React.createRef();
-
this.onChange = this.onChange.bind(this);
}
@@ -47,25 +48,23 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
if (this._editorView) {
const state = this._editorView.state.apply(tx);
this._editorView.updateState(state);
- const { doc, fieldKey } = this.props;
- doc.SetData(fieldKey, JSON.stringify(state.toJSON()), RichTextField);
+ this.props.doc.SetData(this.props.fieldKey, JSON.stringify(state.toJSON()), RichTextField);
}
}
componentDidMount() {
let state: EditorState;
- const { doc, fieldKey } = this.props;
const config = {
schema,
plugins: [
history(),
keymap({ "Mod-z": undo, "Mod-y": redo }),
- keymap(baseKeymap)
+ keymap(baseKeymap),
]
};
- let field = doc.GetT(fieldKey, RichTextField);
- if (field && field != FieldWaiting) { // bcz: don't think this works
+ let field = this.props.doc.GetT(this.props.fieldKey, RichTextField);
+ if (field && field != FieldWaiting) {
state = EditorState.fromJSON(config, JSON.parse(field.Data));
} else {
state = EditorState.create(config);
@@ -85,6 +84,10 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field)));
}
})
+ if (this.props.selectOnLoad) {
+ this.props.select();
+ this._editorView!.focus();
+ }
}
componentWillUnmount() {
@@ -102,19 +105,20 @@ 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();
}
}
+ onPointerWheel = (e: React.WheelEvent): void => {
+ e.stopPropagation();
+ }
render() {
- var val = this.props.doc.Get(this.props.fieldKey);
return (<div className="formattedTextBox-cont"
onPointerDown={this.onPointerDown}
+ onWheel={this.onPointerWheel}
ref={this._ref} />)
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index b5ce8b28c..e206bf8d5 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -15,6 +15,7 @@ export class ImageBox extends React.Component<FieldViewProps> {
public static LayoutString() { return FieldView.LayoutString(ImageBox) }
private _ref: React.RefObject<HTMLDivElement>;
+ private _imgRef: React.RefObject<HTMLImageElement>;
private _downX: number = 0;
private _downY: number = 0;
private _lastTap: number = 0;
@@ -25,12 +26,20 @@ export class ImageBox extends React.Component<FieldViewProps> {
super(props);
this._ref = React.createRef();
+ this._imgRef = React.createRef();
this.state = {
photoIndex: 0,
isOpen: false,
};
}
+ @action
+ onLoad = (target: any) => {
+ var h = this._imgRef.current!.naturalHeight;
+ var w = this._imgRef.current!.naturalWidth;
+ this.props.doc.SetNumber(KeyStore.NativeHeight, this.props.doc.GetNumber(KeyStore.NativeWidth, 0) * h / w)
+ }
+
componentDidMount() {
}
@@ -84,10 +93,9 @@ export class ImageBox extends React.Component<FieldViewProps> {
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} >
- <img src={path} width={nativeWidth} alt="Image not found" />
+ <img src={path} width={nativeWidth} alt="Image not found" ref={this._imgRef} onLoad={this.onLoad} />
{this.lightbox(path)}
</div>)
}
diff --git a/src/client/views/nodes/KeyValueBox.scss b/src/client/views/nodes/KeyValueBox.scss
new file mode 100644
index 000000000..1295266e5
--- /dev/null
+++ b/src/client/views/nodes/KeyValueBox.scss
@@ -0,0 +1,31 @@
+.keyValueBox-cont {
+ overflow-y:scroll;
+ height: 100%;
+ border: black;
+ border-width: 1px;
+ border-style: solid;
+ box-sizing: border-box;
+ display: inline-block;
+ .imageBox-cont img {
+ max-height:45px;
+ height: auto;
+ }
+}
+.keyValueBox-table {
+ position: relative;
+}
+.keyValueBox-header {
+ background:gray;
+}
+.keyValueBox-evenRow {
+ background: white;
+ .formattedTextBox-cont {
+ background: white;
+ }
+}
+.keyValueBox-oddRow {
+ background: lightGray;
+ .formattedTextBox-cont {
+ background: lightgray;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
new file mode 100644
index 000000000..e8ebd50be
--- /dev/null
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -0,0 +1,85 @@
+
+import { IReactionDisposer } from 'mobx';
+import { observer } from "mobx-react";
+import { EditorView } from 'prosemirror-view';
+import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
+import { Document } from '../../../fields/Document';
+import { Opt, FieldWaiting } from '../../../fields/Field';
+import { KeyStore } from '../../../fields/KeyStore';
+import { FieldView, FieldViewProps } from './FieldView';
+import { KeyValuePair } from "./KeyValuePair";
+import "./KeyValueBox.scss";
+import React = require("react")
+
+@observer
+export class KeyValueBox extends React.Component<FieldViewProps> {
+
+ public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(KeyValueBox, fieldStr) }
+ private _ref: React.RefObject<HTMLDivElement>;
+ private _editorView: Opt<EditorView>;
+ private _reactionDisposer: Opt<IReactionDisposer>;
+
+
+ constructor(props: FieldViewProps) {
+ super(props);
+
+ this._ref = React.createRef();
+ }
+
+
+
+ shouldComponentUpdate() {
+ return false;
+ }
+
+
+ onPointerDown = (e: React.PointerEvent): void => {
+ if (e.buttons === 1 && this.props.isSelected()) {
+ e.stopPropagation();
+ }
+ }
+ onPointerWheel = (e: React.WheelEvent): void => {
+ e.stopPropagation();
+ }
+
+ createTable = () => {
+ let doc = this.props.doc.GetT(KeyStore.Data, Document);
+ if (!doc || doc == FieldWaiting) {
+ return <tr><td>Loading...</td></tr>
+ }
+ let realDoc = doc;
+
+ let ids: { [key: string]: string } = {};
+ let protos = doc.GetAllPrototypes();
+ for (const proto of protos) {
+ proto._proxies.forEach((val, key) => {
+ if (!(key in ids)) {
+ ids[key] = key;
+ }
+ })
+ }
+
+ let rows: JSX.Element[] = [];
+ let i = 0;
+ for (let key in ids) {
+ rows.push(<KeyValuePair doc={realDoc} rowStyle={"keyValueBox-" + (i++ % 2 ? "oddRow" : "evenRow")} fieldId={key} key={key} />)
+ }
+ return rows;
+ }
+
+
+ render() {
+
+ return (<div className="keyValueBox-cont" onWheel={this.onPointerWheel}>
+ <table className="keyValueBox-table">
+ <tbody>
+ <tr className="keyValueBox-header">
+ <th>Key</th>
+ <th>Fields</th>
+ </tr>
+ {this.createTable()}
+ </tbody>
+ </table>
+ </div>)
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
new file mode 100644
index 000000000..a97e98313
--- /dev/null
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -0,0 +1,58 @@
+import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
+import "./KeyValueBox.scss";
+import React = require("react")
+import { FieldViewProps, FieldView } from './FieldView';
+import { Opt, Field } from '../../../fields/Field';
+import { observer } from "mobx-react"
+import { observable, action } from 'mobx';
+import { Document } from '../../../fields/Document';
+import { Key } from '../../../fields/Key';
+import { Server } from "../../Server"
+
+// Represents one row in a key value plane
+
+export interface KeyValuePairProps {
+ rowStyle: string;
+ fieldId: string;
+ doc: Document;
+}
+@observer
+export class KeyValuePair extends React.Component<KeyValuePairProps> {
+
+ @observable
+ private key: Opt<Key>
+
+ constructor(props: KeyValuePairProps) {
+ super(props);
+ Server.GetField(this.props.fieldId,
+ action((field: Opt<Field>) => {
+ if (field) {
+ this.key = field as Key;
+ }
+ }));
+
+ }
+
+
+ render() {
+ if (!this.key) {
+ return <tr><td>error</td><td></td></tr>
+
+ }
+ let props: FieldViewProps = {
+ doc: this.props.doc,
+ fieldKey: this.key,
+ isSelected: () => false,
+ select: () => { },
+ isTopMost: false,
+ bindings: {},
+ selectOnLoad: false,
+ }
+ return (
+ <tr className={this.props.rowStyle}>
+ <td>{this.key.Name}</td>
+ <td><FieldView {...props} /></td>
+ </tr>
+ )
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
new file mode 100644
index 000000000..e72b3c4da
--- /dev/null
+++ b/src/client/views/nodes/WebBox.scss
@@ -0,0 +1,14 @@
+
+.webBox-cont {
+ padding: 0vw;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
+
+.webBox-button {
+ padding : 0vw;
+ border: none;
+ width : 100%;
+ height: 100%;
+} \ No newline at end of file
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
new file mode 100644
index 000000000..2ca8d49ce
--- /dev/null
+++ b/src/client/views/nodes/WebBox.tsx
@@ -0,0 +1,38 @@
+import "./WebBox.scss";
+import React = require("react")
+import { WebField } from '../../../fields/WebField';
+import { FieldViewProps, FieldView } from './FieldView';
+import { FieldWaiting } from '../../../fields/Field';
+import { observer } from "mobx-react"
+import { computed } from 'mobx';
+import { KeyStore } from '../../../fields/KeyStore';
+
+@observer
+export class WebBox extends React.Component<FieldViewProps> {
+
+ public static LayoutString() { return FieldView.LayoutString(WebBox); }
+
+ constructor(props: FieldViewProps) {
+ super(props);
+ }
+
+ @computed get html(): string { return this.props.doc.GetHtml(KeyStore.Data, ""); }
+
+ render() {
+ let field = this.props.doc.Get(this.props.fieldKey);
+ let path = field == FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
+ field instanceof WebField ? field.Data.href : "https://crossorigin.me/" + "https://cs.brown.edu";
+
+ let content = this.html ?
+ <span dangerouslySetInnerHTML={{ __html: this.html }}></span> :
+ <div style={{ width: "100%", height: "100%", position: "absolute" }}>
+ <iframe src={path} style={{ position: "absolute", width: "100%", height: "100%" }}></iframe>
+ {this.props.isSelected() ? (null) : <div style={{ width: "100%", height: "100%", position: "absolute" }} />}
+ </div>;
+
+ return (
+ <div className="webBox-cont" >
+ {content}
+ </div>)
+ }
+} \ No newline at end of file
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/debug/Viewer.tsx b/src/debug/Viewer.tsx
index aff77fca3..780e9f8f2 100644
--- a/src/debug/Viewer.tsx
+++ b/src/debug/Viewer.tsx
@@ -17,7 +17,7 @@ configure({
@observer
class FieldViewer extends React.Component<{ field: BasicField<any> }> {
render() {
- return <span>{this.props.field.Data} ({this.props.field.Id})</span>;
+ return <span>{JSON.stringify(this.props.field.Data)} ({this.props.field.Id})</span>;
}
}
diff --git a/src/fields/Document.ts b/src/fields/Document.ts
index 0d7d357a0..0c156b282 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();
@@ -17,8 +18,6 @@ export class Document extends Field {
super(id)
if (save) {
- var title = (this._proxies.has(KeyStore.Title.Id) ? "???" : this.Title) + "(" + this.Id + ")";
- console.log("Save " + title);
Server.UpdateField(this)
}
}
@@ -31,7 +30,7 @@ export class Document extends Field {
}
public Width = () => { return this.GetNumber(KeyStore.Width, 0) }
- public Height = () => { return this.GetNumber(KeyStore.Height, 0) }
+ public Height = () => { return this.GetNumber(KeyStore.Height, this.GetNumber(KeyStore.NativeWidth, 0) ? this.GetNumber(KeyStore.NativeHeight, 0) / this.GetNumber(KeyStore.NativeWidth, 0) * this.GetNumber(KeyStore.Width, 0) : 0) }
public Scale = () => { return this.GetNumber(KeyStore.Scale, 1) }
@computed
@@ -103,6 +102,25 @@ export class Document extends Field {
return false;
}
+ 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);
+ }
+ }
+
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) {
@@ -127,6 +145,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/Field.ts b/src/fields/Field.ts
index c7e0232af..d48509a47 100644
--- a/src/fields/Field.ts
+++ b/src/fields/Field.ts
@@ -1,6 +1,7 @@
import { Utils } from "../Utils";
import { Types } from "../server/Message";
+import { computed } from "mobx";
export function Cast<T extends Field>(field: FieldValue<Field>, ctor: { new(): T }): Opt<T> {
if (field) {
@@ -25,6 +26,8 @@ export abstract class Field {
}
private id: FieldId;
+
+ @computed
get Id(): FieldId {
return this.id;
}
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/Key.ts b/src/fields/Key.ts
index c16a00878..00d78d516 100644
--- a/src/fields/Key.ts
+++ b/src/fields/Key.ts
@@ -2,7 +2,6 @@ import { Field, FieldId } from "./Field"
import { Utils } from "../Utils";
import { observable } from "mobx";
import { Types } from "../server/Message";
-import { ObjectID } from "bson";
import { Server } from "../client/Server";
export class Key extends Field {
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 5115142b8..e6f08bc29 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -35,6 +35,12 @@ const mongoose = require('mongoose');
import { performance } from 'perf_hooks'
import * as path from 'path'
import User, { DashUserModel } from './authentication/models/user_model';
+import * as fs from 'fs';
+import * as request from 'request'
+
+const download = (url: string, dest: fs.PathLike) => {
+ request.get(url).pipe(fs.createWriteStream(dest));
+}
const mongoUrl = 'mongodb://localhost:27017/Dash';
mongoose.connect(mongoUrl)