aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authoryipstanley <stanley_yip@brown.edu>2019-04-13 17:37:15 -0400
committeryipstanley <stanley_yip@brown.edu>2019-04-13 17:37:15 -0400
commit0a3fb59c91ad0498fe0302522ea775f71483d9e7 (patch)
treed5ad95a3ff978b89b1978017f921f58aec2eb884 /src
parent191283a0e61e1f1b81429e6be6c5c1ef3da1a1b1 (diff)
parenta23b160d19beff9163f970f7ae678c2aed9ce14e (diff)
Merge branch 'master' of https://github.com/browngraphicslab/Dash-Web into interaction_changes
Diffstat (limited to 'src')
-rw-r--r--src/Utils.ts11
-rw-r--r--src/client/Server.ts26
-rw-r--r--src/client/SocketStub.ts7
-rw-r--r--src/client/documents/Documents.ts39
-rw-r--r--src/client/northstar/dash-nodes/HistogramBox.tsx4
-rw-r--r--src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx6
-rw-r--r--src/client/northstar/manager/Gateway.ts12
-rw-r--r--src/client/util/DocumentManager.ts3
-rw-r--r--src/client/util/DragManager.ts45
-rw-r--r--src/client/util/Scripting.ts33
-rw-r--r--src/client/util/SelectionManager.ts2
-rw-r--r--src/client/util/TooltipTextMenu.scss2
-rw-r--r--src/client/util/TooltipTextMenu.tsx14
-rw-r--r--src/client/views/ContextMenu.scss4
-rw-r--r--src/client/views/DocumentDecorations.scss40
-rw-r--r--src/client/views/DocumentDecorations.tsx45
-rw-r--r--src/client/views/InkingCanvas.scss2
-rw-r--r--src/client/views/InkingControl.scss2
-rw-r--r--src/client/views/Main.scss25
-rw-r--r--src/client/views/Main.tsx103
-rw-r--r--src/client/views/PreviewCursor.scss9
-rw-r--r--src/client/views/PreviewCursor.tsx37
-rw-r--r--src/client/views/_global_variables.scss17
-rw-r--r--src/client/views/collections/CollectionBaseView.tsx17
-rw-r--r--src/client/views/collections/CollectionDockingView.scss21
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx104
-rw-r--r--src/client/views/collections/CollectionPDFView.tsx3
-rw-r--r--src/client/views/collections/CollectionSchemaView.scss6
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx34
-rw-r--r--src/client/views/collections/CollectionSubView.tsx55
-rw-r--r--src/client/views/collections/CollectionTreeView.scss6
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx8
-rw-r--r--src/client/views/collections/CollectionVideoView.tsx3
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx22
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss24
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx32
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss20
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx71
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.scss2
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx45
-rw-r--r--src/client/views/collections/collectionFreeForm/PreviewCursor.scss27
-rw-r--r--src/client/views/collections/collectionFreeForm/PreviewCursor.tsx120
-rw-r--r--src/client/views/globalCssVariables.scss29
-rw-r--r--src/client/views/globalCssVariables.scss.d.ts8
-rw-r--r--src/client/views/nodes/DocumentView.scss4
-rw-r--r--src/client/views/nodes/DocumentView.tsx55
-rw-r--r--src/client/views/nodes/FieldView.tsx10
-rw-r--r--src/client/views/nodes/FormattedTextBox.scss3
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx60
-rw-r--r--src/client/views/nodes/KeyValueBox.scss93
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx53
-rw-r--r--src/client/views/nodes/KeyValuePair.scss22
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx60
-rw-r--r--src/client/views/nodes/LinkBox.scss2
-rw-r--r--src/client/views/nodes/LinkBox.tsx24
-rw-r--r--src/client/views/nodes/LinkEditor.scss2
-rw-r--r--src/client/views/nodes/VideoBox.tsx2
-rw-r--r--src/fields/Document.ts4
-rw-r--r--src/fields/KeyStore.ts1
-rw-r--r--src/fields/ScriptField.ts63
-rw-r--r--src/server/Client.ts11
-rw-r--r--src/server/database.ts122
-rw-r--r--src/server/index.ts124
63 files changed, 1043 insertions, 817 deletions
diff --git a/src/Utils.ts b/src/Utils.ts
index b0e66787e..c629bc263 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -2,6 +2,7 @@ import v4 = require('uuid/v4');
import v5 = require("uuid/v5");
import { Socket } from 'socket.io';
import { Message, Types } from './server/Message';
+import { Document } from './fields/Document';
export class Utils {
@@ -86,14 +87,12 @@ export class Utils {
}
}
-export function returnTrue() {
- return true;
-}
+export function returnTrue() { return true; }
-export function returnFalse() {
- return false;
-}
+export function returnFalse() { return false; }
export function emptyFunction() { }
+export function emptyDocFunction(doc: Document) { }
+
export type Without<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>; \ No newline at end of file
diff --git a/src/client/Server.ts b/src/client/Server.ts
index 857101a33..3bbbebe72 100644
--- a/src/client/Server.ts
+++ b/src/client/Server.ts
@@ -1,10 +1,10 @@
import { Key } from "../fields/Key";
-import { ObservableMap, action, reaction } from "mobx";
+import { ObservableMap, action, reaction, runInAction } from "mobx";
import { Field, FieldWaiting, FIELD_WAITING, Opt, FieldId } from "../fields/Field";
import { Document } from "../fields/Document";
import { SocketStub, FieldMap } from "./SocketStub";
import * as OpenSocket from 'socket.io-client';
-import { Utils } from "./../Utils";
+import { Utils, emptyFunction } from "./../Utils";
import { MessageStore, Types } from "./../server/Message";
export class Server {
@@ -59,14 +59,14 @@ export class Server {
public static GetFields(fieldIds: FieldId[]): Promise<{ [id: string]: Field }>;
public static GetFields(fieldIds: FieldId[], callback: (fields: FieldMap) => any): void;
public static GetFields(fieldIds: FieldId[], callback?: (fields: FieldMap) => any): Promise<FieldMap> | void {
- let fn = (cb: (fields: FieldMap) => void) => {
+ let fn = action((cb: (fields: FieldMap) => void) => {
let neededFieldIds: FieldId[] = [];
let waitingFieldIds: FieldId[] = [];
- let existingFields: { [id: string]: Field } = {};
+ let existingFields: FieldMap = {};
for (let id of fieldIds) {
let field = this.ClientFieldsCached.get(id);
- if (!field) {
+ if (field === undefined) {
neededFieldIds.push(id);
this.ClientFieldsCached.set(id, FieldWaiting);
} else if (field === FieldWaiting) {
@@ -79,7 +79,7 @@ export class Server {
for (let id of neededFieldIds) {
let field = fields[id];
if (field) {
- if (!(this.ClientFieldsCached.get(field.Id) instanceof Field)) {
+ if (this.ClientFieldsCached.get(field.Id) === FieldWaiting) {
this.ClientFieldsCached.set(field.Id, field);
} else {
throw new Error("we shouldn't be trying to replace things that are already in the cache");
@@ -94,17 +94,17 @@ export class Server {
}
reaction(() => waitingFieldIds.map(id => this.ClientFieldsCached.get(id)),
(cachedFields, reaction) => {
- if (!cachedFields.some(field => !field)) {
+ if (!cachedFields.some(field => field === FieldWaiting)) {
+ const realFields = cachedFields as Opt<Field>[];
reaction.dispose();
- for (let field of cachedFields) {
- let realField = field as Field;
- existingFields[realField.Id] = realField;
- }
+ waitingFieldIds.forEach((id, index) => {
+ existingFields[id] = realFields[index];
+ });
cb({ ...fields, ...existingFields });
}
}, { fireImmediately: true });
}));
- };
+ });
if (callback) {
fn(callback);
} else {
@@ -167,7 +167,7 @@ export class Server {
if (f) {
// console.log("Applying : " + field._id);
f.UpdateFromServer(field.data);
- f.init(() => { });
+ f.init(emptyFunction);
} else {
// console.log("Not applying wa : " + field._id);
}
diff --git a/src/client/SocketStub.ts b/src/client/SocketStub.ts
index 257973e3d..5e2ca6a98 100644
--- a/src/client/SocketStub.ts
+++ b/src/client/SocketStub.ts
@@ -62,10 +62,13 @@ export class SocketStub {
public static SEND_FIELDS_REQUEST(fieldIds: FieldId[], callback: (fields: FieldMap) => any) {
Utils.EmitCallback(Server.Socket, MessageStore.GetFields, fieldIds, (fields: any[]) => {
let fieldMap: any = {};
+ let proms: Promise<any>[] = [];
for (let field of fields) {
- fieldMap[field._id] = ServerUtils.FromJson(field);
+ let f = ServerUtils.FromJson(field);
+ fieldMap[field._id] = f;
+ proms.push(new Promise(res => f.init(res)));
}
- callback(fieldMap);
+ Promise.all(proms).then(() => callback(fieldMap));
});
}
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 72e6e57ab..3c36fe500 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,6 +1,6 @@
import { AudioField } from "../../fields/AudioField";
import { Document } from "../../fields/Document";
-import { Field } from "../../fields/Field";
+import { Field, Opt } from "../../fields/Field";
import { HtmlField } from "../../fields/HtmlField";
import { ImageField } from "../../fields/ImageField";
import { InkField, StrokeData } from "../../fields/InkField";
@@ -26,6 +26,12 @@ import { KeyValueBox } from "../views/nodes/KeyValueBox";
import { PDFBox } from "../views/nodes/PDFBox";
import { VideoBox } from "../views/nodes/VideoBox";
import { WebBox } from "../views/nodes/WebBox";
+import { Gateway } from "../northstar/manager/Gateway";
+import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
+import { action } from "mobx";
+import { ColumnAttributeModel } from "../northstar/core/attribute/AttributeModel";
+import { AttributeTransformationModel } from "../northstar/core/attribute/AttributeTransformationModel";
+import { AggregateFunction } from "../northstar/model/idea/idea";
export interface DocumentOptions {
x?: number;
@@ -200,6 +206,31 @@ export namespace Documents {
export function PdfDocument(url: string, options: DocumentOptions = {}) {
return assignToDelegate(SetInstanceOptions(pdfProto, options, [new URL(url), PDFField]).MakeDelegate(), options);
}
+ export async function DBDocument(url: string, options: DocumentOptions = {}) {
+ let schemaName = options.title ? options.title : "-no schema-";
+ let ctlog = await Gateway.Instance.GetSchema(url, schemaName);
+ if (ctlog && ctlog.schemas) {
+ let schema = ctlog.schemas[0];
+ let schemaDoc = Documents.TreeDocument([], { ...options, nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: schema.displayName! });
+ let schemaDocuments = schemaDoc.GetList(KeyStore.Data, [] as Document[]);
+ CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => {
+ Server.GetField(attr.displayName! + ".alias", action((field: Opt<Field>) => {
+ if (field instanceof Document) {
+ schemaDocuments.push(field);
+ } else {
+ var atmod = new ColumnAttributeModel(attr);
+ let histoOp = new HistogramOperation(schema.displayName!,
+ new AttributeTransformationModel(atmod, AggregateFunction.None),
+ new AttributeTransformationModel(atmod, AggregateFunction.Count),
+ new AttributeTransformationModel(atmod, AggregateFunction.Count));
+ schemaDocuments.push(Documents.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName! }, undefined, attr.displayName! + ".alias"));
+ }
+ }));
+ });
+ return schemaDoc;
+ }
+ return Documents.TreeDocument([], { width: 50, height: 100, title: schemaName });
+ }
export function WebDocument(url: string, options: DocumentOptions = {}) {
return assignToDelegate(SetInstanceOptions(webProto, options, [new URL(url), WebField]).MakeDelegate(), options);
}
@@ -242,13 +273,15 @@ export namespace Documents {
<div style="position:relative; height:15%; text-align:center; ">`
+ FormattedTextBox.LayoutString("CaptionKey") +
`</div>
- </div>`; }
+ </div>`;
+ }
export function FixedCaption(fieldName: string = "Caption") {
return `<div style="position:absolute; height:30px; bottom:0; width:100%">
<div style="position:absolute; width:100%; height:100%; text-align:center;bottom:0;">`
+ FormattedTextBox.LayoutString(fieldName + "Key") +
`</div>
- </div>`; }
+ </div>`;
+ }
function OuterCaption() {
return (`
diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index 7df59ef07..2084fc346 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -47,10 +47,6 @@ export class HistogramBox extends React.Component<FieldViewProps> {
this.BinRanges[1] instanceof AggregateBinRange ? ChartType.VerticalBar : ChartType.HeatMap;
}
- constructor(props: FieldViewProps) {
- super(props);
- }
-
@action
dropX = (e: Event, de: DragManager.DropEvent) => {
if (de.data instanceof DragManager.DocumentDragData) {
diff --git a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
index 4c5bdb14b..721bf6a89 100644
--- a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
@@ -1,7 +1,7 @@
import React = require("react");
import { computed, observable, reaction, runInAction, trace, action } from "mobx";
import { observer } from "mobx-react";
-import { Utils as DashUtils } from '../../../Utils';
+import { Utils as DashUtils, emptyFunction } from '../../../Utils';
import { FilterModel } from "../../northstar/core/filter/FilterModel";
import { ModelHelpers } from "../../northstar/model/ModelHelpers";
import { ArrayUtil } from "../../northstar/utils/ArrayUtil";
@@ -49,7 +49,7 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP
private getSelectionToggle(binPrimitives: HistogramBinPrimitive[], allBrushIndex: number, filterModel: FilterModel) {
let rawAllBrushPrim = ArrayUtil.FirstOrDefault(binPrimitives, bp => bp.BrushIndex === allBrushIndex);
if (!rawAllBrushPrim) {
- return () => { };
+ return emptyFunction;
}
let allBrushPrim = rawAllBrushPrim;
return () => runInAction(() => {
@@ -97,7 +97,7 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP
let trans1Ypercent = `${yFrom / this.renderDimension * 100}%`;
return <line className="histogramboxprimitives-line" key={DashUtils.GenerateGuid()} x1={trans1Xpercent} x2={`${trans2Xpercent}`} y1={trans1Ypercent} y2={`${trans2Ypercent}`} />;
}
- drawRect(r: PIXIRectangle, barAxis: number, color: number | undefined, classExt: string, tapHandler: () => void = () => { }) {
+ drawRect(r: PIXIRectangle, barAxis: number, color: number | undefined, classExt: string, tapHandler: () => void = emptyFunction) {
if (r.height < 0) {
r.y += r.height;
r.height = -r.height;
diff --git a/src/client/northstar/manager/Gateway.ts b/src/client/northstar/manager/Gateway.ts
index 8f3b6b11c..207a9ad19 100644
--- a/src/client/northstar/manager/Gateway.ts
+++ b/src/client/northstar/manager/Gateway.ts
@@ -23,9 +23,9 @@ export class Gateway {
}
}
- public async GetSchema(dbName: string): Promise<Catalog> {
+ public async GetSchema(pathname: string, schemaname: string): Promise<Catalog> {
try {
- const json = await this.MakeGetRequest("schema", undefined, dbName);
+ const json = await this.MakeGetRequest("schema", undefined, { path: pathname, schema: schemaname });
const cat = Catalog.fromJS(json);
return cat;
}
@@ -144,13 +144,13 @@ export class Gateway {
});
}
- public async MakeGetRequest(endpoint: string, signal?: AbortSignal, data?: any): Promise<any> {
- let url = !data ? Gateway.ConstructUrl(endpoint) :
+ public async MakeGetRequest(endpoint: string, signal?: AbortSignal, params?: any): Promise<any> {
+ let url = !params ? Gateway.ConstructUrl(endpoint) :
(() => {
let newUrl = new URL(Gateway.ConstructUrl(endpoint));
- newUrl.searchParams.append("data", data);
+ Object.getOwnPropertyNames(params).map(prop =>
+ newUrl.searchParams.append(prop, params[prop]));
return Gateway.ConstructUrl(endpoint) + newUrl.search;
- return newUrl as any;
})();
const response = await fetch(url,
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index f38b8ca75..3e093c8dc 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -28,8 +28,7 @@ export class DocumentManager {
}
public getAllDocumentViews(collection: Document) {
- return this.DocumentViews.filter(dv =>
- dv.props.ContainingCollectionView && dv.props.ContainingCollectionView.props.Document === collection);
+ return this.DocumentViews.filter(dv => dv.props.ContainingCollectionView && dv.props.ContainingCollectionView.props.Document === collection);
}
public getDocumentView(toFind: Document): DocumentView | null {
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 4849ae9f7..6a7047725 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,12 +1,15 @@
import { action } from "mobx";
import { Document } from "../../fields/Document";
+import { emptyFunction } from "../../Utils";
import { CollectionDockingView } from "../views/collections/CollectionDockingView";
-import { CollectionView } from "../views/collections/CollectionView";
import { DocumentDecorations } from "../views/DocumentDecorations";
+import { Main } from "../views/Main";
import { DocumentView } from "../views/nodes/DocumentView";
-import { returnFalse } from "../../Utils";
+import * as globalCssVariables from "../views/globalCssVariables.scss";
+import { KeyStore } from "../../fields/KeyStore";
+import { FieldWaiting } from "../../fields/Field";
-export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Document, moveFunc?: DragManager.MoveFunction, copyOnDrop: boolean = false) {
+export function SetupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Document, moveFunc?: DragManager.MoveFunction, copyOnDrop: boolean = false) {
let onRowMove = action((e: PointerEvent): void => {
e.stopPropagation();
e.preventDefault();
@@ -38,6 +41,31 @@ export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc:
return onItemDown;
}
+export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: number, sourceDoc: Document) {
+ let srcTarg = sourceDoc.GetT(KeyStore.Prototype, Document);
+ let draggedDocs = (srcTarg && srcTarg !== FieldWaiting) ?
+ srcTarg.GetList(KeyStore.LinkedToDocs, [] as Document[]).map(linkDoc =>
+ (linkDoc.GetT(KeyStore.LinkedToDocs, Document)) as Document) : [];
+ let draggedFromDocs = (srcTarg && srcTarg !== FieldWaiting) ?
+ srcTarg.GetList(KeyStore.LinkedFromDocs, [] as Document[]).map(linkDoc =>
+ (linkDoc.GetT(KeyStore.LinkedFromDocs, Document)) as Document) : [];
+ draggedDocs.push(...draggedFromDocs);
+ if (draggedDocs.length) {
+ let moddrag = [] as Document[];
+ for (const draggedDoc of draggedDocs) {
+ let doc = await draggedDoc.GetTAsync(KeyStore.AnnotationOn, Document);
+ if (doc) moddrag.push(doc);
+ }
+ let dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs);
+ DragManager.StartDocumentDrag([dragEle], dragData, x, y, {
+ handlers: {
+ dragComplete: action(emptyFunction),
+ },
+ hideSource: false
+ });
+ }
+}
+
export namespace DragManager {
export function Root() {
const root = document.getElementById("root");
@@ -129,11 +157,11 @@ export namespace DragManager {
}
export class LinkDragData {
- constructor(linkSourceDoc: DocumentView) {
- this.linkSourceDocumentView = linkSourceDoc;
+ constructor(linkSourceDoc: Document) {
+ this.linkSourceDocument = linkSourceDoc;
}
droppedDocuments: Document[] = [];
- linkSourceDocumentView: DocumentView;
+ linkSourceDocument: Document;
[id: string]: any;
}
@@ -147,6 +175,7 @@ export namespace DragManager {
dragDiv.className = "dragManager-dragDiv";
DragManager.Root().appendChild(dragDiv);
}
+ Main.Instance.SetTextDoc();
let scaleXs: number[] = [];
let scaleYs: number[] = [];
@@ -175,7 +204,7 @@ export namespace DragManager {
dragElement.style.bottom = "";
dragElement.style.left = "0";
dragElement.style.transformOrigin = "0 0";
- dragElement.style.zIndex = "1000";
+ dragElement.style.zIndex = globalCssVariables.contextMenuZindex;// "1000";
dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
dragElement.style.width = `${rect.width / scaleX}px`;
dragElement.style.height = `${rect.height / scaleY}px`;
@@ -224,7 +253,7 @@ export namespace DragManager {
CollectionDockingView.Instance.StartOtherDrag(docs, {
pageX: e.pageX,
pageY: e.pageY,
- preventDefault: () => { },
+ preventDefault: emptyFunction,
button: 0
});
}
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 9015f21cf..c67cc067a 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -54,15 +54,20 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
let paramNames = ["KeyStore", "Documents", ...fieldTypes.map(fn => fn.name)];
let params: any[] = [KeyStore, Documents, ...fieldTypes];
let compiledFunction = new Function(...paramNames, `return ${script}`);
+ let { capturedVariables = {} } = options;
let run = (args: { [name: string]: any } = {}): ScriptResult => {
let argsArray: any[] = [];
for (let name of customParams) {
if (name === "this") {
continue;
}
- argsArray.push(args[name]);
+ if (name in args) {
+ argsArray.push(args[name]);
+ } else {
+ argsArray.push(capturedVariables[name]);
+ }
}
- let thisParam = args.this;
+ let thisParam = args.this || capturedVariables.this;
try {
const result = compiledFunction.apply(thisParam, params).apply(thisParam, argsArray);
return { success: true, result };
@@ -130,22 +135,30 @@ export interface ScriptOptions {
requiredType?: string;
addReturn?: boolean;
params?: { [name: string]: string };
+ capturedVariables?: { [name: string]: Field };
}
-export function CompileScript(script: string, { requiredType = "", addReturn = false, params = {} }: ScriptOptions = {}): CompileResult {
+export function CompileScript(script: string, options: ScriptOptions = {}): CompileResult {
+ const { requiredType = "", addReturn = false, params = {}, capturedVariables = {} } = options;
let host = new ScriptingCompilerHost;
- let paramArray: string[] = [];
- if ("this" in params) {
- paramArray.push("this");
+ let paramNames: string[] = [];
+ if ("this" in params || "this" in capturedVariables) {
+ paramNames.push("this");
}
for (const key in params) {
if (key === "this") continue;
- paramArray.push(key);
+ paramNames.push(key);
}
- let paramString = paramArray.map(key => {
+ let paramList = paramNames.map(key => {
const val = params[key];
return `${key}: ${val}`;
- }).join(", ");
+ });
+ for (const key in capturedVariables) {
+ if (key === "this") continue;
+ paramNames.push(key);
+ paramList.push(`${key}: ${capturedVariables[key].constructor.name}`);
+ }
+ let paramString = paramList.join(", ");
let funcScript = `(function(${paramString})${requiredType ? `: ${requiredType}` : ''} {
${addReturn ? `return ${script};` : script}
})`;
@@ -157,7 +170,7 @@ export function CompileScript(script: string, { requiredType = "", addReturn = f
let diagnostics = ts.getPreEmitDiagnostics(program).concat(testResult.diagnostics);
- return Run(outputText, paramArray, diagnostics, script, { requiredType, addReturn, params });
+ return Run(outputText, paramNames, diagnostics, script, options);
}
export function OrLiteralType(returnType: string): string {
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 5ddaafc72..2fa45a086 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -25,6 +25,7 @@ export namespace SelectionManager {
DeselectAll(): void {
manager.SelectedDocuments.map(dv => dv.props.onActiveChanged(false));
manager.SelectedDocuments = [];
+ Main.Instance.SetTextDoc();
}
}
@@ -48,7 +49,6 @@ export namespace SelectionManager {
manager.DeselectAll();
if (found) manager.SelectDoc(found, false);
- Main.Instance.SetTextDoc(undefined, undefined);
}
export function SelectedDocuments(): Array<DocumentView> {
diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
index ea580d104..7deea3be6 100644
--- a/src/client/util/TooltipTextMenu.scss
+++ b/src/client/util/TooltipTextMenu.scss
@@ -1,4 +1,4 @@
-@import "../views/global_variables";
+@import "../views/globalCssVariables";
.tooltipMenu {
position: absolute;
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index bd5753093..7951e5686 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -12,19 +12,23 @@ const { toggleMark, setBlockType, wrapIn } = require("prosemirror-commands");
import { library } from '@fortawesome/fontawesome-svg-core';
import { wrapInList, bulletList } from 'prosemirror-schema-list';
import { faListUl } from '@fortawesome/free-solid-svg-icons';
+import { FieldViewProps } from "../views/nodes/FieldView";
+import { throwStatement } from "babel-types";
//appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc.
export class TooltipTextMenu {
private tooltip: HTMLElement;
+ private editorProps: FieldViewProps;
- constructor(view: EditorView) {
+ constructor(view: EditorView, editorProps: FieldViewProps) {
+ this.editorProps = editorProps;
this.tooltip = document.createElement("div");
this.tooltip.className = "tooltipMenu";
//add the div which is the tooltip
- view.dom.parentNode!.appendChild(this.tooltip);
+ view.dom.parentNode!.parentNode!.appendChild(this.tooltip);
//add additional icons
library.add(faListUl);
@@ -124,13 +128,13 @@ export class TooltipTextMenu {
// Find a center-ish x position from the selection endpoints (when
// crossing lines, end may be more to the left)
let left = Math.max((start.left + end.left) / 2, start.left + 3);
- this.tooltip.style.left = (left - box.left) + "px";
- let width = Math.abs(start.left - end.left) / 2;
+ this.tooltip.style.left = (left - box.left) * this.editorProps.ScreenToLocalTransform().Scale + "px";
+ let width = Math.abs(start.left - end.left) / 2 * this.editorProps.ScreenToLocalTransform().Scale;
let mid = Math.min(start.left, end.left) + width;
//THIS WIDTH IS 15 * NUMBER OF ICONS + 15
this.tooltip.style.width = 122 + "px";
- this.tooltip.style.bottom = (box.bottom - start.top) + "px";
+ this.tooltip.style.bottom = (box.bottom - start.top) * this.editorProps.ScreenToLocalTransform().Scale + "px";
}
destroy() { this.tooltip.remove(); }
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index f6830d9cd..fe884ca85 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -1,8 +1,8 @@
-@import "global_variables";
+@import "globalCssVariables";
.contextMenu-cont {
position: absolute;
display: flex;
- z-index: 1000;
+ z-index: $contextMenu-zindex;
box-shadow: $intermediate-color 0.2vw 0.2vw 0.4vw;
flex-direction: column;
}
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index c4e4aed8e..c1a949639 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -1,11 +1,14 @@
-@import "global_variables";
+@import "globalCssVariables";
+.documentDecorations {
+ position: absolute;
+}
#documentDecorations-container {
position: absolute;
top: 0;
left:0;
display: grid;
- z-index: 1000;
+ z-index: $docDecorations-zindex;
grid-template-rows: 20px 8px 1fr 8px;
grid-template-columns: 8px 8px 1fr 8px 8px;
pointer-events: none;
@@ -75,6 +78,7 @@
grid-column-end: 6;
pointer-events: all;
text-align: center;
+ cursor: pointer;
}
.documentDecorations-minimizeButton {
background:$alt-accent;
@@ -83,6 +87,7 @@
grid-column-end: 3;
pointer-events: all;
text-align: center;
+ cursor: pointer;
}
.documentDecorations-background {
background: lightblue;
@@ -90,37 +95,6 @@
opacity: 0.1;
}
-// position: absolute;
-// display: grid;
-// z-index: 1000;
-// grid-template-rows: 20px 1fr 20px 0px;
-// grid-template-columns: 20px 1fr 20px;
-// pointer-events: none;
-// #documentDecorations-centerCont {
-// background: none;
-// }
-// .documentDecorations-resizer {
-// pointer-events: auto;
-// background: lightblue;
-// opacity: 0.4;
-// }
-// #documentDecorations-topLeftResizer,
-// #documentDecorations-bottomRightResizer {
-// cursor: nwse-resize;
-// }
-// #documentDecorations-topRightResizer,
-// #documentDecorations-bottomLeftResizer {
-// cursor: nesw-resize;
-// }
-// #documentDecorations-topResizer,
-// #documentDecorations-bottomResizer {
-// cursor: ns-resize;
-// }
-// #documentDecorations-leftResizer,
-// #documentDecorations-rightResizer {
-// cursor: ew-resize;
-// }
-// }
.linkFlyout {
grid-column: 1/4;
margin-left: 25px;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 28af46358..2dc496bc1 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -7,7 +7,7 @@ import { ListField } from "../../fields/ListField";
import { NumberField } from "../../fields/NumberField";
import { Document } from "../../fields/Document";
import { TextField } from "../../fields/TextField";
-import { DragManager } from "../util/DragManager";
+import { DragManager, DragLinksAsDocuments } from "../util/DragManager";
import { SelectionManager } from "../util/SelectionManager";
import { CollectionView } from "./collections/CollectionView";
import './DocumentDecorations.scss';
@@ -15,6 +15,10 @@ import { DocumentView } from "./nodes/DocumentView";
import { LinkMenu } from "./nodes/LinkMenu";
import React = require("react");
import { FieldWaiting } from "../../fields/Field";
+import { emptyFunction } from "../../Utils";
+import { Main } from "./Main";
+import { undo } from "prosemirror-history";
+import { undoBatch } from "../util/UndoManager";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -106,7 +110,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
document.addEventListener("pointerup", this.onBackgroundUp);
this._lastDrag = [e.clientX, e.clientY];
e.stopPropagation();
- e.preventDefault();
+ if (e.currentTarget.localName !== "input") {
+ e.preventDefault();
+ }
}
@action
@@ -152,6 +158,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
if (e.button === 0) {
}
}
+ @undoBatch
@action
onCloseUp = (e: PointerEvent): void => {
e.stopPropagation();
@@ -214,10 +221,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
if (this._linkerButton.current !== null) {
document.removeEventListener("pointermove", this.onLinkerButtonMoved);
document.removeEventListener("pointerup", this.onLinkerButtonUp);
- let dragData = new DragManager.LinkDragData(SelectionManager.SelectedDocuments()[0]);
+ let dragData = new DragManager.LinkDragData(SelectionManager.SelectedDocuments()[0].props.Document);
DragManager.StartLinkDrag(this._linkerButton.current, dragData, e.pageX, e.pageY, {
handlers: {
- dragComplete: action(() => { }),
+ dragComplete: action(emptyFunction),
},
hideSource: false
});
@@ -240,38 +247,15 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
}
onLinkButtonMoved = async (e: PointerEvent) => {
- if (this._linkButton.current !== null) {
+ if (this._linkButton.current !== null && (e.movementX > 1 || e.movementY > 1)) {
document.removeEventListener("pointermove", this.onLinkButtonMoved);
document.removeEventListener("pointerup", this.onLinkButtonUp);
- let sourceDoc = SelectionManager.SelectedDocuments()[0].props.Document;
- let srcTarg = sourceDoc.GetT(KeyStore.Prototype, Document);
- let draggedDocs = (srcTarg && srcTarg !== FieldWaiting) ?
- srcTarg.GetList(KeyStore.LinkedToDocs, [] as Document[]).map(linkDoc =>
- (linkDoc.GetT(KeyStore.LinkedToDocs, Document)) as Document) : [];
- let draggedFromDocs = (srcTarg && srcTarg !== FieldWaiting) ?
- srcTarg.GetList(KeyStore.LinkedFromDocs, [] as Document[]).map(linkDoc =>
- (linkDoc.GetT(KeyStore.LinkedFromDocs, Document)) as Document) : [];
- draggedDocs.push(...draggedFromDocs);
- if (draggedDocs.length) {
- let moddrag = [] as Document[];
- for (const draggedDoc of draggedDocs) {
- let doc = await draggedDoc.GetTAsync(KeyStore.AnnotationOn, Document);
- if (doc) moddrag.push(doc);
- }
- let dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs);
- DragManager.StartDocumentDrag([this._linkButton.current], dragData, e.x, e.y, {
- handlers: {
- dragComplete: action(() => { }),
- },
- hideSource: false
- });
- }
+ DragLinksAsDocuments(this._linkButton.current, e.x, e.y, SelectionManager.SelectedDocuments()[0].props.Document);
}
e.stopPropagation();
}
-
onPointerMove = (e: PointerEvent): void => {
e.stopPropagation();
e.preventDefault();
@@ -320,6 +304,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
break;
}
+ Main.Instance.SetTextDoc();
SelectionManager.SelectedDocuments().forEach(element => {
const rect = element.screenRect();
if (rect.width !== 0) {
@@ -424,7 +409,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
opacity: this._opacity
}}>
<div className="documentDecorations-minimizeButton" onPointerDown={this.onMinimizeDown}>...</div>
- <input ref={this.keyinput} className="title" type="text" name="dynbox" value={this.getValue()} onChange={this.handleChange} onPointerDown={this.onPointerDown} onKeyPress={this.enterPressed} />
+ <input ref={this.keyinput} className="title" type="text" name="dynbox" value={this.getValue()} onChange={this.handleChange} onPointerDown={this.onBackgroundDown} onKeyPress={this.enterPressed} />
<div className="documentDecorations-closeButton" onPointerDown={this.onCloseDown}>X</div>
<div id="documentDecorations-topLeftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
<div id="documentDecorations-topResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
diff --git a/src/client/views/InkingCanvas.scss b/src/client/views/InkingCanvas.scss
index 42ae38c73..2c550051c 100644
--- a/src/client/views/InkingCanvas.scss
+++ b/src/client/views/InkingCanvas.scss
@@ -1,4 +1,4 @@
-@import "global_variables";
+@import "globalCssVariables";
.inkingCanvas {
opacity:0.99;
diff --git a/src/client/views/InkingControl.scss b/src/client/views/InkingControl.scss
index 0d8fd8784..ba4ec41af 100644
--- a/src/client/views/InkingControl.scss
+++ b/src/client/views/InkingControl.scss
@@ -1,4 +1,4 @@
-@import "global_variables";
+@import "globalCssVariables";
.inking-control {
position: absolute;
left: 70px;
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index fe7f007b0..13cadb10d 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -1,4 +1,4 @@
-@import "global_variables";
+@import "globalCssVariables";
@import "nodeModuleOverrides";
html,
body {
@@ -169,20 +169,21 @@ button:hover {
overflow: scroll;
}
.mainDiv-textInput {
- background:pink;
- width: 200px;
- height: 200px;
+ background-color: rgba(248, 6, 6, 0.001);
+ width: 200px;
+ height: 200px;
+ position:absolute;
+ overflow: visible;
+ top: 0;
+ left: 0;
+ z-index: $mainTextInput-zindex;
+ .formattedTextBox-cont {
+ background-color: rgba(248, 6, 6, 0.001);
+ width: 100%;
+ height: 100%;
position:absolute;
- overflow: visible;
top: 0;
left: 0;
- .formattedTextBox-cont {
- background:pink;
- width: 100%;
- height: 100%;
- position:absolute;
- top: 0;
- left: 0;
}
}
#mainContent-div {
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 32798631d..ed61aa5a7 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -15,7 +15,7 @@ import { ListField } from '../../fields/ListField';
import { WorkspacesMenu } from '../../server/authentication/controllers/WorkspacesMenu';
import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
import { MessageStore } from '../../server/Message';
-import { Utils, returnTrue, emptyFunction } from '../../Utils';
+import { Utils, returnTrue, emptyFunction, emptyDocFunction } from '../../Utils';
import * as rp from 'request-promise';
import { RouteStore } from '../../server/RouteStore';
import { ServerUtils } from '../../server/ServerUtil';
@@ -23,12 +23,12 @@ import { Documents } from '../documents/Documents';
import { ColumnAttributeModel } from '../northstar/core/attribute/AttributeModel';
import { AttributeTransformationModel } from '../northstar/core/attribute/AttributeTransformationModel';
import { Gateway, Settings } from '../northstar/manager/Gateway';
-import { AggregateFunction, Catalog } from '../northstar/model/idea/idea';
+import { AggregateFunction, Catalog, Point } from '../northstar/model/idea/idea';
import '../northstar/model/ModelExtensions';
import { HistogramOperation } from '../northstar/operations/HistogramOperation';
import '../northstar/utils/Extensions';
import { Server } from '../Server';
-import { setupDrag } from '../util/DragManager';
+import { SetupDrag, DragManager } from '../util/DragManager';
import { Transform } from '../util/Transform';
import { UndoManager } from '../util/UndoManager';
import { CollectionDockingView } from './collections/CollectionDockingView';
@@ -38,6 +38,10 @@ import { InkingControl } from './InkingControl';
import "./Main.scss";
import { DocumentView } from './nodes/DocumentView';
import { FormattedTextBox } from './nodes/FormattedTextBox';
+import { REPLCommand } from 'repl';
+import { Key } from '../../fields/Key';
+import { PreviewCursor } from './PreviewCursor';
+
@observer
export class Main extends React.Component {
@@ -66,6 +70,7 @@ export class Main extends React.Component {
constructor(props: Readonly<{}>) {
super(props);
+ this._textProxyDiv = React.createRef();
Main.Instance = this;
// causes errors to be generated when modifying an observable outside of an action
configure({ enforceActions: "observed" });
@@ -199,19 +204,70 @@ export class Main extends React.Component {
pwidthFunc = () => this.pwidth;
pheightFunc = () => this.pheight;
- focusDocument = (doc: Document) => { };
noScaling = () => 1;
@observable _textDoc?: Document = undefined;
_textRect: any;
+ _textXf: Transform = Transform.Identity();
+ _textScroll: number = 0;
+ _textFieldKey: Key = KeyStore.Data;
+ _textColor: string | null = null;
+ _textTargetDiv: HTMLDivElement | undefined;
+ _textProxyDiv: React.RefObject<HTMLDivElement>;
@action
- SetTextDoc(textDoc?: Document, div?: HTMLDivElement) {
+ SetTextDoc(textDoc?: Document, textFieldKey?: Key, div?: HTMLDivElement, tx?: Transform) {
+ if (this._textTargetDiv) {
+ this._textTargetDiv.style.color = this._textColor;
+ }
+
this._textDoc = undefined;
this._textDoc = textDoc;
+ this._textFieldKey = textFieldKey!;
+ this._textXf = tx ? tx : Transform.Identity();
+ this._textTargetDiv = div;
if (div) {
+ this._textColor = div.style.color;
+ div.style.color = "transparent";
this._textRect = div.getBoundingClientRect();
+ this._textScroll = div.scrollTop;
+ }
+ }
+
+ @action
+ textScroll = (e: React.UIEvent) => {
+ if (this._textProxyDiv.current && this._textTargetDiv) {
+ this._textTargetDiv.scrollTop = this._textScroll = this._textProxyDiv.current.children[0].scrollTop;
+ }
+ }
+
+ textBoxDown = (e: React.PointerEvent) => {
+ if (e.button !== 0 || e.metaKey || e.altKey) {
+ document.addEventListener("pointermove", this.textBoxMove);
+ document.addEventListener('pointerup', this.textBoxUp);
}
}
+ textBoxMove = (e: PointerEvent) => {
+ if (e.movementX > 1 || e.movementY > 1) {
+ document.removeEventListener("pointermove", this.textBoxMove);
+ document.removeEventListener('pointerup', this.textBoxUp);
+ let dragData = new DragManager.DocumentDragData([this._textDoc!]);
+ const [left, top] = this._textXf
+ .inverse()
+ .transformPoint(0, 0);
+ dragData.xOffset = e.clientX - left;
+ dragData.yOffset = e.clientY - top;
+ DragManager.StartDocumentDrag([this._textTargetDiv!], dragData, e.clientX, e.clientY, {
+ handlers: {
+ dragComplete: action(emptyFunction),
+ },
+ hideSource: false
+ });
+ }
+ }
+ textBoxUp = (e: PointerEvent) => {
+ document.removeEventListener("pointermove", this.textBoxMove);
+ document.removeEventListener('pointerup', this.textBoxUp);
+ }
@computed
get activeTextBox() {
@@ -220,8 +276,14 @@ export class Main extends React.Component {
let y: number = this._textRect.y;
let w: number = this._textRect.width;
let h: number = this._textRect.height;
- return <div className="mainDiv-textInput" style={{ transform: `translate(${x}px, ${y}px)`, width: `${w}px`, height: `${h}px` }} >
- <FormattedTextBox fieldKey={KeyStore.Archives} Document={this._textDoc} isSelected={returnTrue} select={emptyFunction} isTopMost={true} selectOnLoad={true} onActiveChanged={emptyFunction} active={returnTrue} ScreenToLocalTransform={Transform.Identity} focus={(doc) => { }} />
+ let t = this._textXf.transformPoint(0, 0);
+ let s = this._textXf.transformPoint(1, 0);
+ s[0] = Math.sqrt((s[0] - t[0]) * (s[0] - t[0]) + (s[1] - t[1]) * (s[1] - t[1]));
+ return <div className="mainDiv-textInput" style={{ pointerEvents: "none", transform: `translate(${x}px, ${y}px) scale(${1 / s[0]},${1 / s[0]})`, width: "auto", height: "auto" }} >
+ <div className="mainDiv-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll} style={{ pointerEvents: "none", transform: `scale(${1}, ${1})`, width: `${w * s[0]}px`, height: `${h * s[0]}px` }}>
+ <FormattedTextBox fieldKey={this._textFieldKey!} isOverlay={true} Document={this._textDoc} isSelected={returnTrue} select={emptyFunction} isTopMost={true}
+ selectOnLoad={true} onActiveChanged={emptyFunction} active={returnTrue} ScreenToLocalTransform={() => this._textXf} focus={emptyDocFunction} />
+ </div>
</ div>;
}
else return (null);
@@ -239,7 +301,7 @@ export class Main extends React.Component {
PanelHeight={this.pheightFunc}
isTopMost={true}
selectOnLoad={false}
- focus={this.focusDocument}
+ focus={emptyDocFunction}
parentActive={returnTrue}
onActiveChanged={emptyFunction}
ContainingCollectionView={undefined} />;
@@ -284,7 +346,7 @@ export class Main extends React.Component {
<ul id="add-options-list">
{btns.map(btn =>
<li key={btn[1]} ><div ref={btn[0]}>
- <button className="round-button add-button" title={btn[2]} onPointerDown={setupDrag(btn[0], btn[3])}>
+ <button className="round-button add-button" title={btn[2]} onPointerDown={SetupDrag(btn[0], btn[3])}>
<FontAwesomeIcon icon={btn[1]} size="sm" />
</button>
</div></li>)}
@@ -310,7 +372,7 @@ export class Main extends React.Component {
<div className="main-buttonDiv" key="workspaces" style={{ top: '34px', left: '2px', position: 'absolute' }} ref={workspacesRef}>
<button onClick={this.toggleWorkspaces}>Workspaces</button></div>,
<div className="main-buttonDiv" key="logout" style={{ top: '34px', right: '1px', position: 'absolute' }} ref={logoutRef}>
- <button onClick={() => request.get(ServerUtils.prepend(RouteStore.logout), () => { })}>Log Out</button></div>
+ <button onClick={() => request.get(ServerUtils.prepend(RouteStore.logout), emptyFunction)}>Log Out</button></div>
];
}
@@ -332,6 +394,7 @@ export class Main extends React.Component {
{({ measureRef }) =>
<div ref={measureRef} id="mainContent-div">
{this.mainContent}
+ <PreviewCursor />
</div>
}
</Measure>
@@ -351,11 +414,11 @@ export class Main extends React.Component {
@action AddToNorthstarCatalog(ctlog: Catalog) {
CurrentUserUtils.NorthstarDBCatalog = CurrentUserUtils.NorthstarDBCatalog ? CurrentUserUtils.NorthstarDBCatalog : ctlog;
if (ctlog && ctlog.schemas) {
- this._northstarSchemas.push(...ctlog.schemas.map(schema => {
- let schemaDoc = Documents.TreeDocument([], { width: 50, height: 100, title: schema.displayName! });
- let schemaDocuments = schemaDoc.GetList(KeyStore.Data, [] as Document[]);
+ ctlog.schemas.map(schema => {
+ let promises: Promise<void>[] = [];
+ let schemaDocuments: Document[] = [];
CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => {
- Server.GetField(attr.displayName! + ".alias", action((field: Opt<Field>) => {
+ let prom = Server.GetField(attr.displayName! + ".alias").then(action((field: Opt<Field>) => {
if (field instanceof Document) {
schemaDocuments.push(field);
} else {
@@ -367,9 +430,13 @@ export class Main extends React.Component {
schemaDocuments.push(Documents.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName! }, undefined, attr.displayName! + ".alias"));
}
}));
+ promises.push(prom);
});
- return schemaDoc;
- }));
+ Promise.all(promises).finally(() => {
+ let schemaDoc = Documents.TreeDocument(schemaDocuments, { width: 50, height: 100, title: schema.displayName! });
+ this._northstarSchemas.push(schemaDoc);
+ });
+ });
}
}
async initializeNorthstar(): Promise<void> {
@@ -384,8 +451,8 @@ export class Main extends React.Component {
let cat = Gateway.Instance.ClearCatalog();
cat.then(async () => {
this.AddToNorthstarCatalog(await Gateway.Instance.GetCatalog());
- if (!CurrentUserUtils.GetNorthstarSchema("Book1"))
- this.AddToNorthstarCatalog(await Gateway.Instance.GetSchema("http://www.cs.brown.edu/~bcz/Book1.csv"));
+ // if (!CurrentUserUtils.GetNorthstarSchema("Book1"))
+ // this.AddToNorthstarCatalog(await Gateway.Instance.GetSchema("http://www.cs.brown.edu/~bcz/Book1.csv", "Book1"));
});
}
diff --git a/src/client/views/PreviewCursor.scss b/src/client/views/PreviewCursor.scss
new file mode 100644
index 000000000..20f9b9a49
--- /dev/null
+++ b/src/client/views/PreviewCursor.scss
@@ -0,0 +1,9 @@
+
+.previewCursor {
+ color: black;
+ position: absolute;
+ transform-origin: left top;
+ top: 0;
+ left:0;
+ pointer-events: none;
+} \ No newline at end of file
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
new file mode 100644
index 000000000..ff8434681
--- /dev/null
+++ b/src/client/views/PreviewCursor.tsx
@@ -0,0 +1,37 @@
+import { action, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import "normalize.css";
+import * as React from 'react';
+import "./PreviewCursor.scss";
+
+@observer
+export class PreviewCursor extends React.Component<{}> {
+ private _prompt = React.createRef<HTMLDivElement>();
+ //when focus is lost, this will remove the preview cursor
+ @action onBlur = (): void => {
+ PreviewCursor.Visible = false;
+ PreviewCursor.hide();
+ }
+
+ @observable static clickPoint = [0, 0];
+ @observable public static Visible = false;
+ @observable public static hide = () => { };
+ @action
+ public static Show(hide: any, x: number, y: number) {
+ this.clickPoint = [x, y];
+ this.hide = hide;
+ setTimeout(action(() => this.Visible = true), (1));
+ }
+ render() {
+ if (!PreviewCursor.clickPoint) {
+ return (null);
+ }
+ if (PreviewCursor.Visible && this._prompt.current) {
+ this._prompt.current.focus();
+ }
+ return <div className="previewCursor" id="previewCursor" onBlur={this.onBlur} tabIndex={0} ref={this._prompt}
+ style={{ transform: `translate(${PreviewCursor.clickPoint[0]}px, ${PreviewCursor.clickPoint[1]}px)`, opacity: PreviewCursor.Visible ? 1 : 0 }}>
+ I
+ </div >;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/_global_variables.scss b/src/client/views/_global_variables.scss
deleted file mode 100644
index 44a819b79..000000000
--- a/src/client/views/_global_variables.scss
+++ /dev/null
@@ -1,17 +0,0 @@
-@import url("https://fonts.googleapis.com/css?family=Noto+Sans:400,700|Crimson+Text:400,400i,700");
-// colors
-$light-color: #fcfbf7;
-$light-color-secondary: rgb(241, 239, 235);
-$main-accent: #61aaa3;
-// $alt-accent: #cdd5ec;
-// $alt-accent: #cdeceb;
-$alt-accent: #59dff7;
-$lighter-alt-accent: rgb(207, 220, 240);
-$intermediate-color: #9c9396;
-$dark-color: #121721;
-// fonts
-$sans-serif: "Noto Sans", sans-serif;
-// $sans-serif: "Roboto Slab", sans-serif;
-$serif: "Crimson Text", serif;
-// misc values
-$border-radius: 0.3em;
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 4380c8194..b5eaab349 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -32,19 +32,18 @@ export interface CollectionViewProps extends FieldViewProps {
contentRef?: React.Ref<HTMLDivElement>;
}
-export const COLLECTION_BORDER_WIDTH = 1;
@observer
export class CollectionBaseView extends React.Component<CollectionViewProps> {
- get collectionViewType(): CollectionViewType {
+ get collectionViewType(): CollectionViewType | undefined {
let Document = this.props.Document;
let viewField = Document.GetT(KeyStore.ViewType, NumberField);
if (viewField === FieldWaiting) {
- return CollectionViewType.Invalid;
+ return undefined;
} else if (viewField) {
return viewField.Data;
} else {
- return CollectionViewType.Freeform;
+ return CollectionViewType.Invalid;
}
}
@@ -107,15 +106,18 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
const field = new ListField([doc]);
// const script = CompileScript(`
// if(added) {
- // console.log("added " + field.Title);
+ // console.log("added " + field.Title + " " + doc.Title);
// } else {
- // console.log("removed " + field.Title);
+ // console.log("removed " + field.Title + " " + doc.Title);
// }
// `, {
// addReturn: false,
// params: {
// field: Document.name,
// added: "boolean"
+ // },
+ // capturedVariables: {
+ // doc: this.props.Document
// }
// });
// if (script.compiled) {
@@ -177,9 +179,10 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
active: this.active,
onActiveChanged: this.onActiveChanged,
};
+ const viewtype = this.collectionViewType;
return (
<div className={this.props.className || "collectionView-cont"} onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}>
- {this.props.children(this.collectionViewType, props)}
+ {viewtype !== undefined ? this.props.children(viewtype, props) : (null)}
</div>
);
}
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index 583d50c5b..0e7e0afa7 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -1,8 +1,29 @@
+@import "../../views/globalCssVariables.scss";
+
.collectiondockingview-content {
height: 100%;
}
+.lm_active .messageCounter{
+ color:white;
+ background: #999999;
+}
+.messageCounter {
+ width:18px;
+ height:20px;
+ text-align: center;
+ border-radius: 20px;
+ margin-left: 5px;
+ transform: translate(0px, -8px);
+ display: inline-block;
+ background: transparent;
+ border: 1px #999999 solid;
+}
.collectiondockingview-container {
+ width: 100%;
+ height: 100%;
+ border-style: solid;
+ border-width: $COLLECTION_BORDER_WIDTH;
position: absolute;
top: 0;
left: 0;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index ea6d3a247..eb1cd1c09 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -7,18 +7,18 @@ import * as ReactDOM from 'react-dom';
import { Document } from "../../../fields/Document";
import { KeyStore } from "../../../fields/KeyStore";
import Measure from "react-measure";
-import { FieldId, Opt, Field } from "../../../fields/Field";
-import { Utils, returnTrue, emptyFunction } from "../../../Utils";
+import { FieldId, Opt, Field, FieldWaiting } from "../../../fields/Field";
+import { Utils, returnTrue, emptyFunction, emptyDocFunction } from "../../../Utils";
import { Server } from "../../Server";
import { undoBatch } from "../../util/UndoManager";
import { DocumentView } from "../nodes/DocumentView";
import "./CollectionDockingView.scss";
-import { COLLECTION_BORDER_WIDTH } from "./CollectionBaseView";
import React = require("react");
import { SubCollectionViewProps } from "./CollectionSubView";
import { ServerUtils } from "../../../server/ServerUtil";
-import { DragManager } from "../../util/DragManager";
+import { DragManager, DragLinksAsDocuments } from "../../util/DragManager";
import { TextField } from "../../../fields/TextField";
+import { ListField } from "../../../fields/ListField";
@observer
export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
@@ -50,7 +50,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
public StartOtherDrag(dragDocs: Document[], e: any) {
dragDocs.map(dragDoc =>
this.AddRightSplit(dragDoc, true).contentItems[0].tab._dragListener.
- onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: () => { }, button: 0 }));
+ onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 }));
}
@action
@@ -194,23 +194,35 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
@action
onPointerDown = (e: React.PointerEvent): void => {
var className = (e.target as any).className;
- if ((className === "lm_title" || className === "lm_tab lm_active") && (e.ctrlKey || e.altKey)) {
+ if (className === "messageCounter") {
e.stopPropagation();
e.preventDefault();
+ let x = e.clientX;
+ let y = e.clientY;
let docid = (e.target as any).DashDocId;
let tab = (e.target as any).parentElement as HTMLElement;
- Server.GetField(docid, action((f: Opt<Field>) => {
- if (f instanceof Document) {
- DragManager.StartDocumentDrag([tab], new DragManager.DocumentDragData([f]), e.pageX, e.pageY,
- {
- handlers: {
- dragComplete: action(() => { }),
- },
- hideSource: false
- });
- }
- }));
- }
+ Server.GetField(docid, action(async (sourceDoc: Opt<Field>) =>
+ (sourceDoc instanceof Document) && DragLinksAsDocuments(tab, x, y, sourceDoc)));
+ } else
+ if ((className === "lm_title" || className === "lm_tab lm_active") && !e.shiftKey) {
+ e.stopPropagation();
+ e.preventDefault();
+ let x = e.clientX;
+ let y = e.clientY;
+ let docid = (e.target as any).DashDocId;
+ let tab = (e.target as any).parentElement as HTMLElement;
+ Server.GetField(docid, action((f: Opt<Field>) => {
+ if (f instanceof Document) {
+ DragManager.StartDocumentDrag([tab], new DragManager.DocumentDragData([f]), x, y,
+ {
+ handlers: {
+ dragComplete: action(emptyFunction),
+ },
+ hideSource: false
+ });
+ }
+ }));
+ }
if (className === "lm_drag_handle" || className === "lm_close" || className === "lm_maximise" || className === "lm_minimise" || className === "lm_close_tab") {
this._flush = true;
}
@@ -229,24 +241,44 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
this.stateChanged();
}
+ htmlToElement(html: string) {
+ var template = document.createElement('template');
+ html = html.trim(); // Never return a text node of whitespace as the result
+ template.innerHTML = html;
+ return template.content.firstChild;
+ }
+
tabCreated = (tab: any) => {
if (tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") {
- if (tab.titleElement[0].textContent.indexOf("-waiting") !== -1) {
- Server.GetField(tab.contentItem.config.props.documentId, action((f: Opt<Field>) => {
- if (f !== undefined && f instanceof Document) {
- f.GetTAsync(KeyStore.Title, TextField, (tfield) => {
- if (tfield !== undefined) {
- tab.titleElement[0].textContent = f.Title;
- }
- });
- }
- }));
- tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId;
- }
- tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId;
+ Server.GetField(tab.contentItem.config.props.documentId, action((f: Opt<Field>) => {
+ if (f !== undefined && f instanceof Document) {
+ f.GetTAsync(KeyStore.Title, TextField, (tfield) => {
+ if (tfield !== undefined) {
+ tab.titleElement[0].textContent = f.Title;
+ }
+ });
+ f.GetTAsync(KeyStore.LinkedFromDocs, ListField).then(lf =>
+ f.GetTAsync(KeyStore.LinkedToDocs, ListField).then(lt => {
+ let count = (lf ? lf.Data.length : 0) + (lt ? lt.Data.length : 0);
+ let counter: any = this.htmlToElement(`<div class="messageCounter">${count}</div>`);
+ tab.element.append(counter);
+ counter.DashDocId = tab.contentItem.config.props.documentId;
+ (tab as any).reactionDisposer = reaction(() => [f.GetT(KeyStore.LinkedFromDocs, ListField), f.GetT(KeyStore.LinkedToDocs, ListField)],
+ (lists) => {
+ let count = (lists.length > 0 && lists[0] && lists[0]!.Data ? lists[0]!.Data.length : 0) +
+ (lists.length > 1 && lists[1] && lists[1]!.Data ? lists[1]!.Data.length : 0);
+ counter.innerHTML = count;
+ });
+ }));
+ tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId;
+ }
+ }));
}
tab.closeElement.off('click') //unbind the current click handler
.click(function () {
+ if (tab.reactionDisposer) {
+ tab.reactionDisposer();
+ }
tab.contentItem.remove();
});
}
@@ -271,13 +303,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
render() {
return (
<div className="collectiondockingview-container" id="menuContainer"
- onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef}
- style={{
- width: "100%",
- height: "100%",
- borderStyle: "solid",
- borderWidth: `${COLLECTION_BORDER_WIDTH}px`,
- }} />
+ onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef} />
);
}
}
@@ -325,7 +351,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
selectOnLoad={false}
parentActive={returnTrue}
onActiveChanged={emptyFunction}
- focus={(doc: Document) => { }}
+ focus={emptyDocFunction}
ContainingCollectionView={undefined} />
</div>;
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index 97bac745c..6cbe59012 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -7,6 +7,7 @@ import React = require("react");
import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
import { FieldView, FieldViewProps } from "../nodes/FieldView";
import { CollectionRenderProps, CollectionBaseView, CollectionViewType } from "./CollectionBaseView";
+import { emptyFunction } from "../../../Utils";
@observer
@@ -33,7 +34,7 @@ export class CollectionPDFView extends React.Component<FieldViewProps> {
onContextMenu = (e: React.MouseEvent): void => {
if (!e.isPropagationStopped() && this.props.Document.Id !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
- ContextMenu.Instance.addItem({ description: "PDFOptions", event: () => { } });
+ ContextMenu.Instance.addItem({ description: "PDFOptions", event: emptyFunction });
}
}
diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss
index c3a2e88ac..40e49bb5f 100644
--- a/src/client/views/collections/CollectionSchemaView.scss
+++ b/src/client/views/collections/CollectionSchemaView.scss
@@ -1,4 +1,4 @@
-@import "../global_variables";
+@import "../globalCssVariables";
//options menu styling
#schemaOptionsMenuBtn {
@@ -53,7 +53,9 @@
.collectionSchemaView-container {
- border: 1px solid $intermediate-color;
+ border-width: $COLLECTION_BORDER_WIDTH;
+ border-color : $intermediate-color;
+ border-style: solid;
border-radius: $border-radius;
box-sizing: border-box;
position: absolute;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 587f60b3d..fdb82690a 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -2,31 +2,29 @@ import React = require("react");
import { library } from '@fortawesome/fontawesome-svg-core';
import { faCog, faPlus } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, trace, untracked } from "mobx";
+import { action, computed, observable, untracked } from "mobx";
import { observer } from "mobx-react";
import Measure from "react-measure";
import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults } from "react-table";
import "react-table/react-table.css";
import { Document } from "../../../fields/Document";
-import { Field, Opt, FieldWaiting } from "../../../fields/Field";
+import { Field, Opt } from "../../../fields/Field";
import { Key } from "../../../fields/Key";
import { KeyStore } from "../../../fields/KeyStore";
import { ListField } from "../../../fields/ListField";
+import { emptyDocFunction, emptyFunction, returnFalse } from "../../../Utils";
import { Server } from "../../Server";
-import { setupDrag } from "../../util/DragManager";
+import { SetupDrag } from "../../util/DragManager";
import { CompileScript, ToField } from "../../util/Scripting";
import { Transform } from "../../util/Transform";
+import { COLLECTION_BORDER_WIDTH } from "../../views/globalCssVariables.scss";
import { anchorPoints, Flyout } from "../DocumentDecorations";
import '../DocumentDecorations.scss';
import { EditableView } from "../EditableView";
import { DocumentView } from "../nodes/DocumentView";
import { FieldView, FieldViewProps } from "../nodes/FieldView";
import "./CollectionSchemaView.scss";
-import { CollectionView } from "./CollectionView";
import { CollectionSubView } from "./CollectionSubView";
-import { TextField } from "../../../fields/TextField";
-import { COLLECTION_BORDER_WIDTH } from "./CollectionBaseView";
-import { emptyFunction, returnFalse } from "../../../Utils";
// bcz: need to add drag and drop of rows and columns. This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657
@@ -77,12 +75,12 @@ export class CollectionSchemaView extends CollectionSubView {
let props: FieldViewProps = {
Document: rowProps.value[0],
fieldKey: rowProps.value[1],
- isSelected: () => false,
- select: () => { },
+ isSelected: returnFalse,
+ select: emptyFunction,
isTopMost: false,
selectOnLoad: false,
ScreenToLocalTransform: Transform.Identity,
- focus: emptyFunction,
+ focus: emptyDocFunction,
active: returnFalse,
onActiveChanged: emptyFunction,
};
@@ -90,7 +88,7 @@ export class CollectionSchemaView extends CollectionSubView {
<FieldView {...props} />
);
let reference = React.createRef<HTMLDivElement>();
- let onItemDown = setupDrag(reference, () => props.Document, this.props.moveDocument);
+ let onItemDown = SetupDrag(reference, () => props.Document, this.props.moveDocument);
let applyToDoc = (doc: Document, run: (args?: { [name: string]: any }) => any) => {
const res = run({ this: doc });
if (!res.success) return false;
@@ -245,16 +243,16 @@ export class CollectionSchemaView extends CollectionSubView {
this._contentScaling = r.entry.width / selected!.GetNumber(KeyStore.NativeWidth, r.entry.width);
}
+ @computed
+ get borderWidth() { return COLLECTION_BORDER_WIDTH; }
getContentScaling = (): number => this._contentScaling;
getPanelWidth = (): number => this._panelWidth;
getPanelHeight = (): number => this._panelHeight;
- getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling);
- getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX - this._tableWidth, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling);
-
- focusDocument = (doc: Document) => { };
+ getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- this.borderWidth - this.DIVIDER_WIDTH - this._dividerX, - this.borderWidth).scale(1 / this._contentScaling);
+ getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- this.borderWidth - this.DIVIDER_WIDTH - this._dividerX - this._tableWidth, - this.borderWidth).scale(1 / this._contentScaling);
onPointerDown = (e: React.PointerEvent): void => {
- if (this.props.isSelected()) {
+ if (e.button === 1 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) {
e.stopPropagation();
}
}
@@ -315,7 +313,7 @@ export class CollectionSchemaView extends CollectionSubView {
PanelWidth={this.getPanelWidth}
PanelHeight={this.getPanelHeight}
ContainingCollectionView={undefined}
- focus={this.focusDocument}
+ focus={emptyDocFunction}
parentActive={this.props.active}
onActiveChanged={this.props.onActiveChanged} /> : null}
<input value={this.previewScript} onChange={this.onPreviewScriptChange}
@@ -349,7 +347,7 @@ export class CollectionSchemaView extends CollectionSubView {
</Flyout>);
return (
- <div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} onWheel={this.onWheel} ref={this._mainCont} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} >
+ <div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} onWheel={this.onWheel} ref={this._mainCont}>
<div className="collectionSchemaView-dropTarget" onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}>
<Measure onResize={this.setTableDimensions}>
{({ measureRef }) =>
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 5cdea0568..d91db68bb 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -15,6 +15,7 @@ import { ServerUtils } from "../../../server/ServerUtil";
import { Server } from "../../Server";
import { FieldViewProps } from "../nodes/FieldView";
import * as rp from 'request-promise';
+import { emptyFunction } from "../../../Utils";
export interface CollectionViewProps extends FieldViewProps {
addDocument: (document: Document, allowDuplicates?: boolean) => boolean;
@@ -74,12 +75,21 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
}
let added = false;
if (de.data.aliasOnDrop || de.data.copyOnDrop) {
- added = de.data.droppedDocuments.reduce((added: boolean, d) => added || this.props.addDocument(d), false);
+ added = de.data.droppedDocuments.reduce((added: boolean, d) => {
+ let moved = this.props.addDocument(d);
+ return moved || added;
+ }, false);
} else if (de.data.moveDocument) {
const move = de.data.moveDocument;
- added = de.data.droppedDocuments.reduce((added: boolean, d) => added || move(d, this.props.Document, this.props.addDocument), false);
+ added = de.data.droppedDocuments.reduce((added: boolean, d) => {
+ let moved = move(d, this.props.Document, this.props.addDocument);
+ return moved || added;
+ }, false);
} else {
- added = de.data.droppedDocuments.reduce((added: boolean, d) => added || this.props.addDocument(d), false);
+ added = de.data.droppedDocuments.reduce((added: boolean, d) => {
+ let moved = this.props.addDocument(d);
+ return moved || added;
+ }, false);
}
e.stopPropagation();
return added;
@@ -87,8 +97,8 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
return false;
}
- protected getDocumentFromType(type: string, path: string, options: DocumentOptions): Opt<Document> {
- let ctor: ((path: string, options: DocumentOptions) => Document) | undefined;
+ protected async getDocumentFromType(type: string, path: string, options: DocumentOptions): Promise<Opt<Document>> {
+ let ctor: ((path: string, options: DocumentOptions) => (Document | Promise<Document | undefined>)) | undefined = undefined;
if (type.indexOf("image") !== -1) {
ctor = Documents.ImageDocument;
}
@@ -102,6 +112,10 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
ctor = Documents.PdfDocument;
options.nativeWidth = 1200;
}
+ if (type.indexOf("excel") !== -1) {
+ ctor = Documents.DBDocument;
+ options.copyDraggedItems = true;
+ }
if (type.indexOf("html") !== -1) {
if (path.includes('localhost')) {
let s = path.split('/');
@@ -152,21 +166,16 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
let item = e.dataTransfer.items[i];
if (item.kind === "string" && item.type.indexOf("uri") !== -1) {
let str: string;
- let prom = new Promise<string>(res =>
- e.dataTransfer.items[i].getAsString(res)).then(action((s: string) => {
- str = s;
- return rp.head(ServerUtils.prepend(RouteStore.corsProxy + "/" + s));
- })).then(res => {
- let type = res.headers["content-type"];
+ let prom = new Promise<string>(resolve => e.dataTransfer.items[i].getAsString(resolve))
+ .then(action((s: string) => rp.head(ServerUtils.prepend(RouteStore.corsProxy + "/" + (str = s)))))
+ .then(result => {
+ let type = result.headers["content-type"];
if (type) {
- let doc = this.getDocumentFromType(type, str, { ...options, width: 300, nativeWidth: 300 });
- if (doc) {
- this.props.addDocument(doc, false);
- }
+ this.getDocumentFromType(type, str, { ...options, width: 300, nativeWidth: 300 })
+ .then(doc => doc && this.props.addDocument(doc, false));
}
});
promises.push(prom);
- // this.props.addDocument(Documents.WebDocument(s, { ...options, width: 300, height: 300 }), false)
}
let type = item.type;
if (item.kind === "file") {
@@ -176,17 +185,17 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
if (file) {
formData.append('file', file);
}
+ let dropFileName = file ? file.name : "-empty-";
let prom = fetch(upload, {
method: 'POST',
body: formData
}).then(async (res: Response) => {
- const json = await res.json();
- json.map((file: any) => {
+ (await res.json()).map(action((file: any) => {
let path = window.location.origin + file;
- runInAction(() => {
- let doc = this.getDocumentFromType(type, path, { ...options, nativeWidth: 300, width: 300 });
+ let docPromise = this.getDocumentFromType(type, path, { ...options, nativeWidth: 300, width: 300, title: dropFileName });
+ docPromise.then(action((doc?: Document) => {
let docs = this.props.Document.GetT(KeyStore.Data, ListField);
if (docs !== FieldWaiting) {
if (!docs) {
@@ -197,15 +206,15 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
docs.Data.push(doc);
}
}
- });
- });
+ }));
+ }));
});
promises.push(prom);
}
}
if (promises.length) {
- Promise.all(promises).catch(() => { }).then(() => batch.end());
+ Promise.all(promises).finally(() => batch.end());
} else {
batch.end();
}
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index f2affbf55..973eead97 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -1,7 +1,9 @@
-@import "../global_variables";
+@import "../globalCssVariables";
.collectionTreeView-dropTarget {
- border: 0px solid transparent;
+ border-width: $COLLECTION_BORDER_WIDTH;
+ border-color: transparent;
+ border-style: solid;
border-radius: $border-radius;
box-sizing: border-box;
height: 100%;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 659cff9fe..51a02fc25 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -7,13 +7,13 @@ import { Document } from "../../../fields/Document";
import { FieldWaiting } from "../../../fields/Field";
import { KeyStore } from "../../../fields/KeyStore";
import { ListField } from "../../../fields/ListField";
-import { setupDrag, DragManager } from "../../util/DragManager";
+import { SetupDrag, DragManager } from "../../util/DragManager";
import { EditableView } from "../EditableView";
import "./CollectionTreeView.scss";
import { CollectionView } from "./CollectionView";
+import * as globalCssVariables from "../../views/globalCssVariables.scss";
import { CollectionSubView } from "./CollectionSubView";
import React = require("react");
-import { COLLECTION_BORDER_WIDTH } from './CollectionBaseView';
import { props } from 'bluebird';
@@ -77,7 +77,7 @@ class TreeView extends React.Component<TreeViewProps> {
*/
renderTitle() {
let reference = React.createRef<HTMLDivElement>();
- let onItemDown = setupDrag(reference, () => this.props.document, this.props.moveDocument, this.props.copyOnDrag);
+ let onItemDown = SetupDrag(reference, () => this.props.document, this.props.moveDocument, this.props.copyOnDrag);
let editableView = (titleString: string) =>
(<EditableView
display={"inline"}
@@ -139,7 +139,7 @@ export class CollectionTreeView extends CollectionSubView {
);
return (
- <div id="body" className="collectionTreeView-dropTarget" onWheel={(e: React.WheelEvent) => e.stopPropagation()} onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}>
+ <div id="body" className="collectionTreeView-dropTarget" onWheel={(e: React.WheelEvent) => e.stopPropagation()} onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}>
<div className="coll-title">
<EditableView
contents={this.props.Document.Title}
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index b02983a2e..6c9780adb 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -7,6 +7,7 @@ import React = require("react");
import "./CollectionVideoView.scss";
import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
import { FieldView, FieldViewProps } from "../nodes/FieldView";
+import { emptyFunction } from "../../../Utils";
@observer
@@ -100,7 +101,7 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
onContextMenu = (e: React.MouseEvent): void => {
if (!e.isPropagationStopped() && this.props.Document.Id !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
- ContextMenu.Instance.addItem({ description: "VideoOptions", event: () => { } });
+ ContextMenu.Instance.addItem({ description: "VideoOptions", event: emptyFunction });
}
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index cf058090d..647c83d4d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -1,4 +1,4 @@
-import { computed, reaction } from "mobx";
+import { computed, reaction, trace, IReactionDisposer } from "mobx";
import { observer } from "mobx-react";
import { Document } from "../../../../fields/Document";
import { FieldWaiting } from "../../../../fields/Field";
@@ -15,18 +15,15 @@ import React = require("react");
@observer
export class CollectionFreeFormLinksView extends React.Component<CollectionViewProps> {
- HackToAvoidReactionFiringUnnecessarily?: Document = undefined;
+ _brushReactionDisposer?: IReactionDisposer;
componentDidMount() {
- this.HackToAvoidReactionFiringUnnecessarily = this.props.Document;
- reaction(() =>
- DocumentManager.Instance.getAllDocumentViews(this.HackToAvoidReactionFiringUnnecessarily!).
- map(dv => dv.props.Document.GetNumber(KeyStore.X, 0)),
+ this._brushReactionDisposer = reaction(() => this.props.Document.GetList<Document>(this.props.fieldKey, []).map(doc => doc.GetNumber(KeyStore.X, 0)),
() => {
- let views = DocumentManager.Instance.getAllDocumentViews(this.props.Document);
+ let views = this.props.Document.GetList<Document>(this.props.fieldKey, []);
for (let i = 0; i < views.length; i++) {
for (let j = 0; j < views.length; j++) {
- let srcDoc = views[j].props.Document;
- let dstDoc = views[i].props.Document;
+ let srcDoc = views[j];
+ let dstDoc = views[i];
let x1 = srcDoc.GetNumber(KeyStore.X, 0);
let x1w = srcDoc.GetNumber(KeyStore.Width, -1);
let x2 = dstDoc.GetNumber(KeyStore.X, 0);
@@ -53,7 +50,7 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
linkDoc.SetText(KeyStore.LinkDescription, "Brush between " + srcTarg.Title + " and " + dstTarg.Title);
linkDoc.SetData(KeyStore.BrushingDocs, [dstTarg, srcTarg], ListField);
- brushAction = brushAction = (field: ListField<Document>) => {
+ brushAction = (field: ListField<Document>) => {
if (findBrush(field) === -1) {
console.log("ADD BRUSH " + srcTarg.Title + " " + dstTarg.Title);
(findBrush(field) === -1) && field.Data.push(linkDoc);
@@ -67,6 +64,11 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
}
});
}
+ componentWillUnmount() {
+ if (this._brushReactionDisposer) {
+ this._brushReactionDisposer();
+ }
+ }
documentAnchors(view: DocumentView) {
let equalViews = [view];
let containerDoc = view.props.Document.GetT(KeyStore.AnnotationOn, Document);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss
new file mode 100644
index 000000000..c5b8fc5e8
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss
@@ -0,0 +1,24 @@
+@import "globalCssVariables";
+
+.collectionFreeFormRemoteCursors-cont {
+
+ position:absolute;
+ z-index: $remoteCursors-zindex;
+ transform-origin: 'center center';
+}
+.collectionFreeFormRemoteCursors-canvas {
+
+ position:absolute;
+ width: 20px;
+ height: 20px;
+ opacity: 0.5;
+ border-radius: 50%;
+ border: 2px solid black;
+}
+.collectionFreeFormRemoteCursors-symbol {
+ font-size: 14;
+ color: black;
+ // fontStyle: "italic",
+ margin-left: -12;
+ margin-top: 4;
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
index fc832264d..751ea8190 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -59,37 +59,17 @@ export class CollectionFreeFormRemoteCursors extends React.Component<CollectionV
let point = entry.Data[1];
this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22");
return (
- <div
- key={id}
- style={{
- position: "absolute",
- transform: `translate(${point[0] - 10}px, ${point[1] - 10}px)`,
- zIndex: 10000,
- transformOrigin: 'center center',
- }}
+ <div key={id} className="collectionFreeFormRemoteCursors-cont"
+ style={{ transform: `translate(${point[0] - 10}px, ${point[1] - 10}px)` }}
>
- <canvas
+ <canvas className="collectionFreeFormRemoteCursors-canvas"
ref={(el) => { if (el) this.crosshairs = el; }}
width={20}
height={20}
- style={{
- position: 'absolute',
- width: "20px",
- height: "20px",
- opacity: 0.5,
- borderRadius: "50%",
- border: "2px solid black"
- }}
/>
- <p
- style={{
- fontSize: 14,
- color: "black",
- // fontStyle: "italic",
- marginLeft: -12,
- marginTop: 4
- }}
- >{email[0].toUpperCase()}</p>
+ <p className="collectionFreeFormRemoteCursors-symbol">
+ {email[0].toUpperCase()}
+ </p>
</div>
);
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 81f2146e4..26c794e91 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -1,13 +1,13 @@
-@import "../../global_variables";
+@import "../../globalCssVariables";
.collectionfreeformview-measure {
- position: absolute;
+ position: inherit;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.collectionfreeformview {
- position: absolute;
+ position: inherit;
top: 0;
left: 0;
width: 100%;
@@ -16,7 +16,7 @@
}
.collectionfreeformview-container {
.collectionfreeformview > .jsx-parser {
- position: absolute;
+ position: inherit;
height: 100%;
width: 100%;
}
@@ -28,8 +28,10 @@
// background-size: 30px 30px;
// }
+ border-width: $COLLECTION_BORDER_WIDTH;
box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw;
- border: 0px solid $light-color-secondary;
+ border-color: $light-color-secondary;
+ border-style: solid;
border-radius: $border-radius;
box-sizing: border-box;
position: absolute;
@@ -41,7 +43,7 @@
}
.collectionfreeformview-overlay {
.collectionfreeformview > .jsx-parser {
- position: absolute;
+ position: inherit;
height: 100%;
}
.formattedTextBox-cont {
@@ -49,10 +51,12 @@
}
opacity: 0.99;
- border: 0px solid transparent;
+ border-width: 0;
+ border-color: transparent;
+ border-style: solid;
border-radius: $border-radius;
box-sizing: border-box;
- position:absolute;
+ position: absolute;
overflow: hidden;
top: 0;
left: 0;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 9914f3793..e19dc98fa 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,30 +1,30 @@
-import { action, computed, observable, trace, ObservableSet, runInAction } from "mobx";
+import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
+import Measure from "react-measure";
import { Document } from "../../../../fields/Document";
import { FieldWaiting } from "../../../../fields/Field";
import { KeyStore } from "../../../../fields/KeyStore";
+import { NumberField } from "../../../../fields/NumberField";
import { TextField } from "../../../../fields/TextField";
+import { emptyFunction, returnFalse } from "../../../../Utils";
+import { DocumentManager } from "../../../util/DocumentManager";
import { DragManager } from "../../../util/DragManager";
+import { SelectionManager } from "../../../util/SelectionManager";
import { Transform } from "../../../util/Transform";
import { undoBatch } from "../../../util/UndoManager";
+import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss";
import { InkingCanvas } from "../../InkingCanvas";
+import { Main } from "../../Main";
import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
import { DocumentContentsView } from "../../nodes/DocumentContentsView";
import { DocumentViewProps } from "../../nodes/DocumentView";
-import { COLLECTION_BORDER_WIDTH } from "../CollectionBaseView";
import { CollectionSubView } from "../CollectionSubView";
import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";
+import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
import "./CollectionFreeFormView.scss";
import { MarqueeView } from "./MarqueeView";
import React = require("react");
import v5 = require("uuid/v5");
-import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
-import { PreviewCursor } from "./PreviewCursor";
-import { DocumentManager } from "../../../util/DocumentManager";
-import { SelectionManager } from "../../../util/SelectionManager";
-import { NumberField } from "../../../../fields/NumberField";
-import { Main } from "../../Main";
-import Measure from "react-measure";
@observer
export class CollectionFreeFormView extends CollectionSubView {
@@ -134,9 +134,6 @@ export class CollectionFreeFormView extends CollectionSubView {
document.addEventListener("pointerup", this.onPointerUp);
this._lastX = this.DownX = e.pageX;
this._lastY = this.DownY = e.pageY;
- if (this.props.isSelected()) {
- e.stopPropagation();
- }
}
}
@@ -200,7 +197,7 @@ export class CollectionFreeFormView extends CollectionSubView {
@action
private SetPan(panX: number, panY: number) {
- Main.Instance.SetTextDoc(undefined, undefined);
+ Main.Instance.SetTextDoc();
var x1 = this.getLocalTransform().inverse().Scale;
const newPanX = Math.min((1 - 1 / x1) * this.nativeWidth, Math.max(0, panX));
const newPanY = Math.min((1 - 1 / x1) * this.nativeHeight, Math.max(0, panY));
@@ -290,26 +287,32 @@ export class CollectionFreeFormView extends CollectionSubView {
get backgroundView() {
return !this.backgroundLayout ? (null) :
(<DocumentContentsView {...this.getDocumentViewProps(this.props.Document)}
- layoutKey={KeyStore.BackgroundLayout} isTopMost={this.props.isTopMost} isSelected={() => false} select={() => { }} />);
+ layoutKey={KeyStore.BackgroundLayout} isTopMost={this.props.isTopMost} isSelected={returnFalse} select={emptyFunction} />);
}
@computed
get overlayView() {
return !this.overlayLayout ? (null) :
(<DocumentContentsView {...this.getDocumentViewProps(this.props.Document)}
- layoutKey={KeyStore.OverlayLayout} isTopMost={this.props.isTopMost} isSelected={() => false} select={() => { }} />);
+ layoutKey={KeyStore.OverlayLayout} isTopMost={this.props.isTopMost} isSelected={returnFalse} select={emptyFunction} />);
}
- getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform());
- getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH);
+ @computed
+ get borderWidth() {
+ return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH;
+ }
+ getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform());
+ getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.scale).translate(this.panX, this.panY);
noScaling = () => 1;
childViews = () => this.views;
render() {
- let [dx, dy] = [this.centeringShiftX, this.centeringShiftY];
-
+ const [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 zoom: number = this.zoomScaling;// needs to be a variable outside of the <Measure> otherwise, reactions won't fire
+ const backgroundView = this.backgroundView; // needs to be a variable outside of the <Measure> otherwise, reactions won't fire
+ const overlayView = this.overlayView;// needs to be a variable outside of the <Measure> otherwise, reactions won't fire
return (
<Measure onResize={(r: any) => runInAction(() => { this._pwidth = r.entry.width; this._pheight = r.entry.height; })}>
@@ -317,25 +320,21 @@ export class CollectionFreeFormView extends CollectionSubView {
<div className={`collectionfreeformview-measure`} ref={measureRef}>
<div className={`collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`}
onPointerDown={this.onPointerDown} onPointerMove={(e) => super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY))}
- onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} onWheel={this.onPointerWheel}
- style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} ref={this.createDropTarget}>
+ onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} onWheel={this.onPointerWheel} ref={this.createDropTarget}>
<MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments}
- addDocument={this.addDocument} removeDocument={this.props.removeDocument}
+ addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox}
getContainerTransform={this.getContainerTransform} getTransform={this.getTransform}>
- <PreviewCursor container={this} addLiveTextDocument={this.addLiveTextBox}
- getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} >
- <div className="collectionfreeformview" ref={this._canvasRef}
- style={{ transform: `translate(${dx}px, ${dy}px) scale(${this.zoomScaling}, ${this.zoomScaling}) translate(${panx}px, ${pany}px)` }}>
- {this.backgroundView}
- <CollectionFreeFormLinksView {...this.props}>
- <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} >
- {this.childViews}
- </InkingCanvas>
- </CollectionFreeFormLinksView>
- <CollectionFreeFormRemoteCursors {...this.props} />
- </div>
- {this.overlayView}
- </PreviewCursor>
+ <div className="collectionfreeformview" ref={this._canvasRef}
+ style={{ transform: `translate(${dx}px, ${dy}px) scale(${zoom}, ${zoom}) translate(${panx}px, ${pany}px)` }}>
+ {backgroundView}
+ <CollectionFreeFormLinksView {...this.props}>
+ <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} >
+ {this.childViews}
+ </InkingCanvas>
+ </CollectionFreeFormLinksView>
+ <CollectionFreeFormRemoteCursors {...this.props} />
+ </div>
+ {overlayView}
</MarqueeView>
</div>
</div>)}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
index 0b406e722..e5ffcec76 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
@@ -1,6 +1,6 @@
.marqueeView {
- position: absolute;
+ position: inherit;
top:0;
left:0;
width:100%;
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 1e6faafb3..a4722a6f8 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable, trace } from "mobx";
+import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
import { Document } from "../../../../fields/Document";
import { FieldWaiting } from "../../../../fields/Field";
@@ -7,10 +7,11 @@ import { KeyStore } from "../../../../fields/KeyStore";
import { Documents } from "../../../documents/Documents";
import { SelectionManager } from "../../../util/SelectionManager";
import { Transform } from "../../../util/Transform";
+import { undoBatch } from "../../../util/UndoManager";
import { InkingCanvas } from "../../InkingCanvas";
+import { PreviewCursor } from "../../PreviewCursor";
import { CollectionFreeFormView } from "./CollectionFreeFormView";
import "./MarqueeView.scss";
-import { PreviewCursor } from "./PreviewCursor";
import React = require("react");
interface MarqueeViewProps {
@@ -21,6 +22,7 @@ interface MarqueeViewProps {
activeDocuments: () => Document[];
selectDocuments: (docs: Document[]) => void;
removeDocument: (doc: Document) => boolean;
+ addLiveTextDocument: (doc: Document) => void;
}
@observer
@@ -32,6 +34,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
@observable _downY: number = 0;
@observable _used: boolean = false;
@observable _visible: boolean = false;
+ _showOnUp: boolean = false;
static DRAG_THRESHOLD = 4;
@action
@@ -47,11 +50,31 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
}
@action
+ onKeyPress = (e: KeyboardEvent) => {
+ // Mixing events between React and Native is finicky. In FormattedTextBox, we set the
+ // DASHFormattedTextBoxHandled flag when a text box consumes a key press so that we can ignore
+ // the keyPress here.
+ //if not these keys, make a textbox if preview cursor is active!
+ if (!e.ctrlKey && !e.altKey && !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) {
+ //make textbox and add it to this collection
+ let [x, y] = this.props.getTransform().transformPoint(this._downX, this._downY);
+ let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "typed text" });
+ this.props.addLiveTextDocument(newBox);
+ PreviewCursor.Visible = false;
+ e.stopPropagation();
+ }
+ }
+ hideCursor = () => {
+ document.removeEventListener("keypress", this.onKeyPress, false);
+ }
+ @action
onPointerDown = (e: React.PointerEvent): void => {
if (e.buttons === 1 && !e.altKey && !e.metaKey && this.props.container.props.active()) {
this._downX = this._lastX = e.pageX;
this._downY = this._lastY = e.pageY;
this._used = false;
+ this._showOnUp = true;
+ document.removeEventListener("keypress", this.onKeyPress, false);
document.addEventListener("pointermove", this.onPointerMove, true);
document.addEventListener("pointerup", this.onPointerUp, true);
document.addEventListener("keydown", this.marqueeCommand, true);
@@ -63,6 +86,10 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
this._lastX = e.pageX;
this._lastY = e.pageY;
if (!e.cancelBubble) {
+ if (Math.abs(this._downX - e.clientX) > 4 || Math.abs(this._downY - e.clientY) > 4) {
+ this._showOnUp = false;
+ PreviewCursor.Visible = false;
+ }
if (!this._used && e.buttons === 1 && !e.altKey && !e.metaKey &&
(Math.abs(this._lastX - this._downX) > MarqueeView.DRAG_THRESHOLD || Math.abs(this._lastY - this._downY) > MarqueeView.DRAG_THRESHOLD)) {
this._visible = true;
@@ -76,11 +103,16 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
onPointerUp = (e: PointerEvent): void => {
this.cleanupInteractions(true);
this._visible = false;
- let mselect = this.marqueeSelect();
- if (!e.shiftKey) {
- SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document);
+ if (this._showOnUp) {
+ PreviewCursor.Show(this.hideCursor, this._downX, this._downY);
+ document.addEventListener("keypress", this.onKeyPress, false);
+ } else {
+ let mselect = this.marqueeSelect();
+ if (!e.shiftKey) {
+ SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document);
+ }
+ this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]);
}
- this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]);
}
intersectRect(r1: { left: number, top: number, width: number, height: number },
@@ -97,6 +129,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) };
}
+ @undoBatch
@action
marqueeCommand = (e: KeyboardEvent) => {
if (e.key === "Backspace" || e.key === "Delete") {
diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.scss b/src/client/views/collections/collectionFreeForm/PreviewCursor.scss
deleted file mode 100644
index 7a67c29bf..000000000
--- a/src/client/views/collections/collectionFreeForm/PreviewCursor.scss
+++ /dev/null
@@ -1,27 +0,0 @@
-
-.previewCursor {
- color: black;
- position: absolute;
- transform-origin: left top;
- top: 0;
- left:0;
- pointer-events: none;
-}
-.previewCursorView {
- top: 0;
- left:0;
- position: absolute;
- width:100%;
- height:100%;
-}
-
-//this is an animation for the blinking cursor!
-// @keyframes blink {
-// 0% {opacity: 0}
-// 49%{opacity: 0}
-// 50% {opacity: 1}
-// }
-
-// #previewCursor {
-// animation: blink 1s infinite;
-// } \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx
deleted file mode 100644
index 8eabb020a..000000000
--- a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx
+++ /dev/null
@@ -1,120 +0,0 @@
-import { action, observable, trace, computed, reaction } from "mobx";
-import { observer } from "mobx-react";
-import { Document } from "../../../../fields/Document";
-import { Documents } from "../../../documents/Documents";
-import { Transform } from "../../../util/Transform";
-import { CollectionFreeFormView } from "./CollectionFreeFormView";
-import "./PreviewCursor.scss";
-import React = require("react");
-import { interfaceDeclaration } from "babel-types";
-
-
-export interface PreviewCursorProps {
- getTransform: () => Transform;
- getContainerTransform: () => Transform;
- container: CollectionFreeFormView;
- addLiveTextDocument: (doc: Document) => void;
-}
-
-@observer
-export class PreviewCursor extends React.Component<PreviewCursorProps> {
- @observable _lastX: number = 0;
- @observable _lastY: number = 0;
- @observable public _visible: boolean = false;
- @observable public DownX: number = 0;
- @observable public DownY: number = 0;
- _showOnUp: boolean = false;
-
- @action
- cleanupInteractions = () => {
- document.removeEventListener("pointerup", this.onPointerUp, true);
- document.removeEventListener("pointermove", this.onPointerMove, true);
- }
-
- @action
- onPointerDown = (e: React.PointerEvent) => {
- if (e.button === 0 && this.props.container.props.active()) {
- document.removeEventListener("keypress", this.onKeyPress, false);
- this._showOnUp = true;
- this.DownX = e.pageX;
- this.DownY = e.pageY;
- document.addEventListener("pointerup", this.onPointerUp, true);
- document.addEventListener("pointermove", this.onPointerMove, true);
- }
- }
- @action
- onPointerMove = (e: PointerEvent): void => {
- if (Math.abs(this.DownX - e.clientX) > 4 || Math.abs(this.DownY - e.clientY) > 4) {
- this._showOnUp = false;
- this._visible = false;
- }
- }
-
- @action
- onPointerUp = (e: PointerEvent): void => {
- if (this._showOnUp) {
- document.addEventListener("keypress", this.onKeyPress, false);
- this._lastX = this.DownX;
- this._lastY = this.DownY;
- this._visible = true;
- }
- this.cleanupInteractions();
- }
-
- @action
- onKeyPress = (e: KeyboardEvent) => {
- // Mixing events between React and Native is finicky. In FormattedTextBox, we set the
- // DASHFormattedTextBoxHandled flag when a text box consumes a key press so that we can ignore
- // the keyPress here.
- //if not these keys, make a textbox if preview cursor is active!
- if (!e.ctrlKey && !e.altKey && !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) {
- //make textbox and add it to this collection
- let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY);
- let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "typed text" });
- this.props.addLiveTextDocument(newBox);
- document.removeEventListener("keypress", this.onKeyPress, false);
- this._visible = false;
- e.stopPropagation();
- }
- }
-
- getPoint = () => this.props.getContainerTransform().transformPoint(this._lastX, this._lastY);
- getVisible = () => this._visible;
- setVisible = (v: boolean) => {
- this._visible = v;
- document.removeEventListener("keypress", this.onKeyPress, false);
- }
- render() {
- return (
- <div className="previewCursorView" onPointerDown={this.onPointerDown}>
- {this.props.children}
- <PreviewCursorPrompt setVisible={this.setVisible} getPoint={this.getPoint} getVisible={this.getVisible} />
- </div>
- );
- }
-}
-
-export interface PromptProps {
- getPoint: () => number[];
- getVisible: () => boolean;
- setVisible: (v: boolean) => void;
-}
-
-@observer
-export class PreviewCursorPrompt extends React.Component<PromptProps> {
- private _promptRef = React.createRef<HTMLDivElement>();
-
- //when focus is lost, this will remove the preview cursor
- @action onBlur = (): void => this.props.setVisible(false);
-
- render() {
- let p = this.props.getPoint();
- if (this.props.getVisible() && this._promptRef.current) {
- this._promptRef.current.focus();
- }
- return <div className="previewCursor" id="previewCursor" onBlur={this.onBlur} tabIndex={0} ref={this._promptRef}
- style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, opacity: this.props.getVisible() ? 1 : 0 }}>
- I
- </div >;
- }
-} \ No newline at end of file
diff --git a/src/client/views/globalCssVariables.scss b/src/client/views/globalCssVariables.scss
new file mode 100644
index 000000000..5c8e9c8fc
--- /dev/null
+++ b/src/client/views/globalCssVariables.scss
@@ -0,0 +1,29 @@
+@import url("https://fonts.googleapis.com/css?family=Noto+Sans:400,700|Crimson+Text:400,400i,700");
+// colors
+$light-color: #fcfbf7;
+$light-color-secondary: rgb(241, 239, 235);
+$main-accent: #61aaa3;
+// $alt-accent: #cdd5ec;
+// $alt-accent: #cdeceb;
+$alt-accent: #59dff7;
+$lighter-alt-accent: rgb(207, 220, 240);
+$intermediate-color: #9c9396;
+$dark-color: #121721;
+// fonts
+$sans-serif: "Noto Sans", sans-serif;
+// $sans-serif: "Roboto Slab", sans-serif;
+$serif: "Crimson Text", serif;
+// misc values
+$border-radius: 0.3em;
+//
+
+ // dragged items
+$contextMenu-zindex: 1000; // context menu shows up over everything
+$mainTextInput-zindex: 999; // then text input overlay so that it's context menu will appear over decorations, etc
+$docDecorations-zindex: 998; // then doc decorations appear over everything else
+$remoteCursors-zindex: 997; // ... not sure what level the remote cursors should go -- is this right?
+$COLLECTION_BORDER_WIDTH: 1;
+:export {
+ contextMenuZindex: $contextMenu-zindex;
+ COLLECTION_BORDER_WIDTH: $COLLECTION_BORDER_WIDTH;
+} \ No newline at end of file
diff --git a/src/client/views/globalCssVariables.scss.d.ts b/src/client/views/globalCssVariables.scss.d.ts
new file mode 100644
index 000000000..e874b815d
--- /dev/null
+++ b/src/client/views/globalCssVariables.scss.d.ts
@@ -0,0 +1,8 @@
+
+interface IGlobalScss {
+ contextMenuZindex: string; // context menu shows up over everything
+ COLLECTION_BORDER_WIDTH: number;
+}
+declare const globalCssVariables: IGlobalScss;
+
+export = globalCssVariables; \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 5126e69f9..a946ac1a8 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -1,7 +1,7 @@
-@import "../global_variables";
+@import "../globalCssVariables";
.documentView-node {
- position: absolute;
+ position: inherit;
top: 0;
left:0;
background: $light-color; //overflow: hidden;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1d7e9aa79..4eef07a2f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, IReactionDisposer, reaction, runInAction } from "mobx";
+import { action, computed, IReactionDisposer, reaction, runInAction, trace } from "mobx";
import { observer } from "mobx-react";
import { Document } from "../../../fields/Document";
import { Field, FieldWaiting, Opt } from "../../../fields/Field";
@@ -8,7 +8,7 @@ import { ListField } from "../../../fields/ListField";
import { BooleanField } from "../../../fields/BooleanField";
import { TextField } from "../../../fields/TextField";
import { ServerUtils } from "../../../server/ServerUtil";
-import { Utils } from "../../../Utils";
+import { Utils, emptyFunction } from "../../../Utils";
import { Documents } from "../../documents/Documents";
import { DocumentManager } from "../../util/DocumentManager";
import { DragManager } from "../../util/DragManager";
@@ -20,6 +20,7 @@ import { ContextMenu } from "../ContextMenu";
import { DocumentContentsView } from "./DocumentContentsView";
import "./DocumentView.scss";
import React = require("react");
+import { undoBatch, UndoManager } from "../../util/UndoManager";
export interface DocumentViewProps {
@@ -62,12 +63,12 @@ export function FakeJsxArgs(keys: string[], fields: string[] = []): JsxArgs {
let Keys: { [name: string]: any } = {};
let Fields: { [name: string]: any } = {};
for (const key of keys) {
- let fn = () => { };
+ let fn = emptyFunction;
Object.defineProperty(fn, "name", { value: key + "Key" });
Keys[key] = fn;
}
for (const field of fields) {
- let fn = () => { };
+ let fn = emptyFunction;
Object.defineProperty(fn, "name", { value: field });
Fields[field] = fn;
}
@@ -108,7 +109,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
}
e.stopPropagation();
} else {
- if (this.active && !e.isDefaultPrevented()) {
+ if (this.active) {
e.stopPropagation();
document.removeEventListener("pointermove", this.onPointerMove);
document.addEventListener("pointermove", this.onPointerMove);
@@ -160,7 +161,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
dragData.moveDocument = this.props.moveDocument;
DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, {
handlers: {
- dragComplete: action(() => { })
+ dragComplete: action(emptyFunction)
},
hideSource: !dropAliasOfDraggedDoc
});
@@ -189,6 +190,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
document.removeEventListener("pointerup", this.onPointerUp);
e.stopPropagation();
if (!SelectionManager.IsSelected(this) &&
+ e.button !== 2 &&
Math.abs(e.clientX - this._downX) < 4 &&
Math.abs(e.clientY - this._downY) < 4
) {
@@ -240,19 +242,18 @@ export class DocumentView extends React.Component<DocumentViewProps> {
SelectionManager.DeselectAll();
}
+ @undoBatch
@action
drop = (e: Event, de: DragManager.DropEvent) => {
if (de.data instanceof DragManager.LinkDragData) {
- let sourceDoc: Document = de.data.linkSourceDocumentView.props.Document;
+ let sourceDoc: Document = de.data.linkSourceDocument;
let destDoc: Document = this.props.Document;
- if (this.props.isTopMost) {
- return;
- }
let linkDoc: Document = new Document();
destDoc.GetTAsync(KeyStore.Prototype, Document).then(protoDest =>
sourceDoc.GetTAsync(KeyStore.Prototype, Document).then(protoSrc =>
runInAction(() => {
+ let batch = UndoManager.StartBatch("document view drop");
linkDoc.Set(KeyStore.Title, new TextField("New Link"));
linkDoc.Set(KeyStore.LinkDescription, new TextField(""));
linkDoc.Set(KeyStore.LinkTags, new TextField("Default"));
@@ -261,20 +262,23 @@ export class DocumentView extends React.Component<DocumentViewProps> {
let srcTarg = protoSrc ? protoSrc : sourceDoc;
linkDoc.Set(KeyStore.LinkedToDocs, dstTarg);
linkDoc.Set(KeyStore.LinkedFromDocs, srcTarg);
- dstTarg.GetOrCreateAsync(
+ const prom1 = new Promise(resolve => dstTarg.GetOrCreateAsync(
KeyStore.LinkedFromDocs,
ListField,
field => {
(field as ListField<Document>).Data.push(linkDoc);
+ resolve();
}
- );
- srcTarg.GetOrCreateAsync(
+ ));
+ const prom2 = new Promise(resolve => srcTarg.GetOrCreateAsync(
KeyStore.LinkedToDocs,
ListField,
field => {
(field as ListField<Document>).Data.push(linkDoc);
+ resolve();
}
- );
+ ));
+ Promise.all([prom1, prom2]).finally(() => batch.end());
})
)
);
@@ -380,14 +384,26 @@ export class DocumentView extends React.Component<DocumentViewProps> {
SelectionManager.SelectDoc(this, ctrlPressed);
}
+ @computed get nativeWidth(): number { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
+ @computed get nativeHeight(): number { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
+ @computed
+ get contents() {
+ return (<DocumentContentsView
+ {...this.props}
+ isSelected={this.isSelected}
+ select={this.select}
+ layoutKey={KeyStore.Layout}
+ />);
+ }
+
render() {
if (!this.props.Document) {
return null;
}
var scaling = this.props.ContentScaling();
- var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
- var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0);
+ var nativeWidth = this.nativeWidth;
+ var nativeHeight = this.nativeHeight;
if (this.isMinimized()) {
return (
@@ -423,12 +439,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
onContextMenu={this.onContextMenu}
onPointerDown={this.onPointerDown}
>
- <DocumentContentsView
- {...this.props}
- isSelected={this.isSelected}
- select={this.select}
- layoutKey={KeyStore.Layout}
- />
+ {this.contents}
</div>
);
}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 07c5b332c..0037d7b28 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -19,7 +19,7 @@ import { ListField } from "../../../fields/ListField";
import { DocumentContentsView } from "./DocumentContentsView";
import { Transform } from "../../util/Transform";
import { KeyStore } from "../../../fields/KeyStore";
-import { returnFalse } from "../../../Utils";
+import { returnFalse, emptyDocFunction } from "../../../Utils";
//
@@ -85,9 +85,9 @@ export class FieldView extends React.Component<FieldViewProps> {
PanelHeight={() => 100}
isTopMost={true} //TODO Why is this top most?
selectOnLoad={false}
- focus={() => { }}
- isSelected={() => false}
- select={() => false}
+ focus={emptyDocFunction}
+ isSelected={returnFalse}
+ select={returnFalse}
layoutKey={KeyStore.Layout}
ContainingCollectionView={undefined}
parentActive={this.props.active}
@@ -111,7 +111,7 @@ export class FieldView extends React.Component<FieldViewProps> {
}
else {
return <p> {"Waiting for server..."} </p>;
- }
+ }
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index 32da2632e..3978c3d38 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -1,4 +1,4 @@
-@import "../global_variables";
+@import "../globalCssVariables";
.ProseMirror {
width: 100%;
height: auto;
@@ -22,6 +22,7 @@
overflow-x: hidden;
color: initial;
height: 100%;
+ pointer-events: all;
}
.menuicon {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index beca6cdc6..8ea747b1c 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -5,7 +5,6 @@ import { keymap } from "prosemirror-keymap";
import { EditorState, Plugin, Transaction } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { FieldWaiting, Opt } from "../../../fields/Field";
-import { KeyStore } from "../../../fields/KeyStore";
import { RichTextField } from "../../../fields/RichTextField";
import { inpRules } from "../../util/RichTextRules";
import { schema } from "../../util/RichTextSchema";
@@ -34,7 +33,12 @@ const { menuBar } = require("prosemirror-menu");
// specified Key and assigns it to an HTML input node. When changes are made to this node,
// this will edit the document and assign the new value to that field.
//]
-export class FormattedTextBox extends React.Component<FieldViewProps> {
+
+export interface FormattedTextBoxOverlay {
+ isOverlay?: boolean;
+}
+
+export class FormattedTextBox extends React.Component<(FieldViewProps & FormattedTextBoxOverlay)> {
public static LayoutString(fieldStr: string = "DataKey") {
return FieldView.LayoutString(FormattedTextBox, fieldStr);
}
@@ -42,6 +46,7 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
private _editorView: Opt<EditorView>;
private _reactionDisposer: Opt<IReactionDisposer>;
private _inputReactionDisposer: Opt<IReactionDisposer>;
+ private _proxyReactionDisposer: Opt<IReactionDisposer>;
constructor(props: FieldViewProps) {
super(props);
@@ -54,8 +59,8 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
if (this._editorView) {
const state = this._editorView.state.apply(tx);
this._editorView.updateState(state);
- this.FieldDoc.SetDataOnPrototype(
- this.FieldKey,
+ this.props.Document.SetDataOnPrototype(
+ this.props.fieldKey,
JSON.stringify(state.toJSON()),
RichTextField
);
@@ -63,22 +68,23 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
}
}
- get FieldDoc() { return this.props.fieldKey === KeyStore.Archives ? Main.Instance._textDoc! : this.props.Document; }
- get FieldKey() { return this.props.fieldKey === KeyStore.Archives ? KeyStore.Data : this.props.fieldKey; }
-
componentDidMount() {
const config = {
schema,
inpRules, //these currently don't do anything, but could eventually be helpful
- plugins: [
+ plugins: this.props.isOverlay ? [
history(),
keymap({ "Mod-z": undo, "Mod-y": redo }),
keymap(baseKeymap),
this.tooltipMenuPlugin()
- ]
+ ] : [
+ history(),
+ keymap({ "Mod-z": undo, "Mod-y": redo }),
+ keymap(baseKeymap),
+ ]
};
- if (this.props.fieldKey === KeyStore.Archives) {
+ if (this.props.isOverlay) {
this._inputReactionDisposer = reaction(() => Main.Instance._textDoc && Main.Instance._textDoc.Id,
() => {
if (this._editorView) {
@@ -88,11 +94,14 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
this.setupEditor(config);
}
);
+ } else {
+ this._proxyReactionDisposer = reaction(() => this.props.isSelected(),
+ () => this.props.isSelected() && Main.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform()));
}
this._reactionDisposer = reaction(
() => {
- const field = this.FieldDoc.GetT(this.FieldKey, RichTextField);
+ const field = this.props.Document ? this.props.Document.GetT(this.props.fieldKey, RichTextField) : undefined;
return field && field !== FieldWaiting ? field.Data : undefined;
},
field => {
@@ -107,9 +116,8 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
}
private setupEditor(config: any) {
-
let state: EditorState;
- let field = this.FieldDoc.GetT(this.FieldKey, RichTextField);
+ let field = this.props.Document ? this.props.Document.GetT(this.props.fieldKey, RichTextField) : undefined;
if (field && field !== FieldWaiting && field.Data) {
state = EditorState.fromJSON(config, JSON.parse(field.Data));
} else {
@@ -138,6 +146,9 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
if (this._inputReactionDisposer) {
this._inputReactionDisposer();
}
+ if (this._proxyReactionDisposer) {
+ this._proxyReactionDisposer();
+ }
}
shouldComponentUpdate() {
@@ -151,23 +162,26 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
// doc.SetData(fieldKey, e.target.value, RichTextField);
}
onPointerDown = (e: React.PointerEvent): void => {
- if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
+ if (e.button === 1 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) {
e.stopPropagation();
}
+ if (e.button === 2) {
+ e.preventDefault();
+ }
}
onPointerUp = (e: React.PointerEvent): void => {
if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
e.stopPropagation();
}
- if (this.props.fieldKey !== KeyStore.Archives) {
- e.preventDefault();
- Main.Instance.SetTextDoc(this.props.Document, this._ref.current!);
- }
}
onFocused = (e: React.FocusEvent): void => {
- if (this.props.fieldKey !== KeyStore.Archives) {
- Main.Instance.SetTextDoc(this.props.Document, this._ref.current!);
+ if (!this.props.isOverlay) {
+ Main.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform());
+ } else {
+ if (this._ref.current) {
+ this._ref.current.scrollTop = Main.Instance._textScroll;
+ }
}
}
@@ -199,9 +213,10 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
}
tooltipMenuPlugin() {
+ let myprops = this.props;
return new Plugin({
view(_editorView) {
- return new TooltipTextMenu(_editorView);
+ return new TooltipTextMenu(_editorView, myprops);
}
});
}
@@ -214,9 +229,10 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
render() {
return (
<div
- className="formattedTextBox-cont"
+ className={`formattedTextBox-cont`}
onKeyDown={this.onKeyPress}
onKeyPress={this.onKeyPress}
+ onFocus={this.onFocused}
onPointerUp={this.onPointerUp}
onPointerDown={this.onPointerDown}
onContextMenu={this.specificContextMenu}
diff --git a/src/client/views/nodes/KeyValueBox.scss b/src/client/views/nodes/KeyValueBox.scss
index 63ae75424..6ebd73f2c 100644
--- a/src/client/views/nodes/KeyValueBox.scss
+++ b/src/client/views/nodes/KeyValueBox.scss
@@ -1,6 +1,7 @@
-@import "../global_variables";
+@import "../globalCssVariables";
.keyValueBox-cont {
overflow-y: scroll;
+ width:100%;
height: 100%;
background-color: $light-color;
border: 1px solid $intermediate-color;
@@ -8,31 +9,58 @@
box-sizing: border-box;
display: inline-block;
.imageBox-cont img {
- max-height: 45px;
- height: auto;
- }
- td {
- padding: 6px 8px;
- border-right: 1px solid $intermediate-color;
- border-top: 1px solid $intermediate-color;
- &:last-child {
- border-right: none;
- }
+ width: auto;
}
}
+$header-height: 30px;
+.keyValueBox-tbody {
+ width:100%;
+ height:100%;
+ position: absolute;
+ overflow-y: scroll;
+}
+.keyValueBox-key {
+ display: inline-block;
+ height:100%;
+ width:50%;
+ text-align: center;
+}
+.keyValueBox-fields {
+ display: inline-block;
+ height:100%;
+ width:50%;
+ text-align: center;
+}
.keyValueBox-table {
- position: relative;
+ position: absolute;
+ width:100%;
+ height:100%;
border-collapse: collapse;
}
-
+.keyValueBox-td-key {
+ display:inline-block;
+ height:30px;
+}
+.keyValueBox-td-value {
+ display:inline-block;
+ height:30px;
+}
+.keyValueBox-valueRow {
+ width:100%;
+ height:30px;
+ display: inline-block;
+}
.keyValueBox-header {
+ width:100%;
+ position: relative;
+ display: inline-block;
background: $intermediate-color;
color: $light-color;
text-transform: uppercase;
letter-spacing: 2px;
font-size: 12px;
- height: 30px;
+ height: $header-height;
padding-top: 4px;
th {
font-weight: normal;
@@ -43,13 +71,50 @@
}
.keyValueBox-evenRow {
+ position: relative;
+ display: inline-block;
+ width:100%;
+ height:$header-height;
background: $light-color;
.formattedTextBox-cont {
background: $light-color;
}
}
+.keyValueBox-cont {
+ .collectionfreeformview-overlay {
+ position: relative;
+ }
+}
+.keyValueBox-dividerDraggerThumb{
+ position: relative;
+ width: 4px;
+ float: left;
+ height: 30px;
+ width: 10px;
+ z-index: 20;
+ right: 0;
+ top: 0;
+ border-radius: 10px;
+ background: gray;
+ pointer-events: all;
+}
+.keyValueBox-dividerDragger{
+ position: relative;
+ width: 100%;
+ float: left;
+ height: 37px;
+ z-index: 20;
+ right: 0;
+ top: 0;
+ background: transparent;
+ pointer-events: none;
+}
.keyValueBox-oddRow {
+ position: relative;
+ display: inline-block;
+ width:100%;
+ height:30px;
background: $light-color-secondary;
.formattedTextBox-cont {
background: $light-color-secondary;
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index bcac113f0..29e4af160 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -1,23 +1,25 @@
+import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
import { Document } from '../../../fields/Document';
-import { FieldWaiting, Field } from '../../../fields/Field';
+import { Field, FieldWaiting } from '../../../fields/Field';
+import { Key } from '../../../fields/Key';
import { KeyStore } from '../../../fields/KeyStore';
+import { CompileScript, ToField } from "../../util/Scripting";
import { FieldView, FieldViewProps } from './FieldView';
import "./KeyValueBox.scss";
import { KeyValuePair } from "./KeyValuePair";
import React = require("react");
-import { CompileScript, ToField } from "../../util/Scripting";
-import { Key } from '../../../fields/Key';
-import { observable, action } from "mobx";
@observer
export class KeyValueBox extends React.Component<FieldViewProps> {
+ private _mainCont = React.createRef<HTMLDivElement>();
public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(KeyValueBox, fieldStr); }
@observable private _keyInput: string = "";
@observable private _valueInput: string = "";
+ @computed get splitPercentage() { return this.props.Document.GetNumber(KeyStore.SchemaSplitPercentage, 50); }
constructor(props: FieldViewProps) {
@@ -90,7 +92,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
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} />);
+ rows.push(<KeyValuePair doc={realDoc} keyWidth={100 - this.splitPercentage} rowStyle={"keyValueBox-" + (i++ % 2 ? "oddRow" : "evenRow")} fieldId={key} key={key} />);
}
return rows;
}
@@ -107,24 +109,51 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
newKeyValue = () =>
(
- <tr>
- <td><input type="text" value={this._keyInput} placeholder="Key" onChange={this.keyChanged} /></td>
- <td><input type="text" value={this._valueInput} placeholder="Value" onChange={this.valueChanged} onKeyPress={this.onEnterKey} /></td>
+ <tr className="keyValueBox-valueRow">
+ <td className="keyValueBox-td-key" style={{ width: `${100 - this.splitPercentage}%` }}>
+ <input style={{ width: "100%" }} type="text" value={this._keyInput} placeholder="Key" onChange={this.keyChanged} />
+ </td>
+ <td className="keyValueBox-td-value" style={{ width: `${this.splitPercentage}%` }}>
+ <input style={{ width: "100%" }} type="text" value={this._valueInput} placeholder="Value" onChange={this.valueChanged} onKeyPress={this.onEnterKey} />
+ </td>
</tr>
)
+ @action
+ onDividerMove = (e: PointerEvent): void => {
+ let nativeWidth = this._mainCont.current!.getBoundingClientRect();
+ this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, Math.max(0, 100 - Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100)));
+ }
+ @action
+ onDividerUp = (e: PointerEvent): void => {
+ document.removeEventListener("pointermove", this.onDividerMove);
+ document.removeEventListener('pointerup', this.onDividerUp);
+ }
+ onDividerDown = (e: React.PointerEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+ document.addEventListener("pointermove", this.onDividerMove);
+ document.addEventListener('pointerup', this.onDividerUp);
+ }
+
render() {
- return (<div className="keyValueBox-cont" onWheel={this.onPointerWheel}>
+ let dividerDragger = this.splitPercentage === 0 ? (null) :
+ <div className="keyValueBox-dividerDragger" style={{ transform: `translate(calc(${100 - this.splitPercentage}% - 5px), 0px)` }}>
+ <div className="keyValueBox-dividerDraggerThumb" onPointerDown={this.onDividerDown} />
+ </div>;
+
+ return (<div className="keyValueBox-cont" onWheel={this.onPointerWheel} ref={this._mainCont}>
<table className="keyValueBox-table">
- <tbody>
+ <tbody className="keyValueBox-tbody">
<tr className="keyValueBox-header">
- <th>Key</th>
- <th>Fields</th>
+ <th className="keyValueBox-key" style={{ width: `${100 - this.splitPercentage}%` }}>Key</th>
+ <th className="keyValueBox-fields" style={{ width: `${this.splitPercentage}%` }}>Fields</th>
</tr>
{this.createTable()}
{this.newKeyValue()}
</tbody>
</table>
+ {dividerDragger}
</div>);
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/KeyValuePair.scss b/src/client/views/nodes/KeyValuePair.scss
index 64e871e1c..04d002c7b 100644
--- a/src/client/views/nodes/KeyValuePair.scss
+++ b/src/client/views/nodes/KeyValuePair.scss
@@ -1,12 +1,30 @@
-@import "../global_variables";
+@import "../globalCssVariables";
.container{
+ width:100%;
+ height:100%;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
}
+.keyValuePair-td-key {
+ display:inline-block;
+ width: 50%;
+}
+.keyValuePair-td-value {
+ display:inline-block;
+ width: 50%;
+}
+.keyValuePair-keyField {
+ width:100%;
+ text-align: center;
+ position: relative;
+ overflow: auto;
+}
.delete{
- color: red;
+ position: relative;
+ background-color: transparent;
+ color:red;
} \ No newline at end of file
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index a1050dc6e..3e0b61c3d 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -12,7 +12,7 @@ import { Server } from "../../Server";
import { EditableView } from "../EditableView";
import { CompileScript, ToField } from "../../util/Scripting";
import { Transform } from '../../util/Transform';
-import { returnFalse, emptyFunction } from '../../../Utils';
+import { returnFalse, emptyFunction, emptyDocFunction } from '../../../Utils';
// Represents one row in a key value plane
@@ -20,6 +20,7 @@ export interface KeyValuePairProps {
rowStyle: string;
fieldId: string;
doc: Document;
+ keyWidth: number;
}
@observer
export class KeyValuePair extends React.Component<KeyValuePairProps> {
@@ -54,52 +55,53 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
active: returnFalse,
onActiveChanged: emptyFunction,
ScreenToLocalTransform: Transform.Identity,
- focus: emptyFunction,
+ focus: emptyDocFunction,
};
let contents = (
<FieldView {...props} />
);
return (
<tr className={this.props.rowStyle}>
- {/* <button>X</button> */}
- <td>
+ <td className="keyValuePair-td-key" style={{ width: `${this.props.keyWidth}%` }}>
<div className="container">
- <div>{this.key.Name}</div>
<button className="delete" onClick={() => {
let field = props.Document.Get(props.fieldKey);
if (field && field instanceof Field) {
props.Document.Set(props.fieldKey, undefined);
}
}}>X</button>
+ <div className="keyValuePair-keyField">{this.key.Name}</div>
</div>
</td>
- <td><EditableView contents={contents} height={36} GetValue={() => {
- let field = props.Document.Get(props.fieldKey);
- if (field && field instanceof Field) {
- return field.ToScriptString();
- }
- return field || "";
- }}
- SetValue={(value: string) => {
- let script = CompileScript(value, { addReturn: true });
- if (!script.compiled) {
- return false;
+ <td className="keyValuePair-td-value" style={{ width: `${100 - this.props.keyWidth}%` }}>
+ <EditableView contents={contents} height={36} GetValue={() => {
+ let field = props.Document.Get(props.fieldKey);
+ if (field && field instanceof Field) {
+ return field.ToScriptString();
}
- let res = script.run();
- if (!res.success) return false;
- const field = res.result;
- if (field instanceof Field) {
- props.Document.Set(props.fieldKey, field);
- return true;
- } else {
- let dataField = ToField(field);
- if (dataField) {
- props.Document.Set(props.fieldKey, dataField);
+ return field || "";
+ }}
+ SetValue={(value: string) => {
+ let script = CompileScript(value, { addReturn: true });
+ if (!script.compiled) {
+ return false;
+ }
+ let res = script.run();
+ if (!res.success) return false;
+ const field = res.result;
+ if (field instanceof Field) {
+ props.Document.Set(props.fieldKey, field);
return true;
+ } else {
+ let dataField = ToField(field);
+ if (dataField) {
+ props.Document.Set(props.fieldKey, dataField);
+ return true;
+ }
}
- }
- return false;
- }}></EditableView></td>
+ return false;
+ }}>
+ </EditableView></td>
</tr>
);
}
diff --git a/src/client/views/nodes/LinkBox.scss b/src/client/views/nodes/LinkBox.scss
index 5d5f782d2..8bc70b48f 100644
--- a/src/client/views/nodes/LinkBox.scss
+++ b/src/client/views/nodes/LinkBox.scss
@@ -1,4 +1,4 @@
-@import "../global_variables";
+@import "../globalCssVariables";
.link-container {
width: 100%;
height: 35px;
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index b016a3d48..1c0e316e8 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -1,24 +1,16 @@
-import { observable, computed, action } from "mobx";
-import React = require("react");
-import { SelectionManager } from "../../util/SelectionManager";
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faEdit, faEye, faTimes } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { observer } from "mobx-react";
-import './LinkBox.scss';
-import { KeyStore } from '../../../fields/KeyStore';
-import { props } from "bluebird";
-import { DocumentView } from "./DocumentView";
import { Document } from "../../../fields/Document";
+import { KeyStore } from '../../../fields/KeyStore';
import { ListField } from "../../../fields/ListField";
+import { NumberField } from "../../../fields/NumberField";
import { DocumentManager } from "../../util/DocumentManager";
-import { LinkEditor } from "./LinkEditor";
-import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faEye } from '@fortawesome/free-solid-svg-icons';
-import { faEdit } from '@fortawesome/free-solid-svg-icons';
-import { faTimes } from '@fortawesome/free-solid-svg-icons';
import { undoBatch } from "../../util/UndoManager";
-import { FieldWaiting } from "../../../fields/Field";
-import { NumberField } from "../../../fields/NumberField";
+import { CollectionDockingView } from "../collections/CollectionDockingView";
+import './LinkBox.scss';
+import React = require("react");
library.add(faEye);
diff --git a/src/client/views/nodes/LinkEditor.scss b/src/client/views/nodes/LinkEditor.scss
index fb0c69cff..ea2e7289c 100644
--- a/src/client/views/nodes/LinkEditor.scss
+++ b/src/client/views/nodes/LinkEditor.scss
@@ -1,4 +1,4 @@
-@import "../global_variables";
+@import "../globalCssVariables";
.edit-container {
width: 100%;
height: auto;
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 314af64c9..9d7c2bc56 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -66,7 +66,7 @@ export class VideoBox extends React.Component<FieldViewProps> {
<Measure onResize={this.setScaling}>
{({ measureRef }) =>
<div style={{ width: "100%", height: "auto" }} ref={measureRef}>
- <video className="videobox-cont" onClick={() => { }} ref={this.setVideoRef}>
+ <video className="videobox-cont" ref={this.setVideoRef}>
<source src={path} type="video/mp4" />
Not supported.
</video>
diff --git a/src/fields/Document.ts b/src/fields/Document.ts
index 60eaf5b51..628fe684c 100644
--- a/src/fields/Document.ts
+++ b/src/fields/Document.ts
@@ -41,14 +41,14 @@ export class Document extends Field {
@computed
public get Title(): string {
let title = this.Get(KeyStore.Title, true);
- if (title) {
+ if (title || title === FieldWaiting) {
if (title !== FieldWaiting && title instanceof TextField) {
return title.Data;
}
else return "-waiting-";
}
let parTitle = this.GetT(KeyStore.Title, TextField);
- if (parTitle) {
+ if (parTitle || parTitle === FieldWaiting) {
if (parTitle !== FieldWaiting) return parTitle.Data + ".alias";
else return "-waiting-.alias";
}
diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts
index 425408273..da2d7268f 100644
--- a/src/fields/KeyStore.ts
+++ b/src/fields/KeyStore.ts
@@ -1,5 +1,4 @@
import { Key } from "./Key";
-import { KeyTransfer } from "../server/Message";
export namespace KeyStore {
export const Prototype = new Key("Prototype");
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 24c1d9b3a..34c8a7544 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -2,33 +2,34 @@ import { Field, FieldId } from "./Field";
import { Types } from "../server/Message";
import { CompileScript, ScriptOptions, CompiledScript } from "../client/util/Scripting";
import { Server } from "../client/Server";
+import { Without } from "../Utils";
+
+export interface SerializableOptions extends Without<ScriptOptions, "capturedVariables"> {
+ capturedIds: { [id: string]: string };
+}
export interface ScriptData {
script: string;
- options: ScriptOptions;
+ options: SerializableOptions;
}
export class ScriptField extends Field {
- readonly script: CompiledScript;
+ private _script?: CompiledScript;
+ get script(): CompiledScript {
+ return this._script!;
+ }
+ private options?: ScriptData;
- constructor(script: CompiledScript, id?: FieldId, save: boolean = true) {
+ constructor(script?: CompiledScript, id?: FieldId, save: boolean = true) {
super(id);
- this.script = script;
+ this._script = script;
if (save) {
Server.UpdateField(this);
}
}
- static FromJson(id: string, data: ScriptData): ScriptField {
- const script = CompileScript(data.script, data.options);
- if (!script.compiled) {
- throw new Error("Can't compile script");
- }
- return new ScriptField(script, id, false);
- }
-
ToScriptString() {
return "new ScriptField(...)";
}
@@ -45,14 +46,50 @@ export class ScriptField extends Field {
throw new Error("Script fields currently can't be updated");
}
+ static FromJson(id: string, data: ScriptData): ScriptField {
+ let field = new ScriptField(undefined, id, false);
+ field.options = data;
+ return field;
+ }
+
+ init(callback: (res: Field) => any) {
+ const options = this.options!;
+ const keys = Object.keys(options.options.capturedIds);
+ Server.GetFields(keys).then(fields => {
+ let captured: { [name: string]: Field } = {};
+ keys.forEach(key => captured[options.options.capturedIds[key]] = fields[key]);
+ const opts: ScriptOptions = {
+ addReturn: options.options.addReturn,
+ params: options.options.params,
+ requiredType: options.options.requiredType,
+ capturedVariables: captured
+ };
+ const script = CompileScript(options.script, opts);
+ if (!script.compiled) {
+ throw new Error("Can't compile script");
+ }
+ this._script = script;
+ callback(this);
+ });
+ }
+
ToJson(): { _id: string, type: Types, data: ScriptData } {
const { options, originalScript } = this.script;
+ let capturedIds: { [id: string]: string } = {};
+ for (const capt in options.capturedVariables) {
+ capturedIds[options.capturedVariables[capt].Id] = capt;
+ }
+ const opts: SerializableOptions = {
+ ...options,
+ capturedIds
+ };
+ delete (opts as any).capturedVariables;
return {
_id: this.Id,
type: Types.Script,
data: {
script: originalScript,
- options
+ options: opts,
},
};
}
diff --git a/src/server/Client.ts b/src/server/Client.ts
index 02402a5a0..b4c419438 100644
--- a/src/server/Client.ts
+++ b/src/server/Client.ts
@@ -1,15 +1,12 @@
import { computed } from "mobx";
export class Client {
+ private _guid: string;
+
constructor(guid: string) {
- this.guid = guid;
+ this._guid = guid;
}
- private guid: string;
-
- @computed
- public get GUID(): string {
- return this.guid;
- }
+ @computed public get GUID(): string { return this._guid; }
} \ No newline at end of file
diff --git a/src/server/database.ts b/src/server/database.ts
index 0bc806253..e08385d98 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -1,107 +1,73 @@
import * as mongodb from 'mongodb';
export class Database {
+ public static DocumentsCollection = 'documents';
public static Instance = new Database();
private MongoClient = mongodb.MongoClient;
private url = 'mongodb://localhost:27017/Dash';
+ private currentWrites: { [_id: string]: Promise<void> } = {};
private db?: mongodb.Db;
constructor() {
- this.MongoClient.connect(this.url, (err, client) => {
- this.db = client.db();
- });
+ this.MongoClient.connect(this.url, (err, client) => this.db = client.db());
}
- private currentWrites: { [_id: string]: Promise<void> } = {};
-
- public update(id: string, value: any, callback: () => void) {
+ public update(id: string, value: any, callback: () => void, collectionName = Database.DocumentsCollection) {
if (this.db) {
- let collection = this.db.collection('documents');
+ let collection = this.db.collection(collectionName);
const prom = this.currentWrites[id];
- const run = (promise: Promise<void>, resolve?: () => void) => {
- collection.updateOne({ _id: id }, { $set: value }, {
- upsert: true
- }, (err, res) => {
- if (err) {
- console.log(err.message);
- console.log(err.errmsg);
- }
- // if (res) {
- // console.log(JSON.stringify(res.result));
- // }
- if (this.currentWrites[id] === promise) {
- delete this.currentWrites[id];
- }
- if (resolve) {
- resolve();
- }
- callback();
+ const run = (): Promise<void> => {
+ let newProm = new Promise<void>(resolve => {
+ collection.updateOne({ _id: id }, { $set: value }, { upsert: true }
+ , (err, res) => {
+ if (err) {
+ console.log(err.message);
+ console.log(err.errmsg);
+ }
+ // if (res) {
+ // console.log(JSON.stringify(res.result));
+ // }
+ if (this.currentWrites[id] === newProm) {
+ delete this.currentWrites[id];
+ }
+ resolve();
+ callback();
+ });
});
+ return newProm;
};
- if (prom) {
- const newProm: Promise<void> = prom.then(() => run(newProm));
- this.currentWrites[id] = newProm;
- } else {
- const newProm: Promise<void> = new Promise<void>(res => run(newProm, res));
- this.currentWrites[id] = newProm;
- }
+ this.currentWrites[id] = prom ? prom.then(run) : run();
}
}
- public delete(id: string) {
- if (this.db) {
- let collection = this.db.collection('documents');
- collection.remove({ _id: id });
- }
+ public delete(id: string, collectionName = Database.DocumentsCollection) {
+ this.db && this.db.collection(collectionName).remove({ _id: id });
}
- public deleteAll(collectionName: string = 'documents'): Promise<any> {
- return new Promise(res => {
- if (this.db) {
- let collection = this.db.collection(collectionName);
- collection.deleteMany({}, res);
- }
- });
+ public deleteAll(collectionName = Database.DocumentsCollection): Promise<any> {
+ return new Promise(res =>
+ this.db && this.db.collection(collectionName).deleteMany({}, res));
}
- public insert(kvpairs: any) {
- if (this.db) {
- let collection = this.db.collection('documents');
- collection.insertOne(kvpairs, (err: any, res: any) => {
- if (err) {
- // console.log(err)
- return;
- }
- });
- }
+ public insert(kvpairs: any, collectionName = Database.DocumentsCollection) {
+ this.db && this.db.collection(collectionName).insertOne(kvpairs, (err, res) =>
+ err // && console.log(err)
+ );
}
- public getDocument(id: string, fn: (res: any) => void) {
- var result: JSON;
- if (this.db) {
- let collection = this.db.collection('documents');
- collection.findOne({ _id: id }, (err: any, res: any) => {
- result = res;
- if (!result) {
- fn(undefined);
- }
- fn(result);
- });
- }
+ public getDocument(id: string, fn: (res: any) => void, collectionName = Database.DocumentsCollection) {
+ this.db && this.db.collection(collectionName).findOne({ _id: id }, (err, result) =>
+ fn(result ? result : undefined));
}
- public getDocuments(ids: string[], fn: (res: any) => void) {
- if (this.db) {
- let collection = this.db.collection('documents');
- let cursor = collection.find({ _id: { "$in": ids } });
- cursor.toArray((err, docs) => {
- if (err) {
- console.log(err.message);
- console.log(err.errmsg);
- }
- fn(docs);
- });
- }
+ public getDocuments(ids: string[], fn: (res: any) => void, collectionName = Database.DocumentsCollection) {
+ this.db && this.db.collection(collectionName).find({ _id: { "$in": ids } }).toArray((err, docs) => {
+ if (err) {
+ console.log(err.message);
+ console.log(err.errmsg);
+ }
+ fn(docs);
+ });
}
public print() {
diff --git a/src/server/index.ts b/src/server/index.ts
index b9c7448b4..a6fe6fa2c 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1,49 +1,45 @@
+import * as bodyParser from 'body-parser';
+import { exec } from 'child_process';
+import * as cookieParser from 'cookie-parser';
import * as express from 'express';
-const app = express();
-import * as webpack from 'webpack';
-import * as wdm from 'webpack-dev-middleware';
-import * as whm from 'webpack-hot-middleware';
-import * as path from 'path';
+import * as session from 'express-session';
+import * as expressValidator from 'express-validator';
import * as formidable from 'formidable';
+import * as fs from 'fs';
+import * as mobileDetect from 'mobile-detect';
+import { ObservableMap } from 'mobx';
import * as passport from 'passport';
-import { MessageStore, Transferable } from "./Message";
-import { Client } from './Client';
+import * as path from 'path';
+import * as request from 'request';
+import * as io from 'socket.io';
import { Socket } from 'socket.io';
+import * as webpack from 'webpack';
+import * as wdm from 'webpack-dev-middleware';
+import * as whm from 'webpack-hot-middleware';
+import { Field, FieldId } from '../fields/Field';
import { Utils } from '../Utils';
-import { ObservableMap } from 'mobx';
-import { FieldId, Field } from '../fields/Field';
+import { getForgot, getLogin, getLogout, getReset, getSignup, postForgot, postLogin, postReset, postSignup } from './authentication/controllers/user_controller';
+import { DashUserModel } from './authentication/models/user_model';
+import { Client } from './Client';
import { Database } from './database';
-import * as io from 'socket.io';
-import { getLogin, postLogin, getSignup, postSignup, getLogout, postReset, getForgot, postForgot, getReset } from './authentication/controllers/user_controller';
+import { MessageStore, Transferable } from "./Message";
+import { RouteStore } from './RouteStore';
+const app = express();
const config = require('../../webpack.config');
const compiler = webpack(config);
const port = 1050; // default port to listen
const serverPort = 4321;
-import * as expressValidator from 'express-validator';
import expressFlash = require('express-flash');
import flash = require('connect-flash');
-import * as bodyParser from 'body-parser';
-import * as session from 'express-session';
-import * as cookieParser from 'cookie-parser';
-import * as mobileDetect from 'mobile-detect';
import c = require("crypto");
const MongoStore = require('connect-mongo')(session);
const mongoose = require('mongoose');
-import { DashUserModel } from './authentication/models/user_model';
-import * as fs from 'fs';
-import * as request from 'request';
-import { RouteStore } from './RouteStore';
-import { exec } from 'child_process';
-const download = (url: string, dest: fs.PathLike) => {
- request.get(url).pipe(fs.createWriteStream(dest));
-};
+const download = (url: string, dest: fs.PathLike) => request.get(url).pipe(fs.createWriteStream(dest));;
const mongoUrl = 'mongodb://localhost:27017/Dash';
mongoose.connect(mongoUrl);
-mongoose.connection.on('connected', function () {
- console.log("connected");
-});
+mongoose.connection.on('connected', () => console.log("connected"));
// SESSION MANAGEMENT AND AUTHENTICATION MIDDLEWARE
// ORDER OF IMPORTS MATTERS
@@ -54,9 +50,7 @@ app.use(session({
resave: true,
cookie: { maxAge: 7 * 24 * 60 * 60 * 1000 },
saveUninitialized: true,
- store: new MongoStore({
- url: 'mongodb://localhost:27017/Dash'
- })
+ store: new MongoStore({ url: 'mongodb://localhost:27017/Dash' })
}));
app.use(flash());
@@ -71,9 +65,7 @@ app.use((req, res, next) => {
next();
});
-app.get("/hello", (req, res) => {
- res.send("<p>Hello</p>");
-});
+app.get("/hello", (req, res) => res.send("<p>Hello</p>"));
enum Method {
GET,
@@ -112,21 +104,17 @@ function addSecureRoute(method: Method,
}
// STATIC FILE SERVING
-
-let FieldStore: ObservableMap<FieldId, Field> = new ObservableMap();
-
app.use(express.static(__dirname + RouteStore.public));
app.use(RouteStore.images, express.static(__dirname + RouteStore.public));
-app.get("/pull", (req, res) => {
+app.get("/pull", (req, res) =>
exec('"C:\\Program Files\\Git\\git-bash.exe" -c "git pull"', (err, stdout, stderr) => {
if (err) {
res.send(err.message);
return;
}
res.redirect("/");
- });
-});
+ }));
// GETTERS
@@ -143,11 +131,8 @@ addSecureRoute(
Method.GET,
(user, res, req) => {
let detector = new mobileDetect(req.headers['user-agent'] || "");
- if (detector.mobile() !== null) {
- res.sendFile(path.join(__dirname, '../../deploy/mobile/image.html'));
- } else {
- res.sendFile(path.join(__dirname, '../../deploy/index.html'));
- }
+ let filename = detector.mobile() !== null ? 'mobile/image.html' : 'index.html';
+ res.sendFile(path.join(__dirname, '../../deploy/' + filename));
},
undefined,
RouteStore.home,
@@ -163,12 +148,7 @@ addSecureRoute(
addSecureRoute(
Method.GET,
- (user, res) => {
- res.send(JSON.stringify({
- id: user.id,
- email: user.email
- }));
- },
+ (user, res) => res.send(JSON.stringify({ id: user.id, email: user.email })),
undefined,
RouteStore.getCurrUser
);
@@ -185,10 +165,9 @@ addSecureRoute(
console.log("upload");
form.parse(req, (err, fields, files) => {
console.log("parsing");
- let names: any[] = [];
+ let names: string[] = [];
for (const name in files) {
- let file = files[name];
- names.push(`/files/` + path.basename(file.path));
+ names.push(`/files/` + path.basename(files[name].path));
}
res.send(names);
});
@@ -218,28 +197,22 @@ app.post(RouteStore.forgot, postForgot);
app.get(RouteStore.reset, getReset);
app.post(RouteStore.reset, postReset);
-app.use(RouteStore.corsProxy, (req, res) => {
- req.pipe(request(req.url.substring(1))).pipe(res);
-});
+app.use(RouteStore.corsProxy, (req, res) =>
+ req.pipe(request(req.url.substring(1))).pipe(res));
-app.get(RouteStore.delete, (req, res) => {
- deleteFields().then(() => res.redirect(RouteStore.home));
-});
+app.get(RouteStore.delete, (req, res) =>
+ deleteFields().then(() => res.redirect(RouteStore.home)));
-app.get(RouteStore.deleteAll, (req, res) => {
- deleteAll().then(() => res.redirect(RouteStore.home));
-});
+app.get(RouteStore.deleteAll, (req, res) =>
+ deleteAll().then(() => res.redirect(RouteStore.home)));
-app.use(wdm(compiler, {
- publicPath: config.output.publicPath
-}));
+app.use(wdm(compiler, { publicPath: config.output.publicPath }));
app.use(whm(compiler));
// start the Express server
-app.listen(port, () => {
- console.log(`server started at http://localhost:${port}`);
-});
+app.listen(port, () =>
+ console.log(`server started at http://localhost:${port}`));
const server = io();
interface Map {
@@ -274,14 +247,8 @@ function barReceived(guid: String) {
}
function getField([id, callback]: [string, (result: any) => void]) {
- Database.Instance.getDocument(id, (result: any) => {
- if (result) {
- callback(result);
- }
- else {
- callback(undefined);
- }
- });
+ Database.Instance.getDocument(id, (result: any) =>
+ callback(result ? result : undefined));
}
function getFields([ids, callback]: [string[], (result: any) => void]) {
@@ -289,9 +256,8 @@ function getFields([ids, callback]: [string[], (result: any) => void]) {
}
function setField(socket: Socket, newValue: Transferable) {
- Database.Instance.update(newValue._id, newValue, () => {
- socket.broadcast.emit(MessageStore.SetField.Message, newValue);
- });
+ Database.Instance.update(newValue._id, newValue, () =>
+ socket.broadcast.emit(MessageStore.SetField.Message, newValue));
}
server.listen(serverPort);