aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Utils.ts27
-rw-r--r--src/client/Server.ts111
-rw-r--r--src/client/SocketStub.ts73
-rw-r--r--src/client/documents/Documents.ts198
-rw-r--r--src/client/northstar/core/BaseObject.ts6
-rw-r--r--src/client/northstar/core/attribute/AttributeModel.ts10
-rw-r--r--src/client/northstar/core/attribute/AttributeTransformationModel.ts19
-rw-r--r--src/client/northstar/core/attribute/CalculatedAttributeModel.ts6
-rw-r--r--src/client/northstar/core/brusher/IBaseBrushable.ts4
-rw-r--r--src/client/northstar/core/brusher/IBaseBrusher.ts2
-rw-r--r--src/client/northstar/core/filter/FilterModel.ts8
-rw-r--r--src/client/northstar/core/filter/IBaseFilterConsumer.ts4
-rw-r--r--src/client/northstar/core/filter/IBaseFilterProvider.ts2
-rw-r--r--src/client/northstar/core/filter/ValueComparision.ts22
-rw-r--r--src/client/northstar/dash-fields/HistogramField.ts17
-rw-r--r--src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts54
-rw-r--r--src/client/northstar/dash-nodes/HistogramBox.scss10
-rw-r--r--src/client/northstar/dash-nodes/HistogramBox.tsx56
-rw-r--r--src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss16
-rw-r--r--src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx101
-rw-r--r--src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx21
-rw-r--r--src/client/northstar/manager/Gateway.ts72
-rw-r--r--src/client/northstar/model/ModelExtensions.ts32
-rw-r--r--src/client/northstar/model/ModelHelpers.ts18
-rw-r--r--src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts4
-rw-r--r--src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts28
-rw-r--r--src/client/northstar/model/binRanges/NominalVisualBinRange.ts4
-rw-r--r--src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts2
-rw-r--r--src/client/northstar/model/binRanges/VisualBinRange.ts6
-rw-r--r--src/client/northstar/model/binRanges/VisualBinRangeHelper.ts23
-rw-r--r--src/client/northstar/model/idea/MetricTypeMapping.ts28
-rw-r--r--src/client/northstar/operations/BaseOperation.ts17
-rw-r--r--src/client/northstar/operations/HistogramOperation.ts8
-rw-r--r--src/client/northstar/utils/ArrayUtil.ts16
-rw-r--r--src/client/northstar/utils/Extensions.ts6
-rw-r--r--src/client/northstar/utils/GeometryUtil.ts36
-rw-r--r--src/client/northstar/utils/MathUtil.ts50
-rw-r--r--src/client/northstar/utils/SizeConverter.ts18
-rw-r--r--src/client/northstar/utils/StyleContants.ts2
-rw-r--r--src/client/northstar/utils/Utils.ts20
-rw-r--r--src/client/util/DocumentManager.ts41
-rw-r--r--src/client/util/DragManager.ts227
-rw-r--r--src/client/util/RichTextSchema.tsx42
-rw-r--r--src/client/util/Scripting.ts127
-rw-r--r--src/client/util/ScrollBox.tsx4
-rw-r--r--src/client/util/SelectionManager.ts34
-rw-r--r--src/client/util/TooltipTextMenu.scss2
-rw-r--r--src/client/util/TooltipTextMenu.tsx240
-rw-r--r--src/client/util/Transform.ts56
-rw-r--r--src/client/util/TypedEvent.ts4
-rw-r--r--src/client/util/UndoManager.ts50
-rw-r--r--src/client/util/type_decls.d2
-rw-r--r--src/client/views/ContextMenu.scss4
-rw-r--r--src/client/views/ContextMenu.tsx14
-rw-r--r--src/client/views/ContextMenuItem.tsx4
-rw-r--r--src/client/views/DocumentDecorations.scss126
-rw-r--r--src/client/views/DocumentDecorations.tsx327
-rw-r--r--src/client/views/EditableView.tsx14
-rw-r--r--src/client/views/InkingCanvas.scss7
-rw-r--r--src/client/views/InkingCanvas.tsx45
-rw-r--r--src/client/views/InkingControl.scss2
-rw-r--r--src/client/views/InkingControl.tsx19
-rw-r--r--src/client/views/InkingStroke.tsx6
-rw-r--r--src/client/views/Main.scss15
-rw-r--r--src/client/views/Main.tsx311
-rw-r--r--src/client/views/MainOverlayTextBox.scss19
-rw-r--r--src/client/views/MainOverlayTextBox.tsx110
-rw-r--r--src/client/views/PresentationView.tsx39
-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.tsx194
-rw-r--r--src/client/views/collections/CollectionDockingView.scss23
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx159
-rw-r--r--src/client/views/collections/CollectionPDFView.scss2
-rw-r--r--src/client/views/collections/CollectionPDFView.tsx57
-rw-r--r--src/client/views/collections/CollectionSchemaView.scss6
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx153
-rw-r--r--src/client/views/collections/CollectionSubView.tsx (renamed from src/client/views/collections/CollectionViewBase.tsx)148
-rw-r--r--src/client/views/collections/CollectionTreeView.scss90
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx46
-rw-r--r--src/client/views/collections/CollectionVideoView.scss2
-rw-r--r--src/client/views/collections/CollectionVideoView.tsx59
-rw-r--r--src/client/views/collections/CollectionView.tsx185
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx10
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx121
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss24
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx55
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss30
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx222
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.scss14
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx82
-rw-r--r--src/client/views/collections/collectionFreeForm/PreviewCursor.scss23
-rw-r--r--src/client/views/collections/collectionFreeForm/PreviewCursor.tsx119
-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/Annotation.tsx128
-rw-r--r--src/client/views/nodes/AudioBox.tsx14
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx37
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx48
-rw-r--r--src/client/views/nodes/DocumentView.scss24
-rw-r--r--src/client/views/nodes/DocumentView.tsx634
-rw-r--r--src/client/views/nodes/FieldView.tsx89
-rw-r--r--src/client/views/nodes/FormattedTextBox.scss3
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx185
-rw-r--r--src/client/views/nodes/ImageBox.tsx20
-rw-r--r--src/client/views/nodes/KeyValueBox.scss93
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx88
-rw-r--r--src/client/views/nodes/KeyValuePair.scss34
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx120
-rw-r--r--src/client/views/nodes/LinkBox.scss2
-rw-r--r--src/client/views/nodes/LinkBox.tsx37
-rw-r--r--src/client/views/nodes/LinkEditor.scss2
-rw-r--r--src/client/views/nodes/LinkEditor.tsx6
-rw-r--r--src/client/views/nodes/LinkMenu.tsx12
-rw-r--r--src/client/views/nodes/PDFBox.scss4
-rw-r--r--src/client/views/nodes/PDFBox.tsx150
-rw-r--r--src/client/views/nodes/Sticky.tsx138
-rw-r--r--src/client/views/nodes/VideoBox.tsx30
-rw-r--r--src/client/views/nodes/WebBox.scss2
-rw-r--r--src/client/views/nodes/WebBox.tsx12
-rw-r--r--src/debug/Test.tsx8
-rw-r--r--src/debug/Viewer.tsx54
-rw-r--r--src/fields/AudioField.ts8
-rw-r--r--src/fields/BasicField.ts8
-rw-r--r--src/fields/BooleanField.ts25
-rw-r--r--src/fields/Document.ts201
-rw-r--r--src/fields/DocumentReference.ts6
-rw-r--r--src/fields/Field.ts8
-rw-r--r--src/fields/HtmlField.ts6
-rw-r--r--src/fields/ImageField.ts8
-rw-r--r--src/fields/InkField.ts6
-rw-r--r--src/fields/Key.ts10
-rw-r--r--src/fields/KeyStore.ts19
-rw-r--r--src/fields/ListField.ts139
-rw-r--r--src/fields/NumberField.ts10
-rw-r--r--src/fields/PDFField.ts10
-rw-r--r--src/fields/RichTextField.ts6
-rw-r--r--src/fields/ScriptField.ts101
-rw-r--r--src/fields/TextField.ts8
-rw-r--r--src/fields/TupleField.ts10
-rw-r--r--src/fields/VideoField.ts8
-rw-r--r--src/fields/WebField.ts8
-rw-r--r--src/mobile/ImageUpload.tsx59
-rw-r--r--src/server/Client.ts12
-rw-r--r--src/server/Message.ts120
-rw-r--r--src/server/ServerUtil.ts118
-rw-r--r--src/server/authentication/config/passport.ts6
-rw-r--r--src/server/authentication/controllers/WorkspacesMenu.tsx4
-rw-r--r--src/server/authentication/controllers/user_controller.ts26
-rw-r--r--src/server/authentication/models/current_user_utils.ts107
-rw-r--r--src/server/authentication/models/user_model.ts23
-rw-r--r--src/server/database.ts114
-rw-r--r--src/server/index.ts190
-rw-r--r--src/server/public/files/.gitignore1
-rw-r--r--src/server/public/files/upload_e72669595eae4384a2a32196496f4f05.pdfbin548616 -> 0 bytes
157 files changed, 4743 insertions, 3590 deletions
diff --git a/src/Utils.ts b/src/Utils.ts
index a5d9bd0ca..e07d4e82d 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -1,24 +1,25 @@
import v4 = require('uuid/v4');
import v5 = require("uuid/v5");
import { Socket } from 'socket.io';
-import { Message, Types } from './server/Message';
+import { Message, Types, Transferable } from './server/Message';
+import { Document } from './fields/Document';
export class Utils {
public static GenerateGuid(): string {
- return v4()
+ return v4();
}
public static GenerateDeterministicGuid(seed: string): string {
- return v5(seed, v5.URL)
+ return v5(seed, v5.URL);
}
public static GetScreenTransform(ele: HTMLElement): { scale: number, translateX: number, translateY: number } {
if (!ele) {
- return { scale: 1, translateX: 1, translateY: 1 }
+ return { scale: 1, translateX: 1, translateY: 1 };
}
const rect = ele.getBoundingClientRect();
- const scale = ele.offsetWidth == 0 && rect.width == 0 ? 1 : rect.width / ele.offsetWidth;
+ const scale = ele.offsetWidth === 0 && rect.width === 0 ? 1 : rect.width / ele.offsetWidth;
const translateX = rect.left;
const translateY = rect.top;
@@ -47,7 +48,7 @@ export class Utils {
if (this.logFilter !== undefined && this.logFilter !== message.type) {
return;
}
- let idString = (message._id || message.id || "").padStart(36, ' ');
+ let idString = (message.id || "").padStart(36, ' ');
prefix = prefix.padEnd(16, ' ');
console.log(`${prefix}: ${idString}, ${receiving ? 'receiving' : 'sending'} ${messageName} with data ${JSON.stringify(message)}`);
}
@@ -55,8 +56,8 @@ export class Utils {
return (args: any) => {
this.log(prefix, messageName, args, true);
func(args);
- }
- };
+ };
+ }
public static Emit<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T) {
this.log("Emit", message.Name, args, false);
@@ -81,9 +82,17 @@ export class Utils {
public static AddServerHandlerCallback<T>(socket: Socket, message: Message<T>, handler: (args: [T, (res: any) => any]) => any) {
socket.on(message.Message, (arg: T, fn: (res: any) => any) => {
this.log('S receiving', message.Name, arg, true);
- handler([arg, this.loggingCallback('S sending', fn, message.Name)])
+ handler([arg, this.loggingCallback('S sending', fn, message.Name)]);
});
}
}
+export function returnTrue() { return true; }
+
+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 37e3c2c0d..66e9878d9 100644
--- a/src/client/Server.ts
+++ b/src/client/Server.ts
@@ -1,17 +1,16 @@
-import { Key } from "../fields/Key"
-import { ObservableMap, action, reaction } from "mobx";
-import { Field, FieldWaiting, FIELD_WAITING, Opt, FieldId } from "../fields/Field"
-import { Document } from "../fields/Document"
+import { Key } from "../fields/Key";
+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 {
public static ClientFieldsCached: ObservableMap<FieldId, Field | FIELD_WAITING> = new ObservableMap();
static Socket: SocketIOClient.Socket = OpenSocket(`${window.location.protocol}//${window.location.hostname}:4321`);
- static GUID: string = Utils.GenerateGuid()
-
+ static GUID: string = Utils.GenerateGuid();
// Retrieves the cached value of the field and sends a request to the server for the real value (if it's not cached).
// Call this is from within a reaction and test whether the return value is FieldWaiting.
@@ -21,52 +20,52 @@ export class Server {
let fn = (cb: (field: Opt<Field>) => void) => {
let cached = this.ClientFieldsCached.get(fieldid);
- if (!cached) {
+ if (cached === undefined) {
this.ClientFieldsCached.set(fieldid, FieldWaiting);
SocketStub.SEND_FIELD_REQUEST(fieldid, action((field: Field | undefined) => {
let cached = this.ClientFieldsCached.get(fieldid);
- if (cached != FieldWaiting)
+ if (cached !== FieldWaiting) {
cb(cached);
+ }
else {
if (field) {
this.ClientFieldsCached.set(fieldid, field);
} else {
- this.ClientFieldsCached.delete(fieldid)
+ this.ClientFieldsCached.delete(fieldid);
}
- cb(field)
+ cb(field);
}
}));
- } else if (cached != FieldWaiting) {
+ } else if (cached !== FieldWaiting) {
setTimeout(() => cb(cached as Field), 0);
} else {
- reaction(() => {
- return this.ClientFieldsCached.get(fieldid);
- }, (field, reaction) => {
- if (field !== "<Waiting>") {
- reaction.dispose()
- cb(field)
- }
- })
+ reaction(() => this.ClientFieldsCached.get(fieldid),
+ (field, reaction) => {
+ if (field !== FieldWaiting) {
+ reaction.dispose();
+ cb(field);
+ }
+ });
}
- }
+ };
if (callback) {
fn(callback);
} else {
- return new Promise(res => fn(res));
+ return new Promise(fn);
}
}
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,37 +78,36 @@ export class Server {
for (let id of neededFieldIds) {
let field = fields[id];
if (field) {
- if (!(this.ClientFieldsCached.get(field.Id) instanceof Field)) {
- this.ClientFieldsCached.set(field.Id, 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")
+ throw new Error("we shouldn't be trying to replace things that are already in the cache");
}
} else {
if (this.ClientFieldsCached.get(id) === FieldWaiting) {
this.ClientFieldsCached.delete(id);
} else {
- throw new Error("we shouldn't be trying to replace things that are already in the cache")
+ throw new Error("we shouldn't be trying to replace things that are already in the cache");
}
}
}
- reaction(() => {
- return waitingFieldIds.map(id => this.ClientFieldsCached.get(id));
- }, (cachedFields, reaction) => {
- if (!cachedFields.some(field => !field || field === FieldWaiting)) {
- reaction.dispose();
- for (let field of cachedFields) {
- let realField = field as Field;
- existingFields[realField.Id] = realField;
+ reaction(() => waitingFieldIds.map(id => this.ClientFieldsCached.get(id)),
+ (cachedFields, reaction) => {
+ if (!cachedFields.some(field => field === FieldWaiting)) {
+ const realFields = cachedFields as Opt<Field>[];
+ reaction.dispose();
+ waitingFieldIds.forEach((id, index) => {
+ existingFields[id] = realFields[index];
+ });
+ cb({ ...fields, ...existingFields });
}
- cb({ ...fields, ...existingFields })
- }
- }, { fireImmediately: true })
+ }, { fireImmediately: true });
}));
- };
+ });
if (callback) {
fn(callback);
} else {
- return new Promise(res => fn(res));
+ return new Promise(fn);
}
}
@@ -128,20 +126,13 @@ export class Server {
}
}
- public static AddDocument(document: Document) {
- SocketStub.SEND_ADD_DOCUMENT(document);
- }
- public static AddDocumentField(doc: Document, key: Key, value: Field) {
- console.log("Add doc field " + doc.Title + " " + key.Name + " fid " + value.Id + " " + value);
- SocketStub.SEND_ADD_DOCUMENT_FIELD(doc, key, value);
- }
public static DeleteDocumentField(doc: Document, key: Key) {
SocketStub.SEND_DELETE_DOCUMENT_FIELD(doc, key);
}
public static UpdateField(field: Field) {
if (!this.ClientFieldsCached.has(field.Id)) {
- this.ClientFieldsCached.set(field.Id, field)
+ this.ClientFieldsCached.set(field.Id, field);
}
SocketStub.SEND_SET_FIELD(field);
}
@@ -153,27 +144,27 @@ export class Server {
@action
private static cacheField(clientField: Field) {
var cached = this.ClientFieldsCached.get(clientField.Id);
- if (!cached || cached == FieldWaiting) {
+ if (!cached) {
this.ClientFieldsCached.set(clientField.Id, clientField);
} else {
// probably should overwrite the values within any field that was already here...
}
- return this.ClientFieldsCached.get(clientField.Id) as Field;
+ return this.ClientFieldsCached.get(clientField.Id);
}
@action
- static updateField(field: { _id: string, data: any, type: Types }) {
- if (Server.ClientFieldsCached.has(field._id)) {
- var f = Server.ClientFieldsCached.get(field._id);
- if (f && f != FieldWaiting) {
- // console.log("Applying : " + field._id);
+ static updateField(field: { id: string, data: any, type: Types }) {
+ if (Server.ClientFieldsCached.has(field.id)) {
+ var f = Server.ClientFieldsCached.get(field.id);
+ if (f) {
+ // console.log("Applying : " + field.id);
f.UpdateFromServer(field.data);
- f.init(() => { });
+ f.init(emptyFunction);
} else {
- // console.log("Not applying wa : " + field._id);
+ // console.log("Not applying wa : " + field.id);
}
} else {
- // console.log("Not applying mi : " + field._id);
+ // console.log("Not applying mi : " + field.id);
}
}
}
diff --git a/src/client/SocketStub.ts b/src/client/SocketStub.ts
index 5045037c5..382a81f66 100644
--- a/src/client/SocketStub.ts
+++ b/src/client/SocketStub.ts
@@ -1,8 +1,8 @@
-import { Key } from "../fields/Key"
-import { Field, FieldId, Opt } from "../fields/Field"
+import { Key } from "../fields/Key";
+import { Field, FieldId, Opt } from "../fields/Field";
import { ObservableMap } from "mobx";
-import { Document } from "../fields/Document"
-import { MessageStore, DocumentTransfer } from "../server/Message";
+import { Document } from "../fields/Document";
+import { MessageStore, Transferable } from "../server/Message";
import { Utils } from "../Utils";
import { Server } from "./Server";
import { ServerUtils } from "../server/ServerUtil";
@@ -16,76 +16,36 @@ export interface FieldMap {
export class SocketStub {
static FieldStore: ObservableMap<FieldId, Field> = new ObservableMap();
- public static SEND_ADD_DOCUMENT(document: Document) {
-
- // Send a serialized version of the document to the server
- // ...SOCKET(ADD_DOCUMENT, serialied document)
-
- // server stores each document field in its repository of stored fields
- // document.fields.forEach((f, key) => this.FieldStore.set((f as Field).Id, f as Field));
- // let strippedDoc = new Document(document.Id);
- // document.fields.forEach((f, key) => {
- // if (f) {
- // // let args: SetFieldArgs = new SetFieldArgs(f.Id, f.GetValue())
- // let args: Transferable = f.ToJson()
- // Utils.Emit(Server.Socket, MessageStore.SetField, args)
- // }
- // })
-
- // // server stores stripped down document (w/ only field id proxies) in the field store
- // this.FieldStore.set(document.Id, new Document(document.Id));
- // document.fields.forEach((f, key) => (this.FieldStore.get(document.Id) as Document)._proxies.set(key.Id, (f as Field).Id));
-
- console.log("sending " + document.Title);
- Utils.Emit(Server.Socket, MessageStore.AddDocument, new DocumentTransfer(document.ToJson()))
- }
public static SEND_FIELD_REQUEST(fieldid: FieldId): Promise<Opt<Field>>;
public static SEND_FIELD_REQUEST(fieldid: FieldId, callback: (field: Opt<Field>) => void): void;
public static SEND_FIELD_REQUEST(fieldid: FieldId, callback?: (field: Opt<Field>) => void): Promise<Opt<Field>> | void {
let fn = function (cb: (field: Opt<Field>) => void) {
- Utils.EmitCallback(Server.Socket, MessageStore.GetField, fieldid, (field: any) => {
+ Utils.EmitCallback(Server.Socket, MessageStore.GetField, fieldid, (field: Transferable) => {
if (field) {
ServerUtils.FromJson(field).init(cb);
} else {
cb(undefined);
}
- })
- }
+ });
+ };
if (callback) {
fn(callback);
} else {
- return new Promise(res => fn(res))
+ return new Promise(fn);
}
}
public static SEND_FIELDS_REQUEST(fieldIds: FieldId[], callback: (fields: FieldMap) => any) {
- Utils.EmitCallback(Server.Socket, MessageStore.GetFields, fieldIds, (fields: any[]) => {
- let fieldMap: any = {};
- for (let field of fields) {
- fieldMap[field._id] = ServerUtils.FromJson(field);
- }
- callback(fieldMap)
+ Utils.EmitCallback(Server.Socket, MessageStore.GetFields, fieldIds, (fields: Transferable[]) => {
+ let fieldMap: FieldMap = {};
+ fields.map(field => fieldMap[field.id] = ServerUtils.FromJson(field));
+ let proms = Object.values(fieldMap).map(val =>
+ new Promise(resolve => val!.init(resolve)));
+ Promise.all(proms).then(() => callback(fieldMap));
});
}
- public static SEND_ADD_DOCUMENT_FIELD(doc: Document, key: Key, value: Field) {
-
- // Send a serialized version of the field to the server along with the
- // associated info of the document id and key where it is used.
-
- // ...SOCKET(ADD_DOCUMENT_FIELD, document id, key id, serialized field)
-
- // server updates its document to hold a proxy mapping from key => fieldId
- var document = this.FieldStore.get(doc.Id) as Document;
- if (document)
- document._proxies.set(key.Id, value.Id);
-
- // server adds the field to its repository of fields
- this.FieldStore.set(value.Id, value);
- // Utils.Emit(Server.Socket, MessageStore.AddDocument, new DocumentTransfer(doc.ToJson()))
- }
-
public static SEND_DELETE_DOCUMENT_FIELD(doc: Document, key: Key) {
// Send a request to delete the field stored under the specified key from the document
@@ -93,8 +53,9 @@ export class SocketStub {
// Server removes the field id from the document's list of field proxies
var document = this.FieldStore.get(doc.Id) as Document;
- if (document)
+ if (document) {
document._proxies.delete(key.Id);
+ }
}
public static SEND_SET_FIELD(field: Field) {
@@ -103,6 +64,6 @@ export class SocketStub {
// ...SOCKET(SET_FIELD, field id, serialized field value)
// Server updates the value of the field in its fieldstore
- Utils.Emit(Server.Socket, MessageStore.SetField, field.ToJson())
+ Utils.Emit(Server.Socket, MessageStore.SetField, field.ToJson());
}
}
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 0bf275df8..4febfa7eb 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";
@@ -17,7 +17,8 @@ import { HistogramOperation } from "../northstar/operations/HistogramOperation";
import { Server } from "../Server";
import { CollectionPDFView } from "../views/collections/CollectionPDFView";
import { CollectionVideoView } from "../views/collections/CollectionVideoView";
-import { CollectionView, CollectionViewType } from "../views/collections/CollectionView";
+import { CollectionView } from "../views/collections/CollectionView";
+import { CollectionViewType } from "../views/collections/CollectionBaseView";
import { AudioBox } from "../views/nodes/AudioBox";
import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
import { ImageBox } from "../views/nodes/ImageBox";
@@ -25,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;
@@ -43,6 +50,7 @@ export interface DocumentOptions {
layoutKeys?: Key[];
viewType?: number;
backgroundColor?: string;
+ copyDraggedItems?: boolean;
}
export namespace Documents {
@@ -62,17 +70,20 @@ export namespace Documents {
const webProtoId = "webProto";
const collProtoId = "collectionProto";
const kvpProtoId = "kvpProto";
- const videoProtoId = "videoProto"
+ const videoProtoId = "videoProto";
const audioProtoId = "audioProto";
export function initProtos(): Promise<void> {
- return Server.GetFields([textProtoId, histoProtoId, collProtoId, imageProtoId, webProtoId, kvpProtoId]).then(fields => {
- textProto = fields[textProtoId] as Document;
- histoProto = fields[histoProtoId] as Document;
- collProto = fields[collProtoId] as Document;
- imageProto = fields[imageProtoId] as Document;
- webProto = fields[webProtoId] as Document;
- kvpProto = fields[kvpProtoId] as Document;
+ return Server.GetFields([textProtoId, histoProtoId, collProtoId, pdfProtoId, imageProtoId, videoProtoId, audioProtoId, webProtoId, kvpProtoId]).then(fields => {
+ textProto = fields[textProtoId] as Document || CreateTextPrototype();
+ histoProto = fields[histoProtoId] as Document || CreateHistogramPrototype();
+ collProto = fields[collProtoId] as Document || CreateCollectionPrototype();
+ imageProto = fields[imageProtoId] as Document || CreateImagePrototype();
+ webProto = fields[webProtoId] as Document || CreateWebPrototype();
+ kvpProto = fields[kvpProtoId] as Document || CreateKVPPrototype();
+ videoProto = fields[videoProtoId] as Document || CreateVideoPrototype();
+ audioProto = fields[audioProtoId] as Document || CreateAudioPrototype();
+ pdfProto = fields[pdfProtoId] as Document || CreatePdfPrototype();
});
}
function assignOptions(doc: Document, options: DocumentOptions): Document {
@@ -86,6 +97,7 @@ export namespace Documents {
if (options.ink !== undefined) { doc.Set(KeyStore.Ink, new InkField(options.ink)); }
if (options.layout !== undefined) { doc.SetText(KeyStore.Layout, options.layout); }
if (options.layoutKeys !== undefined) { doc.Set(KeyStore.LayoutKeys, new ListField(options.layoutKeys)); }
+ if (options.copyDraggedItems !== undefined) { doc.SetBoolean(KeyStore.CopyDraggedItems, options.copyDraggedItems); }
return doc;
}
@@ -96,7 +108,7 @@ export namespace Documents {
if (options.height !== undefined) { doc.SetNumber(KeyStore.Height, options.height); }
if (options.panx !== undefined) { doc.SetNumber(KeyStore.PanX, options.panx); }
if (options.pany !== undefined) { doc.SetNumber(KeyStore.PanY, options.pany); }
- return doc
+ return doc;
}
function setupPrototypeOptions(protoId: string, title: string, layout: string, options: DocumentOptions): Document {
@@ -104,78 +116,73 @@ export namespace Documents {
}
function SetInstanceOptions<T, U extends Field & { Data: T }>(doc: Document, options: DocumentOptions, value: [T, { new(): U }] | Document, id?: string) {
var deleg = doc.MakeDelegate(id);
- if (value instanceof Document)
- deleg.Set(KeyStore.Data, value)
- else
+ if (value instanceof Document) {
+ deleg.Set(KeyStore.Data, value);
+ }
+ else {
deleg.SetData(KeyStore.Data, value[0], value[1]);
+ }
return assignOptions(deleg, options);
}
- function GetImagePrototype(): Document {
- if (!imageProto) {
- imageProto = setupPrototypeOptions(imageProtoId, "IMAGE_PROTO", CollectionView.LayoutString("AnnotationsKey"),
- { x: 0, y: 0, nativeWidth: 300, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] });
- imageProto.SetText(KeyStore.BackgroundLayout, ImageBox.LayoutString());
- imageProto.SetNumber(KeyStore.CurPage, 0);
- }
+ function CreateImagePrototype(): Document {
+ let imageProto = setupPrototypeOptions(imageProtoId, "IMAGE_PROTO", CollectionView.LayoutString("AnnotationsKey"),
+ { x: 0, y: 0, nativeWidth: 600, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] });
+ imageProto.SetText(KeyStore.BackgroundLayout, ImageBox.LayoutString());
+ imageProto.SetNumber(KeyStore.CurPage, 0);
return imageProto;
}
- function GetHistogramPrototype(): Document {
- if (!histoProto) {
- histoProto = setupPrototypeOptions(histoProtoId, "HISTO PROTO", CollectionView.LayoutString("AnnotationsKey"),
- { x: 0, y: 0, width: 300, height: 300, backgroundColor: "black", layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] });
- histoProto.SetText(KeyStore.BackgroundLayout, HistogramBox.LayoutString());
- }
+
+ function CreateHistogramPrototype(): Document {
+ let histoProto = setupPrototypeOptions(histoProtoId, "HISTO PROTO", CollectionView.LayoutString("AnnotationsKey"),
+ { x: 0, y: 0, width: 300, height: 300, backgroundColor: "black", layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] });
+ histoProto.SetText(KeyStore.BackgroundLayout, HistogramBox.LayoutString());
return histoProto;
}
- function GetTextPrototype(): Document {
- return textProto ? textProto :
- textProto = setupPrototypeOptions(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(),
- { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] });
- }
- function GetPdfPrototype(): Document {
- if (!pdfProto) {
- pdfProto = setupPrototypeOptions(pdfProtoId, "PDF_PROTO", CollectionPDFView.LayoutString("AnnotationsKey"),
- { x: 0, y: 0, nativeWidth: 600, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations] });
- pdfProto.SetNumber(KeyStore.CurPage, 1);
- pdfProto.SetText(KeyStore.BackgroundLayout, PDFBox.LayoutString());
- }
+ function CreateTextPrototype(): Document {
+ let textProto = setupPrototypeOptions(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(),
+ { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] });
+ return textProto;
+ }
+ function CreatePdfPrototype(): Document {
+ let pdfProto = setupPrototypeOptions(pdfProtoId, "PDF_PROTO", CollectionPDFView.LayoutString("AnnotationsKey"),
+ { x: 0, y: 0, nativeWidth: 1200, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations] });
+ pdfProto.SetNumber(KeyStore.CurPage, 1);
+ pdfProto.SetText(KeyStore.BackgroundLayout, PDFBox.LayoutString());
return pdfProto;
}
- function GetWebPrototype(): Document {
- return webProto ? webProto :
- webProto = setupPrototypeOptions(webProtoId, "WEB_PROTO", WebBox.LayoutString(),
- { x: 0, y: 0, width: 300, height: 300, layoutKeys: [KeyStore.Data] });
+ function CreateWebPrototype(): Document {
+ let webProto = setupPrototypeOptions(webProtoId, "WEB_PROTO", WebBox.LayoutString(),
+ { x: 0, y: 0, width: 300, height: 300, layoutKeys: [KeyStore.Data] });
+ return webProto;
}
- function GetCollectionPrototype(): Document {
- return collProto ? collProto :
- collProto = setupPrototypeOptions(collProtoId, "COLLECTION_PROTO", CollectionView.LayoutString("DataKey"),
- { panx: 0, pany: 0, scale: 1, width: 500, height: 500, layoutKeys: [KeyStore.Data] });
+ function CreateCollectionPrototype(): Document {
+ let collProto = setupPrototypeOptions(collProtoId, "COLLECTION_PROTO", CollectionView.LayoutString("DataKey"),
+ { panx: 0, pany: 0, scale: 1, width: 500, height: 500, layoutKeys: [KeyStore.Data] });
+ return collProto;
}
- function GetKVPPrototype(): Document {
- return kvpProto ? kvpProto :
- kvpProto = setupPrototypeOptions(kvpProtoId, "KVP_PROTO", KeyValueBox.LayoutString(),
- { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] })
- }
- function GetVideoPrototype(): Document {
- if (!videoProto) {
- videoProto = setupPrototypeOptions(videoProtoId, "VIDEO_PROTO", CollectionVideoView.LayoutString("AnnotationsKey"),
- { x: 0, y: 0, nativeWidth: 600, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] });
- videoProto.SetNumber(KeyStore.CurPage, 0);
- videoProto.SetText(KeyStore.BackgroundLayout, VideoBox.LayoutString());
- }
+ function CreateKVPPrototype(): Document {
+ let kvpProto = setupPrototypeOptions(kvpProtoId, "KVP_PROTO", KeyValueBox.LayoutString(),
+ { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] });
+ return kvpProto;
+ }
+ function CreateVideoPrototype(): Document {
+ let videoProto = setupPrototypeOptions(videoProtoId, "VIDEO_PROTO", CollectionVideoView.LayoutString("AnnotationsKey"),
+ { x: 0, y: 0, nativeWidth: 600, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] });
+ videoProto.SetNumber(KeyStore.CurPage, 0);
+ videoProto.SetText(KeyStore.BackgroundLayout, VideoBox.LayoutString());
return videoProto;
}
- function GetAudioPrototype(): Document {
- return audioProto ? audioProto :
- audioProto = setupPrototypeOptions(audioProtoId, "AUDIO_PROTO", AudioBox.LayoutString(),
- { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] })
+ function CreateAudioPrototype(): Document {
+ let audioProto = setupPrototypeOptions(audioProtoId, "AUDIO_PROTO", AudioBox.LayoutString(),
+ { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] });
+ return audioProto;
}
export function ImageDocument(url: string, options: DocumentOptions = {}) {
- return assignToDelegate(SetInstanceOptions(GetImagePrototype(), options, [new URL(url), ImageField]).MakeDelegate(), { ...options, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] });
+ return assignToDelegate(SetInstanceOptions(imageProto, options, [new URL(url), ImageField]).MakeDelegate(), { ...options, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] });
// let doc = SetInstanceOptions(GetImagePrototype(), { ...options, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] },
// [new URL(url), ImageField]);
// doc.SetText(KeyStore.Caption, "my caption...");
@@ -184,44 +191,69 @@ export namespace Documents {
// return doc;
}
export function VideoDocument(url: string, options: DocumentOptions = {}) {
- return assignToDelegate(SetInstanceOptions(GetVideoPrototype(), options, [new URL(url), VideoField]), options);
+ return assignToDelegate(SetInstanceOptions(videoProto, options, [new URL(url), VideoField]), options);
}
export function AudioDocument(url: string, options: DocumentOptions = {}) {
- return assignToDelegate(SetInstanceOptions(GetAudioPrototype(), options, [new URL(url), AudioField]), options);
+ return assignToDelegate(SetInstanceOptions(audioProto, options, [new URL(url), AudioField]), options);
}
export function HistogramDocument(histoOp: HistogramOperation, options: DocumentOptions = {}, id?: string, delegId?: string) {
- return assignToDelegate(SetInstanceOptions(GetHistogramPrototype(), options, [histoOp, HistogramField], id).MakeDelegate(delegId), options);
+ return assignToDelegate(SetInstanceOptions(histoProto, options, [histoOp, HistogramField], id).MakeDelegate(delegId), options);
}
export function TextDocument(options: DocumentOptions = {}) {
- return assignToDelegate(SetInstanceOptions(GetTextPrototype(), options, ["", TextField]).MakeDelegate(), options);
+ return assignToDelegate(SetInstanceOptions(textProto, options, ["", TextField]).MakeDelegate(), options);
}
export function PdfDocument(url: string, options: DocumentOptions = {}) {
- return assignToDelegate(SetInstanceOptions(GetPdfPrototype(), options, [new URL(url), PDFField]).MakeDelegate(), options);
+ 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(GetWebPrototype(), options, [new URL(url), WebField]).MakeDelegate(), options);
+ return assignToDelegate(SetInstanceOptions(webProto, options, [new URL(url), WebField]).MakeDelegate(), options);
}
export function HtmlDocument(html: string, options: DocumentOptions = {}) {
- return assignToDelegate(SetInstanceOptions(GetWebPrototype(), options, [html, HtmlField]).MakeDelegate(), options);
+ return assignToDelegate(SetInstanceOptions(webProto, options, [html, HtmlField]).MakeDelegate(), options);
}
export function KVPDocument(document: Document, options: DocumentOptions = {}, id?: string) {
- return assignToDelegate(SetInstanceOptions(GetKVPPrototype(), options, document, id), options)
+ return assignToDelegate(SetInstanceOptions(kvpProto, options, document, id), options);
}
export function FreeformDocument(documents: Array<Document>, options: DocumentOptions, id?: string, makePrototype: boolean = true) {
if (!makePrototype) {
- return SetInstanceOptions(GetCollectionPrototype(), { ...options, viewType: CollectionViewType.Freeform }, [documents, ListField], id)
+ return SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Freeform }, [documents, ListField], id);
}
- return assignToDelegate(SetInstanceOptions(GetCollectionPrototype(), { ...options, viewType: CollectionViewType.Freeform }, [documents, ListField], id).MakeDelegate(), options)
+ return assignToDelegate(SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Freeform }, [documents, ListField], id).MakeDelegate(), options);
}
export function SchemaDocument(documents: Array<Document>, options: DocumentOptions, id?: string) {
- return assignToDelegate(SetInstanceOptions(GetCollectionPrototype(), { ...options, viewType: CollectionViewType.Schema }, [documents, ListField], id), options)
+ return assignToDelegate(SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Schema }, [documents, ListField], id), options);
}
export function TreeDocument(documents: Array<Document>, options: DocumentOptions, id?: string) {
- return assignToDelegate(SetInstanceOptions(GetCollectionPrototype(), { ...options, viewType: CollectionViewType.Tree }, [documents, ListField], id), options)
+ return assignToDelegate(SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Tree }, [documents, ListField], id), options);
}
export function DockDocument(config: string, options: DocumentOptions, id?: string) {
- return assignToDelegate(SetInstanceOptions(GetCollectionPrototype(), { ...options, viewType: CollectionViewType.Docking }, [config, TextField], id), options)
+ return assignToDelegate(SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Docking }, [config, TextField], id), options);
}
export function CaptionDocument(doc: Document) {
@@ -241,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 (`
@@ -259,7 +293,7 @@ export namespace Documents {
<FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"CaptionKey"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
</div>
</div>
- `)
+ `);
}
function InnerCaption() {
return (`
@@ -271,7 +305,7 @@ export namespace Documents {
<FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"CaptionKey"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
</div>
</div>
- `)
+ `);
}
/*
@@ -294,6 +328,6 @@ export namespace Documents {
<FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"CaptionKey"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
</div>
</div>
- `)
+ `);
}
} \ No newline at end of file
diff --git a/src/client/northstar/core/BaseObject.ts b/src/client/northstar/core/BaseObject.ts
index e9e766e31..ed3818071 100644
--- a/src/client/northstar/core/BaseObject.ts
+++ b/src/client/northstar/core/BaseObject.ts
@@ -1,10 +1,10 @@
-import { IEquatable } from '../utils/IEquatable'
-import { IDisposable } from '../utils/IDisposable'
+import { IEquatable } from '../utils/IEquatable';
+import { IDisposable } from '../utils/IDisposable';
export class BaseObject implements IEquatable, IDisposable {
public Equals(other: Object): boolean {
- return this == other;
+ return this === other;
}
public Dispose(): void {
diff --git a/src/client/northstar/core/attribute/AttributeModel.ts b/src/client/northstar/core/attribute/AttributeModel.ts
index 124a5b45a..c89b1617c 100644
--- a/src/client/northstar/core/attribute/AttributeModel.ts
+++ b/src/client/northstar/core/attribute/AttributeModel.ts
@@ -1,5 +1,5 @@
-import { Attribute, DataType, VisualizationHint } from '../../model/idea/idea'
-import { BaseObject } from '../BaseObject'
+import { Attribute, DataType, VisualizationHint } from '../../model/idea/idea';
+import { BaseObject } from '../BaseObject';
import { observable } from "mobx";
export abstract class AttributeModel extends BaseObject {
@@ -34,7 +34,7 @@ export class ColumnAttributeModel extends AttributeModel {
}
public Equals(other: ColumnAttributeModel): boolean {
- return this.Attribute.rawName == other.Attribute.rawName;
+ return this.Attribute.rawName === other.Attribute.rawName;
}
}
@@ -93,7 +93,7 @@ export class BackendAttributeModel extends AttributeModel {
}
public get DisplayName(): string {
- return this._displayName.ReplaceAll("_", " ");;
+ return this._displayName.ReplaceAll("_", " ");
}
public get CodeName(): string {
@@ -105,7 +105,7 @@ export class BackendAttributeModel extends AttributeModel {
}
public Equals(other: BackendAttributeModel): boolean {
- return this.Id == other.Id;
+ return this.Id === other.Id;
}
} \ No newline at end of file
diff --git a/src/client/northstar/core/attribute/AttributeTransformationModel.ts b/src/client/northstar/core/attribute/AttributeTransformationModel.ts
index cc5aa7154..66485183b 100644
--- a/src/client/northstar/core/attribute/AttributeTransformationModel.ts
+++ b/src/client/northstar/core/attribute/AttributeTransformationModel.ts
@@ -1,4 +1,4 @@
-;
+
import { computed, observable } from "mobx";
import { AggregateFunction } from "../../model/idea/idea";
import { AttributeModel } from "./AttributeModel";
@@ -20,16 +20,21 @@ export class AttributeTransformationModel implements IEquatable {
if (this.AggregateFunction === AggregateFunction.Count) {
return "count";
}
- if (this.AggregateFunction === AggregateFunction.Avg)
+ if (this.AggregateFunction === AggregateFunction.Avg) {
displayName = "avg(" + displayName + ")";
- else if (this.AggregateFunction === AggregateFunction.Max)
+ }
+ else if (this.AggregateFunction === AggregateFunction.Max) {
displayName = "max(" + displayName + ")";
- else if (this.AggregateFunction === AggregateFunction.Min)
+ }
+ else if (this.AggregateFunction === AggregateFunction.Min) {
displayName = "min(" + displayName + ")";
- else if (this.AggregateFunction === AggregateFunction.Sum)
+ }
+ else if (this.AggregateFunction === AggregateFunction.Sum) {
displayName = "sum(" + displayName + ")";
- else if (this.AggregateFunction === AggregateFunction.SumE)
+ }
+ else if (this.AggregateFunction === AggregateFunction.SumE) {
displayName = "sumE(" + displayName + ")";
+ }
return displayName;
}
@@ -41,7 +46,7 @@ export class AttributeTransformationModel implements IEquatable {
}
public Equals(other: AttributeTransformationModel): boolean {
- return this.AggregateFunction == other.AggregateFunction &&
+ return this.AggregateFunction === other.AggregateFunction &&
this.AttributeModel.Equals(other.AttributeModel);
}
} \ No newline at end of file
diff --git a/src/client/northstar/core/attribute/CalculatedAttributeModel.ts b/src/client/northstar/core/attribute/CalculatedAttributeModel.ts
index ab96c794d..a197c1305 100644
--- a/src/client/northstar/core/attribute/CalculatedAttributeModel.ts
+++ b/src/client/northstar/core/attribute/CalculatedAttributeModel.ts
@@ -1,5 +1,5 @@
import { BackendAttributeModel, AttributeModel, CodeAttributeModel } from "./AttributeModel";
-import { DataType, VisualizationHint } from '../../model/idea/idea'
+import { DataType, VisualizationHint } from '../../model/idea/idea';
export class CalculatedAttributeManager {
public static AllCalculatedAttributes: Array<AttributeModel> = new Array<AttributeModel>();
@@ -11,7 +11,7 @@ export class CalculatedAttributeManager {
public static CreateBackendAttributeModel(id: string, dataType: DataType, displayName: string, codeName: string, visualizationHints: VisualizationHint[]): BackendAttributeModel {
var filtered = this.AllCalculatedAttributes.filter(am => {
if (am instanceof BackendAttributeModel &&
- am.Id == id) {
+ am.Id === id) {
return true;
}
return false;
@@ -27,7 +27,7 @@ export class CalculatedAttributeManager {
public static CreateCodeAttributeModel(code: string, codeName: string, visualizationHints: VisualizationHint[]): CodeAttributeModel {
var filtered = this.AllCalculatedAttributes.filter(am => {
if (am instanceof CodeAttributeModel &&
- am.CodeName == codeName) {
+ am.CodeName === codeName) {
return true;
}
return false;
diff --git a/src/client/northstar/core/brusher/IBaseBrushable.ts b/src/client/northstar/core/brusher/IBaseBrushable.ts
index 99a36636f..c46db4d22 100644
--- a/src/client/northstar/core/brusher/IBaseBrushable.ts
+++ b/src/client/northstar/core/brusher/IBaseBrushable.ts
@@ -1,6 +1,6 @@
-import { PIXIPoint } from '../../utils/MathUtil'
+import { PIXIPoint } from '../../utils/MathUtil';
import { IEquatable } from '../../utils/IEquatable';
-import { Document } from '../../../../fields/Document'
+import { Document } from '../../../../fields/Document';
export interface IBaseBrushable<T> extends IEquatable {
BrusherModels: Array<Document>;
diff --git a/src/client/northstar/core/brusher/IBaseBrusher.ts b/src/client/northstar/core/brusher/IBaseBrusher.ts
index d7ae65464..d2de6ed62 100644
--- a/src/client/northstar/core/brusher/IBaseBrusher.ts
+++ b/src/client/northstar/core/brusher/IBaseBrusher.ts
@@ -1,4 +1,4 @@
-import { PIXIPoint } from '../../utils/MathUtil'
+import { PIXIPoint } from '../../utils/MathUtil';
import { IEquatable } from '../../utils/IEquatable';
diff --git a/src/client/northstar/core/filter/FilterModel.ts b/src/client/northstar/core/filter/FilterModel.ts
index aee99d2b6..e2ba3f652 100644
--- a/src/client/northstar/core/filter/FilterModel.ts
+++ b/src/client/northstar/core/filter/FilterModel.ts
@@ -15,7 +15,7 @@ export class FilterModel {
public Equals(other: FilterModel): boolean {
if (!Utils.EqualityHelper(this, other)) return false;
- if (!this.isSame(this.ValueComparisons, (other as FilterModel).ValueComparisons)) return false;
+ if (!this.isSame(this.ValueComparisons, (other).ValueComparisons)) return false;
return true;
}
@@ -46,16 +46,16 @@ export class FilterModel {
let filtered = baseOperation.FilterModels.filter(fm => fm && fm.ValueComparisons.length > 0);
if (!isFirst && filtered.length > 0) {
filterModels.push(...filtered);
- ret = "(" + baseOperation.FilterModels.filter(fm => fm != null).map(fm => fm.ToPythonString()).join(" || ") + ")";
+ ret = "(" + baseOperation.FilterModels.filter(fm => fm !== null).map(fm => fm.ToPythonString()).join(" || ") + ")";
}
if (Utils.isBaseFilterConsumer(baseOperation) && baseOperation.Links) {
let children = new Array<string>();
let linkedGraphNodes = baseOperation.Links;
linkedGraphNodes.map(linkVm => {
let filterDoc = linkVm.Get(KeyStore.LinkedFromDocs);
- if (filterDoc && filterDoc != FieldWaiting && filterDoc instanceof Document) {
+ if (filterDoc && filterDoc !== FieldWaiting && filterDoc instanceof Document) {
let filterHistogram = filterDoc.GetT(KeyStore.Data, HistogramField);
- if (filterHistogram && filterHistogram != FieldWaiting) {
+ if (filterHistogram && filterHistogram !== FieldWaiting) {
if (!visitedFilterProviders.has(filterHistogram.Data)) {
let child = FilterModel.GetFilterModelsRecursive(filterHistogram.Data, visitedFilterProviders, filterModels, false);
if (child !== "") {
diff --git a/src/client/northstar/core/filter/IBaseFilterConsumer.ts b/src/client/northstar/core/filter/IBaseFilterConsumer.ts
index 93f66a154..59d7adf4c 100644
--- a/src/client/northstar/core/filter/IBaseFilterConsumer.ts
+++ b/src/client/northstar/core/filter/IBaseFilterConsumer.ts
@@ -1,5 +1,5 @@
-import { FilterOperand } from '../filter/FilterOperand'
-import { IEquatable } from '../../utils/IEquatable'
+import { FilterOperand } from '../filter/FilterOperand';
+import { IEquatable } from '../../utils/IEquatable';
import { Document } from "../../../../fields/Document";
export interface IBaseFilterConsumer extends IEquatable {
diff --git a/src/client/northstar/core/filter/IBaseFilterProvider.ts b/src/client/northstar/core/filter/IBaseFilterProvider.ts
index d082bfe12..fc3301b11 100644
--- a/src/client/northstar/core/filter/IBaseFilterProvider.ts
+++ b/src/client/northstar/core/filter/IBaseFilterProvider.ts
@@ -1,4 +1,4 @@
-import { FilterModel } from '../filter/FilterModel'
+import { FilterModel } from '../filter/FilterModel';
export interface IBaseFilterProvider {
FilterModels: Array<FilterModel>;
diff --git a/src/client/northstar/core/filter/ValueComparision.ts b/src/client/northstar/core/filter/ValueComparision.ts
index 1e729d06e..80b1242a9 100644
--- a/src/client/northstar/core/filter/ValueComparision.ts
+++ b/src/client/northstar/core/filter/ValueComparision.ts
@@ -1,5 +1,5 @@
-import { Predicate } from '../../model/idea/idea'
-import { Utils } from '../../utils/Utils'
+import { Predicate } from '../../model/idea/idea';
+import { Utils } from '../../utils/Utils';
import { AttributeModel } from '../attribute/AttributeModel';
export class ValueComparison {
@@ -15,15 +15,19 @@ export class ValueComparison {
}
public Equals(other: Object): boolean {
- if (!Utils.EqualityHelper(this, other))
+ if (!Utils.EqualityHelper(this, other)) {
return false;
- if (this.Predicate !== (other as ValueComparison).Predicate)
+ }
+ if (this.Predicate !== (other as ValueComparison).Predicate) {
return false;
+ }
let isComplex = (typeof this.Value === "object");
- if (!isComplex && this.Value != (other as ValueComparison).Value)
+ if (!isComplex && this.Value !== (other as ValueComparison).Value) {
return false;
- if (isComplex && !this.Value.Equals((other as ValueComparison).Value))
+ }
+ if (isComplex && !this.Value.Equals((other as ValueComparison).Value)) {
return false;
+ }
return true;
}
@@ -58,13 +62,13 @@ export class ValueComparison {
var rawName = this.attributeModel.CodeName;
switch (this.Predicate) {
case Predicate.STARTS_WITH:
- ret += rawName + " != null && " + rawName + ".StartsWith(" + val + ") ";
+ ret += rawName + " !== null && " + rawName + ".StartsWith(" + val + ") ";
return ret;
case Predicate.ENDS_WITH:
- ret += rawName + " != null && " + rawName + ".EndsWith(" + val + ") ";
+ ret += rawName + " !== null && " + rawName + ".EndsWith(" + val + ") ";
return ret;
case Predicate.CONTAINS:
- ret += rawName + " != null && " + rawName + ".Contains(" + val + ") ";
+ ret += rawName + " !== null && " + rawName + ".Contains(" + val + ") ";
return ret;
default:
ret += rawName + " " + op + " " + val + " ";
diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts
index 90be70b80..82de51d56 100644
--- a/src/client/northstar/dash-fields/HistogramField.ts
+++ b/src/client/northstar/dash-fields/HistogramField.ts
@@ -16,7 +16,7 @@ export class HistogramField extends BasicField<HistogramOperation> {
omitKeys(obj: any, keys: any) {
var dup: any = {};
for (var key in obj) {
- if (keys.indexOf(key) == -1) {
+ if (keys.indexOf(key) === -1) {
dup[key] = obj[key];
}
}
@@ -27,7 +27,7 @@ export class HistogramField extends BasicField<HistogramOperation> {
}
Copy(): Field {
- return new HistogramField(this.Data);
+ return new HistogramField(this.Data.Copy());
}
ToScriptString(): string {
@@ -35,13 +35,12 @@ export class HistogramField extends BasicField<HistogramOperation> {
}
- ToJson(): { type: Types, data: string, _id: string } {
+ ToJson() {
return {
type: Types.HistogramOp,
-
data: this.toString(),
- _id: this.Id
- }
+ id: this.Id
+ };
}
@action
@@ -54,13 +53,13 @@ export class HistogramField extends BasicField<HistogramOperation> {
let schema = CurrentUserUtils.GetNorthstarSchema(jp.SchemaName);
if (schema) {
CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => {
- if (attr.displayName == jp.X.AttributeModel.Attribute.DisplayName) {
+ if (attr.displayName === jp.X.AttributeModel.Attribute.DisplayName) {
X = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.X.AggregateFunction);
}
- if (attr.displayName == jp.Y.AttributeModel.Attribute.DisplayName) {
+ if (attr.displayName === jp.Y.AttributeModel.Attribute.DisplayName) {
Y = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.Y.AggregateFunction);
}
- if (attr.displayName == jp.V.AttributeModel.Attribute.DisplayName) {
+ if (attr.displayName === jp.V.AttributeModel.Attribute.DisplayName) {
V = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.V.AggregateFunction);
}
});
diff --git a/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts b/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts
index 43e768c62..6291ec1fc 100644
--- a/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts
+++ b/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts
@@ -1,4 +1,4 @@
-import React = require("react")
+import React = require("react");
import { AttributeTransformationModel } from "../../northstar/core/attribute/AttributeTransformationModel";
import { ChartType } from '../../northstar/model/binRanges/VisualBinRange';
import { AggregateFunction, Bin, Brush, DoubleValueAggregateResult, HistogramResult, MarginAggregateParameters, MarginAggregateResult } from "../../northstar/model/idea/idea";
@@ -29,7 +29,7 @@ export class HistogramBinPrimitiveCollection {
private _histoBox: HistogramBox;
private get histoOp() { return this._histoBox.HistoOp; }
private get histoResult() { return this.histoOp.Result as HistogramResult; }
- private get sizeConverter() { return this._histoBox.SizeConverter!; }
+ private get sizeConverter() { return this._histoBox.SizeConverter; }
public BinPrimitives: Array<HistogramBinPrimitive> = new Array<HistogramBinPrimitive>();
public HitGeom: PIXIRectangle = PIXIRectangle.EMPTY;
@@ -48,28 +48,28 @@ export class HistogramBinPrimitiveCollection {
// adjust brush rects (stacking or not)
var allBrushIndex = ModelHelpers.AllBrushIndex(this.histoResult);
- var filteredBinPrims = this.BinPrimitives.filter(b => b.BrushIndex != allBrushIndex && b.DataValue != 0.0);
+ var filteredBinPrims = this.BinPrimitives.filter(b => b.BrushIndex !== allBrushIndex && b.DataValue !== 0.0);
filteredBinPrims.reduce((sum, fbp) => {
- if (histoBox.ChartType == ChartType.VerticalBar) {
- if (this.histoOp.Y.AggregateFunction == AggregateFunction.Count) {
+ if (histoBox.ChartType === ChartType.VerticalBar) {
+ if (this.histoOp.Y.AggregateFunction === AggregateFunction.Count) {
fbp.Rect = new PIXIRectangle(fbp.Rect.x, fbp.Rect.y - sum, fbp.Rect.width, fbp.Rect.height);
fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x, fbp.MarginRect.y - sum, fbp.MarginRect.width, fbp.MarginRect.height);
return sum + fbp.Rect.height;
}
- if (this.histoOp.Y.AggregateFunction == AggregateFunction.Avg) {
+ if (this.histoOp.Y.AggregateFunction === AggregateFunction.Avg) {
var w = fbp.Rect.width / 2.0;
fbp.Rect = new PIXIRectangle(fbp.Rect.x + sum, fbp.Rect.y, fbp.Rect.width / filteredBinPrims.length, fbp.Rect.height);
fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x - w + sum + (fbp.Rect.width / 2.0), fbp.MarginRect.y, fbp.MarginRect.width, fbp.MarginRect.height);
return sum + fbp.Rect.width;
}
}
- else if (histoBox.ChartType == ChartType.HorizontalBar) {
- if (this.histoOp.X.AggregateFunction == AggregateFunction.Count) {
+ else if (histoBox.ChartType === ChartType.HorizontalBar) {
+ if (this.histoOp.X.AggregateFunction === AggregateFunction.Count) {
fbp.Rect = new PIXIRectangle(fbp.Rect.x + sum, fbp.Rect.y, fbp.Rect.width, fbp.Rect.height);
fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x + sum, fbp.MarginRect.y, fbp.MarginRect.width, fbp.MarginRect.height);
return sum + fbp.Rect.width;
}
- if (this.histoOp.X.AggregateFunction == AggregateFunction.Avg) {
+ if (this.histoOp.X.AggregateFunction === AggregateFunction.Avg) {
var h = fbp.Rect.height / 2.0;
fbp.Rect = new PIXIRectangle(fbp.Rect.x, fbp.Rect.y + sum, fbp.Rect.width, fbp.Rect.height / filteredBinPrims.length);
fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x, fbp.MarginRect.y - h + sum + (fbp.Rect.height / 2.0), fbp.MarginRect.width, fbp.MarginRect.height);
@@ -79,19 +79,19 @@ export class HistogramBinPrimitiveCollection {
return 0;
}, 0);
this.BinPrimitives = this.BinPrimitives.reverse();
- var f = this.BinPrimitives.filter(b => b.BrushIndex == allBrushIndex);
+ var f = this.BinPrimitives.filter(b => b.BrushIndex === allBrushIndex);
this.HitGeom = f.length > 0 ? f[0].Rect : PIXIRectangle.EMPTY;
}
private setupBrushing(bin: Bin, normalization: number) {
var overlapBrushIndex = ModelHelpers.OverlapBrushIndex(this.histoResult);
var orderedBrushes = [this.histoResult.brushes![0], this.histoResult.brushes![overlapBrushIndex]];
- this.histoResult.brushes!.map(brush => brush.brushIndex != 0 && brush.brushIndex != overlapBrushIndex && orderedBrushes.push(brush));
+ this.histoResult.brushes!.map(brush => brush.brushIndex !== 0 && brush.brushIndex !== overlapBrushIndex && orderedBrushes.push(brush));
return {
orderedBrushes,
maxAxis: orderedBrushes.reduce((prev, Brush) => {
let aggResult = this.getBinValue(normalization, bin, Brush.brushIndex!);
- return aggResult != undefined && aggResult > prev ? aggResult : prev;
+ return aggResult !== undefined && aggResult > prev ? aggResult : prev;
}, Number.MIN_VALUE)
};
}
@@ -99,20 +99,21 @@ export class HistogramBinPrimitiveCollection {
private createHeatmapBinPrimitives(bin: Bin, brush: Brush, brushFactorSum: number): number {
let unNormalizedValue = this.getBinValue(2, bin, brush.brushIndex!);
- if (unNormalizedValue == undefined)
+ if (unNormalizedValue === undefined) {
return brushFactorSum;
+ }
var normalizedValue = (unNormalizedValue - this._histoBox.ValueRange[0]) / (Math.abs((this._histoBox.ValueRange[1] - this._histoBox.ValueRange[0])) < HistogramBinPrimitiveCollection.TOLERANCE ?
unNormalizedValue : this._histoBox.ValueRange[1] - this._histoBox.ValueRange[0]);
- let allUnNormalizedValue = this.getBinValue(2, bin, ModelHelpers.AllBrushIndex(this.histoResult))
+ let allUnNormalizedValue = this.getBinValue(2, bin, ModelHelpers.AllBrushIndex(this.histoResult));
// bcz: are these calls needed?
let [xFrom, xTo] = this.sizeConverter.DataToScreenXAxisRange(this._histoBox.VisualBinRanges, 0, bin);
let [yFrom, yTo] = this.sizeConverter.DataToScreenYAxisRange(this._histoBox.VisualBinRanges, 1, bin);
var returnBrushFactorSum = brushFactorSum;
- if (allUnNormalizedValue != undefined) {
+ if (allUnNormalizedValue !== undefined) {
var brushFactor = (unNormalizedValue / allUnNormalizedValue);
returnBrushFactorSum += brushFactor;
returnBrushFactorSum = Math.min(returnBrushFactorSum, 1.0);
@@ -141,19 +142,20 @@ export class HistogramBinPrimitiveCollection {
private createSinglePointChartBinPrimitives(bin: Bin, brush: Brush): number {
let unNormalizedValue = this.getBinValue(2, bin, brush.brushIndex!);
- if (unNormalizedValue != undefined) {
+ if (unNormalizedValue !== undefined) {
let [xFrom, xTo] = this.sizeConverter.DataToScreenPointRange(0, bin, ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, this.histoOp.X, this.histoResult, brush.brushIndex!));
let [yFrom, yTo] = this.sizeConverter.DataToScreenPointRange(1, bin, ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, this.histoOp.Y, this.histoResult, brush.brushIndex!));
- if (xFrom != undefined && yFrom != undefined && xTo != undefined && yTo != undefined)
+ if (xFrom !== undefined && yFrom !== undefined && xTo !== undefined && yTo !== undefined) {
this.createBinPrimitive(-1, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, this.baseColorFromBrush(brush), 1, unNormalizedValue);
+ }
}
return 0;
}
private createVerticalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number): number {
let dataValue = this.getBinValue(1, bin, brush.brushIndex!);
- if (dataValue != undefined) {
+ if (dataValue !== undefined) {
let [yFrom, yValue, yTo] = this.sizeConverter.DataToScreenNormalizedRange(dataValue, normalization, 1, binBrushMaxAxis);
let [xFrom, xTo] = this.sizeConverter.DataToScreenXAxisRange(this._histoBox.VisualBinRanges, 0, bin);
@@ -163,14 +165,14 @@ export class HistogramBinPrimitiveCollection {
this.sizeConverter.DataToScreenY(yValue - yMarginAbsolute) - this.sizeConverter.DataToScreenY(yValue + yMarginAbsolute));
this.createBinPrimitive(1, brush, marginRect, 0, xFrom, xTo, yFrom, yTo,
- this.baseColorFromBrush(brush), normalization != 0 ? 1 : 0.6 * binBrushMaxAxis / this.sizeConverter.DataRanges[1] + 0.4, dataValue);
+ this.baseColorFromBrush(brush), normalization !== 0 ? 1 : 0.6 * binBrushMaxAxis / this.sizeConverter.DataRanges[1] + 0.4, dataValue);
}
return 0;
}
private createHorizontalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number): number {
let dataValue = this.getBinValue(0, bin, brush.brushIndex!);
- if (dataValue != undefined) {
+ if (dataValue !== undefined) {
let [xFrom, xValue, xTo] = this.sizeConverter.DataToScreenNormalizedRange(dataValue, normalization, 0, binBrushMaxAxis);
let [yFrom, yTo] = this.sizeConverter.DataToScreenYAxisRange(this._histoBox.VisualBinRanges, 1, bin);
@@ -181,15 +183,15 @@ export class HistogramBinPrimitiveCollection {
2.0);
this.createBinPrimitive(0, brush, marginRect, 0, xFrom, xTo, yFrom, yTo,
- this.baseColorFromBrush(brush), normalization != 1 ? 1 : 0.6 * binBrushMaxAxis / this.sizeConverter.DataRanges[0] + 0.4, dataValue);
+ this.baseColorFromBrush(brush), normalization !== 1 ? 1 : 0.6 * binBrushMaxAxis / this.sizeConverter.DataRanges[0] + 0.4, dataValue);
}
return 0;
}
public getBinValue(axis: number, bin: Bin, brushIndex: number) {
- var aggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, axis == 0 ? this.histoOp.X : axis == 1 ? this.histoOp.Y : this.histoOp.V, this.histoResult, brushIndex);
+ var aggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, axis === 0 ? this.histoOp.X : axis === 1 ? this.histoOp.Y : this.histoOp.V, this.histoResult, brushIndex);
let dataValue = ModelHelpers.GetAggregateResult(bin, aggregateKey) as DoubleValueAggregateResult;
- return dataValue != null && dataValue.hasResult ? dataValue.result : undefined;
+ return dataValue !== null && dataValue.hasResult ? dataValue.result : undefined;
}
private getMargin(bin: Bin, brush: Brush, axis: AttributeTransformationModel) {
@@ -218,13 +220,13 @@ export class HistogramBinPrimitiveCollection {
private baseColorFromBrush(brush: Brush): number {
let bc = StyleConstants.BRUSH_COLORS;
- if (brush.brushIndex == ModelHelpers.RestBrushIndex(this.histoResult)) {
+ if (brush.brushIndex === ModelHelpers.RestBrushIndex(this.histoResult)) {
return StyleConstants.HIGHLIGHT_COLOR;
}
- else if (brush.brushIndex == ModelHelpers.OverlapBrushIndex(this.histoResult)) {
+ else if (brush.brushIndex === ModelHelpers.OverlapBrushIndex(this.histoResult)) {
return StyleConstants.OVERLAP_COLOR;
}
- else if (brush.brushIndex == ModelHelpers.AllBrushIndex(this.histoResult)) {
+ else if (brush.brushIndex === ModelHelpers.AllBrushIndex(this.histoResult)) {
return 0x00ff00;
}
else if (bc.length > 0) {
diff --git a/src/client/northstar/dash-nodes/HistogramBox.scss b/src/client/northstar/dash-nodes/HistogramBox.scss
index b11840a65..e899cf15e 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.scss
+++ b/src/client/northstar/dash-nodes/HistogramBox.scss
@@ -1,6 +1,8 @@
.histogrambox-container {
padding: 0vw;
position: absolute;
+ top: 0;
+ left:0;
text-align: center;
width: 100%;
height: 100%;
@@ -8,6 +10,7 @@
}
.histogrambox-xaxislabel {
position:absolute;
+ left:0;
width:100%;
text-align: center;
bottom:0;
@@ -19,14 +22,17 @@
position:absolute;
height:100%;
width: 25px;
+ left:0;
bottom:0;
background: lightgray;
}
.histogrambox-yaxislabel-text {
position:absolute;
- transform-origin: left;
+ left:0;
+ width: 1000px;
+ transform-origin: 10px 10px;
transform: rotate(-90deg);
- text-align: center;
+ text-align: left;
font-size: 14;
font-weight: bold;
bottom: calc(50% - 25px);
diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index 49ebe5ebc..3e94fed81 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -1,4 +1,4 @@
-import React = require("react")
+import React = require("react");
import { action, computed, observable, reaction, runInAction, trace } from "mobx";
import { observer } from "mobx-react";
import Measure from "react-measure";
@@ -8,7 +8,7 @@ import { KeyStore } from "../../../fields/KeyStore";
import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
import { ChartType, VisualBinRange } from '../../northstar/model/binRanges/VisualBinRange';
import { VisualBinRangeHelper } from "../../northstar/model/binRanges/VisualBinRangeHelper";
-import { AggregateBinRange, AggregateFunction, BinRange, Catalog, DoubleValueAggregateResult, HistogramResult, Result } from "../../northstar/model/idea/idea";
+import { AggregateBinRange, AggregateFunction, BinRange, Catalog, DoubleValueAggregateResult, HistogramResult } from "../../northstar/model/idea/idea";
import { ModelHelpers } from "../../northstar/model/ModelHelpers";
import { HistogramOperation } from "../../northstar/operations/HistogramOperation";
import { SizeConverter } from "../../northstar/utils/SizeConverter";
@@ -25,7 +25,7 @@ import { StyleConstants } from "../utils/StyleContants";
@observer
export class HistogramBox extends React.Component<FieldViewProps> {
- public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(HistogramBox, fieldStr) }
+ public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(HistogramBox, fieldStr); }
private _dropXRef = React.createRef<HTMLDivElement>();
private _dropYRef = React.createRef<HTMLDivElement>();
private _dropXDisposer?: DragManager.DragDropDisposer;
@@ -47,15 +47,11 @@ 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) {
let h = de.data.draggedDocuments[0].GetT(KeyStore.Data, HistogramField);
- if (h && h != FieldWaiting) {
+ if (h && h !== FieldWaiting) {
this.HistoOp.X = h.Data.X;
}
e.stopPropagation();
@@ -66,7 +62,7 @@ export class HistogramBox extends React.Component<FieldViewProps> {
dropY = (e: Event, de: DragManager.DropEvent) => {
if (de.data instanceof DragManager.DocumentDragData) {
let h = de.data.draggedDocuments[0].GetT(KeyStore.Data, HistogramField);
- if (h && h != FieldWaiting) {
+ if (h && h !== FieldWaiting) {
this.HistoOp.Y = h.Data.X;
}
e.stopPropagation();
@@ -76,11 +72,11 @@ export class HistogramBox extends React.Component<FieldViewProps> {
@action
xLabelPointerDown = (e: React.PointerEvent) => {
- this.HistoOp.X = new AttributeTransformationModel(this.HistoOp.X.AttributeModel, this.HistoOp.X.AggregateFunction == AggregateFunction.None ? AggregateFunction.Count : AggregateFunction.None);
+ this.HistoOp.X = new AttributeTransformationModel(this.HistoOp.X.AttributeModel, this.HistoOp.X.AggregateFunction === AggregateFunction.None ? AggregateFunction.Count : AggregateFunction.None);
}
@action
yLabelPointerDown = (e: React.PointerEvent) => {
- this.HistoOp.Y = new AttributeTransformationModel(this.HistoOp.Y.AttributeModel, this.HistoOp.Y.AggregateFunction == AggregateFunction.None ? AggregateFunction.Count : AggregateFunction.None);
+ this.HistoOp.Y = new AttributeTransformationModel(this.HistoOp.Y.AttributeModel, this.HistoOp.Y.AggregateFunction === AggregateFunction.None ? AggregateFunction.Count : AggregateFunction.None);
}
componentDidMount() {
@@ -92,15 +88,15 @@ export class HistogramBox extends React.Component<FieldViewProps> {
}
reaction(() => CurrentUserUtils.NorthstarDBCatalog, (catalog?: Catalog) => this.activateHistogramOperation(catalog), { fireImmediately: true });
reaction(() => [this.VisualBinRanges && this.VisualBinRanges.slice()], () => this.SizeConverter.SetVisualBinRanges(this.VisualBinRanges));
- reaction(() => [this.PanelHeight, this.PanelWidth], () => this.SizeConverter.SetIsSmall(this.PanelWidth < 40 && this.PanelHeight < 40))
+ reaction(() => [this.PanelHeight, this.PanelWidth], () => this.SizeConverter.SetIsSmall(this.PanelWidth < 40 && this.PanelHeight < 40));
reaction(() => this.HistogramResult ? this.HistogramResult.binRanges : undefined,
(binRanges: BinRange[] | undefined) => {
if (binRanges) {
this.VisualBinRanges.splice(0, this.VisualBinRanges.length, ...binRanges.map((br, ind) =>
- VisualBinRangeHelper.GetVisualBinRange(this.HistoOp.Schema!.distinctAttributeParameters, br, this.HistogramResult!, ind ? this.HistoOp.Y : this.HistoOp.X, this.ChartType)));
+ VisualBinRangeHelper.GetVisualBinRange(this.HistoOp.Schema!.distinctAttributeParameters, br, this.HistogramResult, ind ? this.HistoOp.Y : this.HistoOp.X, this.ChartType)));
- let valueAggregateKey = ModelHelpers.CreateAggregateKey(this.HistoOp.Schema!.distinctAttributeParameters, this.HistoOp.V, this.HistogramResult!, ModelHelpers.AllBrushIndex(this.HistogramResult!));
- this.ValueRange = Object.values(this.HistogramResult!.bins!).reduce((prev, cur) => {
+ let valueAggregateKey = ModelHelpers.CreateAggregateKey(this.HistoOp.Schema!.distinctAttributeParameters, this.HistoOp.V, this.HistogramResult, ModelHelpers.AllBrushIndex(this.HistogramResult));
+ this.ValueRange = Object.values(this.HistogramResult.bins!).reduce((prev, cur) => {
let value = ModelHelpers.GetAggregateResult(cur, valueAggregateKey) as DoubleValueAggregateResult;
return value && value.hasResult ? [Math.min(prev[0], value.result!), Math.max(prev[1], value.result!)] : prev;
}, [Number.MAX_VALUE, Number.MIN_VALUE]);
@@ -109,26 +105,28 @@ export class HistogramBox extends React.Component<FieldViewProps> {
}
componentWillUnmount() {
- if (this._dropXDisposer)
+ if (this._dropXDisposer) {
this._dropXDisposer();
- if (this._dropYDisposer)
+ }
+ if (this._dropYDisposer) {
this._dropYDisposer();
+ }
}
activateHistogramOperation(catalog?: Catalog) {
if (catalog) {
- this.props.doc.GetTAsync(this.props.fieldKey, HistogramField).then((histoOp: Opt<HistogramField>) => runInAction(() => {
+ this.props.Document.GetTAsync(this.props.fieldKey, HistogramField).then((histoOp: Opt<HistogramField>) => runInAction(() => {
this.HistoOp = histoOp ? histoOp.Data : HistogramOperation.Empty;
- if (this.HistoOp != HistogramOperation.Empty) {
- reaction(() => this.props.doc.GetList(KeyStore.LinkedFromDocs, []), (docs: Document[]) => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true });
- reaction(() => this.props.doc.GetList(KeyStore.BrushingDocs, []).length,
+ if (this.HistoOp !== HistogramOperation.Empty) {
+ reaction(() => this.props.Document.GetList(KeyStore.LinkedFromDocs, []), (docs: Document[]) => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true });
+ reaction(() => this.props.Document.GetList(KeyStore.BrushingDocs, []).length,
() => {
- let brushingDocs = this.props.doc.GetList(KeyStore.BrushingDocs, [] as Document[]);
- let proto = this.props.doc.GetPrototype() as Document;
+ let brushingDocs = this.props.Document.GetList(KeyStore.BrushingDocs, [] as Document[]);
+ let proto = this.props.Document.GetPrototype() as Document;
this.HistoOp.BrushLinks.splice(0, this.HistoOp.BrushLinks.length, ...brushingDocs.map((brush, i) => {
brush.SetNumber(KeyStore.BackgroundColor, StyleConstants.BRUSH_COLORS[i % StyleConstants.BRUSH_COLORS.length]);
let brushed = brush.GetList(KeyStore.BrushingDocs, [] as Document[]);
- return { l: brush, b: brushed[0].Id == proto.Id ? brushed[1] : brushed[0] }
+ return { l: brush, b: brushed[0].Id === proto.Id ? brushed[1] : brushed[0] };
}));
}, { fireImmediately: true });
reaction(() => this.createOperationParamsCache, () => this.HistoOp.Update(), { fireImmediately: true });
@@ -139,16 +137,16 @@ export class HistogramBox extends React.Component<FieldViewProps> {
render() {
let labelY = this.HistoOp && this.HistoOp.Y ? this.HistoOp.Y.PresentedName : "<...>";
let labelX = this.HistoOp && this.HistoOp.X ? this.HistoOp.X.PresentedName : "<...>";
- var h = this.props.isTopMost ? this.PanelHeight : this.props.doc.GetNumber(KeyStore.Height, 0);
- var w = this.props.isTopMost ? this.PanelWidth : this.props.doc.GetNumber(KeyStore.Width, 0);
+ var h = this.props.isTopMost ? this.PanelHeight : this.props.Document.GetNumber(KeyStore.Height, 0);
+ var w = this.props.isTopMost ? this.PanelWidth : this.props.Document.GetNumber(KeyStore.Width, 0);
let loff = this.SizeConverter.LeftOffset;
let toff = this.SizeConverter.TopOffset;
let roff = this.SizeConverter.RightOffset;
let boff = this.SizeConverter.BottomOffset;
return (
- <Measure onResize={(r: any) => runInAction(() => { this.PanelWidth = r.entry.width; this.PanelHeight = r.entry.height })}>
+ <Measure onResize={(r: any) => runInAction(() => { this.PanelWidth = r.entry.width; this.PanelHeight = r.entry.height; })}>
{({ measureRef }) =>
- <div className="histogrambox-container" ref={measureRef} style={{ transform: `translate(${-w / 2}px, ${-h / 2}px)` }}>
+ <div className="histogrambox-container" ref={measureRef} style={{ transform: `translate(-50%, -50%)` }}>
<div className="histogrambox-yaxislabel" onPointerDown={this.yLabelPointerDown} ref={this._dropYRef} >
<span className="histogrambox-yaxislabel-text">
{labelY}
@@ -168,7 +166,7 @@ export class HistogramBox extends React.Component<FieldViewProps> {
</div>
}
</Measure>
- )
+ );
}
}
diff --git a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss
index ce9edd65e..26203612a 100644
--- a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss
+++ b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss
@@ -4,10 +4,11 @@
}
.histogramboxprimitives-border {
border: 3px;
- border-style: solid;
- border-color: white;
pointer-events: none;
position: absolute;
+ fill:"transparent";
+ stroke: white;
+ stroke-width: 1px;
}
.histogramboxprimitives-bar {
position: absolute;
@@ -23,8 +24,19 @@
width: 100%;
height: 100%;
}
+.histogramboxprimitives-svgContainer {
+ position: absolute;
+ top:0;
+ left:0;
+ width:100%;
+ height: 100%;
+}
.histogramboxprimitives-line {
position: absolute;
background: darkGray;
+ stroke: darkGray;
+ stroke-width: 1px;
+ width:100%;
+ height:100%;
opacity: 0.4;
} \ No newline at end of file
diff --git a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
index e9adb3ce5..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 } from "mobx";
+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";
@@ -11,6 +11,9 @@ import { StyleConstants } from "../../northstar/utils/StyleContants";
import { HistogramBinPrimitiveCollection, HistogramBinPrimitive } from "./HistogramBinPrimitiveCollection";
import { HistogramBox } from "./HistogramBox";
import "./HistogramBoxPrimitives.scss";
+import { JSXElement } from "babel-types";
+import { Utils } from "../utils/Utils";
+import { all } from "bluebird";
export interface HistogramPrimitivesProps {
HistoBox: HistogramBox;
@@ -23,16 +26,16 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP
@computed get xaxislines() { return this.renderGridLinesAndLabels(0); }
@computed get yaxislines() { return this.renderGridLinesAndLabels(1); }
@computed get selectedPrimitives() { return this._selectedPrims.map(bp => this.drawRect(bp.Rect, bp.BarAxis, undefined, "border")); }
- @computed get binPrimitives() {
+ @computed get barPrimitives() {
let histoResult = this.props.HistoBox.HistogramResult;
- if (!histoResult || !histoResult.bins || !this.props.HistoBox.VisualBinRanges.length)
+ if (!histoResult || !histoResult.bins || !this.props.HistoBox.VisualBinRanges.length) {
return (null);
- trace();
+ }
let allBrushIndex = ModelHelpers.AllBrushIndex(histoResult);
- return Object.keys(histoResult.bins).reduce((prims, key) => {
- let drawPrims = new HistogramBinPrimitiveCollection(histoResult!.bins![key], this.props.HistoBox);
+ return Object.keys(histoResult.bins).reduce((prims: JSX.Element[], key: string) => {
+ let drawPrims = new HistogramBinPrimitiveCollection(histoResult.bins![key], this.props.HistoBox);
let toggle = this.getSelectionToggle(drawPrims.BinPrimitives, allBrushIndex,
- ModelHelpers.GetBinFilterModel(histoResult!.bins![key], allBrushIndex, histoResult!, this.histoOp.X, this.histoOp.Y));
+ ModelHelpers.GetBinFilterModel(histoResult.bins![key], allBrushIndex, histoResult, this.histoOp.X, this.histoOp.Y));
drawPrims.BinPrimitives.filter(bp => bp.DataValue && bp.BrushIndex !== allBrushIndex).map(bp =>
prims.push(...[{ r: bp.Rect, c: bp.Color }, { r: bp.MarginRect, c: StyleConstants.MARGIN_BARS_COLOR }].map(pair => this.drawRect(pair.r, bp.BarAxis, pair.c, "bar", toggle))));
return prims;
@@ -44,39 +47,41 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP
}
private getSelectionToggle(binPrimitives: HistogramBinPrimitive[], allBrushIndex: number, filterModel: FilterModel) {
- let allBrushPrim = ArrayUtil.FirstOrDefault(binPrimitives, bp => bp.BrushIndex == allBrushIndex);
- return !allBrushPrim ? () => { } : () => runInAction(() => {
+ let rawAllBrushPrim = ArrayUtil.FirstOrDefault(binPrimitives, bp => bp.BrushIndex === allBrushIndex);
+ if (!rawAllBrushPrim) {
+ return emptyFunction;
+ }
+ let allBrushPrim = rawAllBrushPrim;
+ return () => runInAction(() => {
if (ArrayUtil.Contains(this.histoOp.FilterModels, filterModel)) {
- this._selectedPrims.splice(this._selectedPrims.indexOf(allBrushPrim!), 1);
+ this._selectedPrims.splice(this._selectedPrims.indexOf(allBrushPrim), 1);
this.histoOp.RemoveFilterModels([filterModel]);
}
else {
- this._selectedPrims.push(allBrushPrim!);
+ this._selectedPrims.push(allBrushPrim);
this.histoOp.AddFilterModels([filterModel]);
}
- })
+ });
}
private renderGridLinesAndLabels(axis: number) {
- if (!this.props.HistoBox.SizeConverter.Initialized)
+ trace();
+ if (!this.props.HistoBox.SizeConverter.Initialized) {
return (null);
+ }
let labels = this.props.HistoBox.VisualBinRanges[axis].GetLabels();
- return labels.reduce((prims, binLabel, i) => {
- let r = this.props.HistoBox.SizeConverter.DataToScreenRange(binLabel.minValue!, binLabel.maxValue!, axis);
- prims.push(this.drawLine(r.xFrom, r.yFrom, axis == 0 ? 0 : r.xTo - r.xFrom, axis == 0 ? r.yTo - r.yFrom : 0));
- if (i == labels.length - 1)
- prims.push(this.drawLine(axis == 0 ? r.xTo : r.xFrom, axis == 0 ? r.yFrom : r.yTo, axis == 0 ? 0 : r.xTo - r.xFrom, axis == 0 ? r.yTo - r.yFrom : 0));
- return prims;
- }, [] as JSX.Element[]);
+ return <svg className="histogramboxprimitives-svgContainer">
+ {labels.reduce((prims, binLabel, i) => {
+ let r = this.props.HistoBox.SizeConverter.DataToScreenRange(binLabel.minValue!, binLabel.maxValue!, axis);
+ prims.push(this.drawLine(r.xFrom, r.yFrom, axis === 0 ? 0 : r.xTo - r.xFrom, axis === 0 ? r.yTo - r.yFrom : 0));
+ if (i === labels.length - 1) {
+ prims.push(this.drawLine(axis === 0 ? r.xTo : r.xFrom, axis === 0 ? r.yFrom : r.yTo, axis === 0 ? 0 : r.xTo - r.xFrom, axis === 0 ? r.yTo - r.yFrom : 0));
+ }
+ return prims;
+ }, [] as JSX.Element[])}
+ </svg>;
}
- drawEntity(xFrom: number, yFrom: number, entity: JSX.Element) {
- let transXpercent = xFrom / this.renderDimension * 100;
- let transYpercent = yFrom / this.renderDimension * 100;
- return (<div key={DashUtils.GenerateGuid()} className={`histogramboxprimitives-placer`} style={{ transform: `translate(${transXpercent}%, ${transYpercent}%)` }}>
- {entity}
- </div>);
- }
drawLine(xFrom: number, yFrom: number, width: number, height: number) {
if (height < 0) {
yFrom += height;
@@ -86,12 +91,13 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP
xFrom += width;
width = -width;
}
- let trans2Xpercent = width == 0 ? `1px` : `${(xFrom + width) / this.renderDimension * 100}%`;
- let trans2Ypercent = height == 0 ? `1px` : `${(yFrom + height) / this.renderDimension * 100}%`;
- let line = (<div className="histogramboxprimitives-line" style={{ width: trans2Xpercent, height: trans2Ypercent, }} />);
- return this.drawEntity(xFrom, yFrom, line);
+ let trans2Xpercent = `${(xFrom + width) / this.renderDimension * 100}%`;
+ let trans2Ypercent = `${(yFrom + height) / this.renderDimension * 100}%`;
+ let trans1Xpercent = `${xFrom / this.renderDimension * 100}%`;
+ 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;
@@ -100,25 +106,22 @@ export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesP
r.x += r.width;
r.width = -r.width;
}
- let widthPercent = r.width / this.renderDimension * 100;
- let heightPercent = r.height / this.renderDimension * 100;
- let rect = (<div className={`histogramboxprimitives-${classExt}`} onPointerDown={(e: React.PointerEvent) => { if (e.button == 0) tapHandler() }}
- style={{
- borderBottomStyle: barAxis == 1 ? "none" : "solid",
- borderLeftStyle: barAxis == 0 ? "none" : "solid",
- width: `${widthPercent}%`,
- height: `${heightPercent}%`,
- background: color ? `${LABColor.RGBtoHexString(color)}` : ""
- }}
- />);
- return this.drawEntity(r.x, r.y, rect);
+ let transXpercent = `${r.x / this.renderDimension * 100}%`;
+ let transYpercent = `${r.y / this.renderDimension * 100}%`;
+ let widthXpercent = `${r.width / this.renderDimension * 100}%`;
+ let heightYpercent = `${r.height / this.renderDimension * 100}%`;
+ return (<rect className={`histogramboxprimitives-${classExt}`} key={DashUtils.GenerateGuid()} onPointerDown={(e: React.PointerEvent) => { if (e.button === 0) tapHandler(); }}
+ x={transXpercent} width={`${widthXpercent}`} y={transYpercent} height={`${heightYpercent}`} fill={color ? `${LABColor.RGBtoHexString(color)}` : "transparent"} />);
}
render() {
+ trace();
return <div className="histogramboxprimitives-container">
{this.xaxislines}
{this.yaxislines}
- {this.binPrimitives}
- {this.selectedPrimitives}
- </div>
+ <svg className="histogramboxprimitives-svgContainer">
+ {this.barPrimitives}
+ {this.selectedPrimitives}
+ </svg>
+ </div>;
}
} \ No newline at end of file
diff --git a/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
index 93b237deb..5785fe838 100644
--- a/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
+++ b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
@@ -1,4 +1,4 @@
-import React = require("react")
+import React = require("react");
import { action, computed, reaction } from "mobx";
import { observer } from "mobx-react";
import { Utils as DashUtils } from '../../../Utils';
@@ -13,7 +13,7 @@ import { HistogramPrimitivesProps } from "./HistogramBoxPrimitives";
export class HistogramLabelPrimitives extends React.Component<HistogramPrimitivesProps> {
componentDidMount() {
reaction(() => [this.props.HistoBox.PanelWidth, this.props.HistoBox.SizeConverter.LeftOffset, this.props.HistoBox.VisualBinRanges.length],
- (fields) => HistogramLabelPrimitives.computeLabelAngle(fields[0] as number, fields[1] as number, this.props.HistoBox), { fireImmediately: true });
+ (fields) => HistogramLabelPrimitives.computeLabelAngle(fields[0], fields[1], this.props.HistoBox), { fireImmediately: true });
}
@action
@@ -32,9 +32,10 @@ export class HistogramLabelPrimitives extends React.Component<HistogramPrimitive
private renderGridLinesAndLabels(axis: number) {
let sc = this.props.HistoBox.SizeConverter;
let vb = this.props.HistoBox.VisualBinRanges;
- if (!vb.length || !sc.Initialized)
+ if (!vb.length || !sc.Initialized) {
return (null);
- let dim = (axis == 0 ? this.props.HistoBox.PanelWidth : this.props.HistoBox.PanelHeight) / ((axis == 0 && vb[axis] instanceof NominalVisualBinRange) ?
+ }
+ let dim = (axis === 0 ? this.props.HistoBox.PanelWidth : this.props.HistoBox.PanelHeight) / ((axis === 0 && vb[axis] instanceof NominalVisualBinRange) ?
(12 + 5) : // (<number>FontStyles.AxisLabel.fontSize + 5)));
sc.MaxLabelSizes[axis].coords[axis] + 5);
@@ -47,21 +48,21 @@ export class HistogramLabelPrimitives extends React.Component<HistogramPrimitive
let xStart = (axis === 0 ? r.xFrom + (r.xTo - r.xFrom) / 2.0 : r.xFrom - 10 - textWidth);
let yStart = (axis === 1 ? r.yFrom - textHeight / 2 : r.yFrom);
- if (axis == 0 && vb[axis] instanceof NominalVisualBinRange) {
+ if (axis === 0 && vb[axis] instanceof NominalVisualBinRange) {
let space = (r.xTo - r.xFrom) / sc.RenderDimension * this.props.HistoBox.PanelWidth;
xStart += Math.max(textWidth / 2, (1 - textWidth / space) * textWidth / 2) - textHeight / 2;
}
- let xPercent = axis == 1 ? `${xStart}px` : `${xStart / sc.RenderDimension * 100}%`
- let yPercent = axis == 0 ? `${this.props.HistoBox.PanelHeight - sc.BottomOffset - textHeight}px` : `${yStart / sc.RenderDimension * 100}%`
+ let xPercent = axis === 1 ? `${xStart}px` : `${xStart / sc.RenderDimension * 100}%`;
+ let yPercent = axis === 0 ? `${this.props.HistoBox.PanelHeight - sc.BottomOffset - textHeight}px` : `${yStart / sc.RenderDimension * 100}%`;
prims.push(
<div className="histogramLabelPrimitives-placer" key={DashUtils.GenerateGuid()} style={{ transform: `translate(${xPercent}, ${yPercent})` }}>
- <div className="histogramLabelPrimitives-gridlabel" style={{ transform: `rotate(${axis == 0 ? sc.LabelAngle : 0}rad)` }}>
+ <div className="histogramLabelPrimitives-gridlabel" style={{ transform: `rotate(${axis === 0 ? sc.LabelAngle : 0}rad)` }}>
{label}
</div>
</div>
- )
+ );
}
return prims;
}, [] as JSX.Element[]);
@@ -73,7 +74,7 @@ export class HistogramLabelPrimitives extends React.Component<HistogramPrimitive
return <div className="histogramLabelPrimitives-container">
{xaxislines}
{yaxislines}
- </div>
+ </div>;
}
} \ No newline at end of file
diff --git a/src/client/northstar/manager/Gateway.ts b/src/client/northstar/manager/Gateway.ts
index 5ae5e4f47..d26f2724f 100644
--- a/src/client/northstar/manager/Gateway.ts
+++ b/src/client/northstar/manager/Gateway.ts
@@ -1,4 +1,4 @@
-import { Catalog, OperationReference, Result, CompileResults } from "../model/idea/idea"
+import { Catalog, OperationReference, Result, CompileResults } from "../model/idea/idea";
import { computed, observable, action } from "mobx";
export class Gateway {
@@ -23,9 +23,20 @@ export class Gateway {
}
}
+ public async GetSchema(pathname: string, schemaname: string): Promise<Catalog> {
+ try {
+ const json = await this.MakeGetRequest("schema", undefined, { path: pathname, schema: schemaname });
+ const cat = Catalog.fromJS(json);
+ return cat;
+ }
+ catch (error) {
+ throw new Error("can not reach northstar's backend");
+ }
+ }
+
public async ClearCatalog(): Promise<void> {
try {
- const json = await this.MakePostJsonRequest("Datamart/ClearAllAugmentations", {});
+ await this.MakePostJsonRequest("Datamart/ClearAllAugmentations", {});
}
catch (error) {
throw new Error("can not reach northstar's backend");
@@ -49,7 +60,7 @@ export class Gateway {
public async Compile(data: any): Promise<CompileResults | undefined> {
const json = await this.MakePostJsonRequest("compile", data);
- if (json != null) {
+ if (json !== null) {
const cr = CompileResults.fromJS(json);
return cr;
}
@@ -108,7 +119,7 @@ export class Gateway {
public async StartOperation(data: any): Promise<OperationReference | undefined> {
const json = await this.MakePostJsonRequest("operation", data);
- if (json != null) {
+ if (json !== null) {
const or = OperationReference.fromJS(json);
return or;
}
@@ -116,7 +127,7 @@ export class Gateway {
public async GetResult(data: any): Promise<Result | undefined> {
const json = await this.MakePostJsonRequest("result", data);
- if (json != null) {
+ if (json !== null) {
const res = Result.fromJS(json);
return res;
}
@@ -133,8 +144,15 @@ export class Gateway {
});
}
- public async MakeGetRequest(endpoint: string, signal?: AbortSignal): Promise<any> {
- const url = 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));
+ Object.getOwnPropertyNames(params).map(prop =>
+ newUrl.searchParams.append(prop, params[prop]));
+ return Gateway.ConstructUrl(endpoint) + newUrl.search;
+ })();
+
const response = await fetch(url,
{
redirect: "follow",
@@ -162,18 +180,18 @@ export class Gateway {
public static ConstructUrl(appendix: string): string {
- let base = Settings.Instance.ServerUrl;
- if (base.slice(-1) == "/") {
+ let base = NorthstarSettings.Instance.ServerUrl;
+ if (base.slice(-1) === "/") {
base = base.slice(0, -1);
}
- let url = base + "/" + Settings.Instance.ServerApiPath + "/" + appendix;
+ let url = base + "/" + NorthstarSettings.Instance.ServerApiPath + "/" + appendix;
return url;
}
}
declare var ENV: any;
-export class Settings {
+export class NorthstarSettings {
private _environment: any;
@observable
@@ -230,10 +248,10 @@ export class Settings {
return window.location.origin + "/";
}
- private static _instance: Settings;
+ private static _instance: NorthstarSettings;
@action
- public Update(environment: any): void {
+ public UpdateEnvironment(environment: any): void {
/*let serverParam = new URL(document.URL).searchParams.get("serverUrl");
if (serverParam) {
if (serverParam === "debug") {
@@ -246,23 +264,23 @@ export class Settings {
else {
this.ServerUrl = environment["SERVER_URL"] ? environment["SERVER_URL"] : document.URL;
}*/
- this.ServerUrl = environment["SERVER_URL"] ? environment["SERVER_URL"] : document.URL;
- this.ServerApiPath = environment["SERVER_API_PATH"];
- this.SampleSize = environment["SAMPLE_SIZE"];
- this.XBins = environment["X_BINS"];
- this.YBins = environment["Y_BINS"];
- this.SplashTimeInMS = environment["SPLASH_TIME_IN_MS"];
- this.ShowFpsCounter = environment["SHOW_FPS_COUNTER"];
- this.ShowShutdownButton = environment["SHOW_SHUTDOWN_BUTTON"];
- this.IsMenuFixed = environment["IS_MENU_FIXED"];
- this.IsDarpa = environment["IS_DARPA"];
- this.IsIGT = environment["IS_IGT"];
- this.DegreeOfParallelism = environment["DEGREE_OF_PARALLISM"];
+ this.ServerUrl = environment.SERVER_URL ? environment.SERVER_URL : document.URL;
+ this.ServerApiPath = environment.SERVER_API_PATH;
+ this.SampleSize = environment.SAMPLE_SIZE;
+ this.XBins = environment.X_BINS;
+ this.YBins = environment.Y_BINS;
+ this.SplashTimeInMS = environment.SPLASH_TIME_IN_MS;
+ this.ShowFpsCounter = environment.SHOW_FPS_COUNTER;
+ this.ShowShutdownButton = environment.SHOW_SHUTDOWN_BUTTON;
+ this.IsMenuFixed = environment.IS_MENU_FIXED;
+ this.IsDarpa = environment.IS_DARPA;
+ this.IsIGT = environment.IS_IGT;
+ this.DegreeOfParallelism = environment.DEGREE_OF_PARALLISM;
}
- public static get Instance(): Settings {
+ public static get Instance(): NorthstarSettings {
if (!this._instance) {
- this._instance = new Settings();
+ this._instance = new NorthstarSettings();
}
return this._instance;
}
diff --git a/src/client/northstar/model/ModelExtensions.ts b/src/client/northstar/model/ModelExtensions.ts
index 9fcba7f1c..29f80d2d1 100644
--- a/src/client/northstar/model/ModelExtensions.ts
+++ b/src/client/northstar/model/ModelExtensions.ts
@@ -1,48 +1,48 @@
-import { AttributeParameters, Brush, MarginAggregateParameters, SingleDimensionAggregateParameters, Solution } from '../model/idea/idea'
-import { Utils } from '../utils/Utils'
+import { AttributeParameters, Brush, MarginAggregateParameters, SingleDimensionAggregateParameters, Solution } from '../model/idea/idea';
+import { Utils } from '../utils/Utils';
-import { FilterModel } from '../core/filter/FilterModel'
+import { FilterModel } from '../core/filter/FilterModel';
-(SingleDimensionAggregateParameters as any).prototype["Equals"] = function (other: Object) {
+(SingleDimensionAggregateParameters as any).prototype.Equals = function (other: Object) {
if (!Utils.EqualityHelper(this, other)) return false;
if (!Utils.EqualityHelper((this as SingleDimensionAggregateParameters).attributeParameters!,
(other as SingleDimensionAggregateParameters).attributeParameters!)) return false;
- if (!((this as SingleDimensionAggregateParameters).attributeParameters! as any)["Equals"]((other as SingleDimensionAggregateParameters).attributeParameters)) return false;
+ if (!((this as SingleDimensionAggregateParameters).attributeParameters! as any).Equals((other as SingleDimensionAggregateParameters).attributeParameters)) return false;
return true;
-}
+};
{
- (AttributeParameters as any).prototype["Equals"] = function (other: AttributeParameters) {
- return (<any>this).constructor.name === (<any>other).constructor.name &&
+ (AttributeParameters as any).prototype.Equals = function (other: AttributeParameters) {
+ return (this).constructor.name === (<any>other).constructor.name &&
this.rawName === other.rawName;
- }
+ };
}
{
- (Solution as any).prototype["Equals"] = function (other: Object) {
+ (Solution as any).prototype.Equals = function (other: Object) {
if (!Utils.EqualityHelper(this, other)) return false;
if ((this as Solution).solutionId !== (other as Solution).solutionId) return false;
return true;
- }
+ };
}
{
- (MarginAggregateParameters as any).prototype["Equals"] = function (other: Object) {
+ (MarginAggregateParameters as any).prototype.Equals = function (other: Object) {
if (!Utils.EqualityHelper(this, other)) return false;
if (!Utils.EqualityHelper((this as SingleDimensionAggregateParameters).attributeParameters!,
(other as SingleDimensionAggregateParameters).attributeParameters!)) return false;
- if (!((this as SingleDimensionAggregateParameters).attributeParameters! as any)["Equals"]((other as SingleDimensionAggregateParameters).attributeParameters!)) return false;
+ if (!((this as SingleDimensionAggregateParameters).attributeParameters! as any).Equals((other as SingleDimensionAggregateParameters).attributeParameters!)) return false;
if ((this as MarginAggregateParameters).aggregateFunction !== (other as MarginAggregateParameters).aggregateFunction) return false;
return true;
- }
+ };
}
{
- (Brush as any).prototype["Equals"] = function (other: Object) {
+ (Brush as any).prototype.Equals = function (other: Object) {
if (!Utils.EqualityHelper(this, other)) return false;
if ((this as Brush).brushEnum !== (other as Brush).brushEnum) return false;
if ((this as Brush).brushIndex !== (other as Brush).brushIndex) return false;
return true;
- }
+ };
} \ No newline at end of file
diff --git a/src/client/northstar/model/ModelHelpers.ts b/src/client/northstar/model/ModelHelpers.ts
index d0711fb69..ac807b41f 100644
--- a/src/client/northstar/model/ModelHelpers.ts
+++ b/src/client/northstar/model/ModelHelpers.ts
@@ -16,7 +16,7 @@ export class ModelHelpers {
public static CreateAggregateKey(distinctAttributeParameters: AttributeParameters | undefined, atm: AttributeTransformationModel, histogramResult: HistogramResult,
brushIndex: number, aggParameters?: SingleDimensionAggregateParameters): AggregateKey {
{
- if (aggParameters == undefined) {
+ if (aggParameters === undefined) {
aggParameters = ModelHelpers.GetAggregateParameter(distinctAttributeParameters, atm);
}
else {
@@ -64,7 +64,7 @@ export class ModelHelpers {
if (aggParams) {
aggregateParameters.push(aggParams);
- var margin = new MarginAggregateParameters()
+ var margin = new MarginAggregateParameters();
margin.aggregateFunction = agg.AggregateFunction;
margin.attributeParameters = ModelHelpers.GetAttributeParameters(agg.AttributeModel);
margin.distinctAttributeParameters = distinctAttributeParameters;
@@ -106,7 +106,7 @@ export class ModelHelpers {
{
rawName: am.CodeName,
visualizationHints: am.VisualizationHints,
- id: (am as BackendAttributeModel).Id
+ id: (am).Id
});
}
else if (am instanceof CodeAttributeModel) {
@@ -114,11 +114,11 @@ export class ModelHelpers {
{
rawName: am.CodeName,
visualizationHints: am.VisualizationHints,
- code: (am as CodeAttributeModel).Code
+ code: (am).Code
});
}
else {
- throw new Exception()
+ throw new Exception();
}
}
@@ -146,7 +146,7 @@ export class ModelHelpers {
}
public static GetAggregateResult(bin: Bin, aggregateKey: AggregateKey) {
- if (aggregateKey.aggregateParameterIndex == -1 || aggregateKey.brushIndex == -1) {
+ if (aggregateKey.aggregateParameterIndex === -1 || aggregateKey.brushIndex === -1) {
return null;
}
return bin.aggregateResults![aggregateKey.aggregateParameterIndex! * bin.ySize! + aggregateKey.brushIndex!];
@@ -157,9 +157,9 @@ export class ModelHelpers {
var ret = new Array<AggregateFunction>();
ret.push(AggregateFunction.None);
ret.push(AggregateFunction.Count);
- if (atm.AttributeModel.DataType == DataType.Float ||
- atm.AttributeModel.DataType == DataType.Double ||
- atm.AttributeModel.DataType == DataType.Int) {
+ if (atm.AttributeModel.DataType === DataType.Float ||
+ atm.AttributeModel.DataType === DataType.Double ||
+ atm.AttributeModel.DataType === DataType.Int) {
ret.push(AggregateFunction.Avg);
ret.push(AggregateFunction.Sum);
}
diff --git a/src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts b/src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts
index 995bf4e0b..120b034f2 100644
--- a/src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts
+++ b/src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts
@@ -1,5 +1,5 @@
-import { AlphabeticBinRange, BinLabel } from '../../model/idea/idea'
-import { VisualBinRange } from './VisualBinRange'
+import { AlphabeticBinRange, BinLabel } from '../../model/idea/idea';
+import { VisualBinRange } from './VisualBinRange';
export class AlphabeticVisualBinRange extends VisualBinRange {
public DataBinRange: AlphabeticBinRange;
diff --git a/src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts b/src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts
index 9313fb1a7..776e643cd 100644
--- a/src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts
+++ b/src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts
@@ -1,5 +1,5 @@
-import { DateTimeBinRange, DateTimeStep, DateTimeStepGranularity } from '../idea/idea'
-import { VisualBinRange } from './VisualBinRange'
+import { DateTimeBinRange, DateTimeStep, DateTimeStepGranularity } from '../idea/idea';
+import { VisualBinRange } from './VisualBinRange';
export class DateTimeVisualBinRange extends VisualBinRange {
public DataBinRange: DateTimeBinRange;
@@ -39,24 +39,24 @@ export class DateTimeVisualBinRange extends VisualBinRange {
public GetLabel(value: number): string {
var dt = DateTimeVisualBinRange.TicksToDate(value);
- if (this.DataBinRange.step!.dateTimeStepGranularity == DateTimeStepGranularity.Second ||
- this.DataBinRange.step!.dateTimeStepGranularity == DateTimeStepGranularity.Minute) {
+ if (this.DataBinRange.step!.dateTimeStepGranularity === DateTimeStepGranularity.Second ||
+ this.DataBinRange.step!.dateTimeStepGranularity === DateTimeStepGranularity.Minute) {
return ("" + this.pad(dt.getMinutes(), 2) + ":" + this.pad(dt.getSeconds(), 2));
//return dt.ToString("mm:ss");
}
- else if (this.DataBinRange.step!.dateTimeStepGranularity == DateTimeStepGranularity.Hour) {
+ else if (this.DataBinRange.step!.dateTimeStepGranularity === DateTimeStepGranularity.Hour) {
return (this.pad(dt.getHours(), 2) + ":" + this.pad(dt.getMinutes(), 2));
//return dt.ToString("HH:mm");
}
- else if (this.DataBinRange.step!.dateTimeStepGranularity == DateTimeStepGranularity.Day) {
+ else if (this.DataBinRange.step!.dateTimeStepGranularity === DateTimeStepGranularity.Day) {
return ((dt.getMonth() + 1) + "/" + dt.getDate() + "/" + dt.getFullYear());
//return dt.ToString("MM/dd/yyyy");
}
- else if (this.DataBinRange.step!.dateTimeStepGranularity == DateTimeStepGranularity.Month) {
+ else if (this.DataBinRange.step!.dateTimeStepGranularity === DateTimeStepGranularity.Month) {
//return dt.ToString("MM/yyyy");
return ((dt.getMonth() + 1) + "/" + dt.getFullYear());
}
- else if (this.DataBinRange.step!.dateTimeStepGranularity == DateTimeStepGranularity.Year) {
+ else if (this.DataBinRange.step!.dateTimeStepGranularity === DateTimeStepGranularity.Year) {
return "" + dt.getFullYear();
}
return "n/a";
@@ -82,22 +82,22 @@ export class DateTimeVisualBinRange extends VisualBinRange {
public static AddToDateTimeTicks(ticks: number, dateTimeStep: DateTimeStep): number {
var copiedDate = DateTimeVisualBinRange.TicksToDate(ticks);
var returnDate: Date = new Date(Date.now());
- if (dateTimeStep.dateTimeStepGranularity == DateTimeStepGranularity.Second) {
+ if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Second) {
returnDate = new Date(copiedDate.setSeconds(copiedDate.getSeconds() + dateTimeStep.dateTimeStepValue!));
}
- else if (dateTimeStep.dateTimeStepGranularity == DateTimeStepGranularity.Minute) {
+ else if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Minute) {
returnDate = new Date(copiedDate.setMinutes(copiedDate.getMinutes() + dateTimeStep.dateTimeStepValue!));
}
- else if (dateTimeStep.dateTimeStepGranularity == DateTimeStepGranularity.Hour) {
+ else if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Hour) {
returnDate = new Date(copiedDate.setHours(copiedDate.getHours() + dateTimeStep.dateTimeStepValue!));
}
- else if (dateTimeStep.dateTimeStepGranularity == DateTimeStepGranularity.Day) {
+ else if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Day) {
returnDate = new Date(copiedDate.setDate(copiedDate.getDate() + dateTimeStep.dateTimeStepValue!));
}
- else if (dateTimeStep.dateTimeStepGranularity == DateTimeStepGranularity.Month) {
+ else if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Month) {
returnDate = new Date(copiedDate.setMonth(copiedDate.getMonth() + dateTimeStep.dateTimeStepValue!));
}
- else if (dateTimeStep.dateTimeStepGranularity == DateTimeStepGranularity.Year) {
+ else if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Year) {
returnDate = new Date(copiedDate.setFullYear(copiedDate.getFullYear() + dateTimeStep.dateTimeStepValue!));
}
return DateTimeVisualBinRange.DateToTicks(returnDate);
diff --git a/src/client/northstar/model/binRanges/NominalVisualBinRange.ts b/src/client/northstar/model/binRanges/NominalVisualBinRange.ts
index 407ff3ea6..42509d797 100644
--- a/src/client/northstar/model/binRanges/NominalVisualBinRange.ts
+++ b/src/client/northstar/model/binRanges/NominalVisualBinRange.ts
@@ -1,5 +1,5 @@
-import { NominalBinRange, BinLabel } from '../../model/idea/idea'
-import { VisualBinRange } from './VisualBinRange'
+import { NominalBinRange, BinLabel } from '../../model/idea/idea';
+import { VisualBinRange } from './VisualBinRange';
export class NominalVisualBinRange extends VisualBinRange {
public DataBinRange: NominalBinRange;
diff --git a/src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts b/src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts
index 80886416b..c579c8e5f 100644
--- a/src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts
+++ b/src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts
@@ -1,4 +1,4 @@
-import { QuantitativeBinRange } from '../idea/idea'
+import { QuantitativeBinRange } from '../idea/idea';
import { VisualBinRange } from './VisualBinRange';
import { format } from "d3-format";
diff --git a/src/client/northstar/model/binRanges/VisualBinRange.ts b/src/client/northstar/model/binRanges/VisualBinRange.ts
index f53008f9a..449a22e91 100644
--- a/src/client/northstar/model/binRanges/VisualBinRange.ts
+++ b/src/client/northstar/model/binRanges/VisualBinRange.ts
@@ -1,11 +1,7 @@
-import { BinLabel } from '../../model/idea/idea'
+import { BinLabel } from '../../model/idea/idea';
export abstract class VisualBinRange {
- constructor() {
-
- }
-
public abstract AddStep(value: number): number;
public abstract GetValueFromIndex(index: number): number;
diff --git a/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts b/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts
index 53d585bb4..a92412686 100644
--- a/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts
+++ b/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts
@@ -4,7 +4,7 @@ import { NominalVisualBinRange } from "./NominalVisualBinRange";
import { QuantitativeVisualBinRange } from "./QuantitativeVisualBinRange";
import { AlphabeticVisualBinRange } from "./AlphabeticVisualBinRange";
import { DateTimeVisualBinRange } from "./DateTimeVisualBinRange";
-import { Settings } from "../../manager/Gateway";
+import { NorthstarSettings } from "../../manager/Gateway";
import { ModelHelpers } from "../ModelHelpers";
import { AttributeTransformationModel } from "../../core/attribute/AttributeTransformationModel";
@@ -16,18 +16,18 @@ export class VisualBinRangeHelper {
public static GetNonAggregateVisualBinRange(dataBinRange: BinRange): VisualBinRange {
if (dataBinRange instanceof NominalBinRange) {
- return new NominalVisualBinRange(dataBinRange as NominalBinRange);
+ return new NominalVisualBinRange(dataBinRange);
}
else if (dataBinRange instanceof QuantitativeBinRange) {
- return new QuantitativeVisualBinRange(dataBinRange as QuantitativeBinRange);
+ return new QuantitativeVisualBinRange(dataBinRange);
}
else if (dataBinRange instanceof AlphabeticBinRange) {
- return new AlphabeticVisualBinRange(dataBinRange as AlphabeticBinRange);
+ return new AlphabeticVisualBinRange(dataBinRange);
}
else if (dataBinRange instanceof DateTimeBinRange) {
- return new DateTimeVisualBinRange(dataBinRange as DateTimeBinRange);
+ return new DateTimeVisualBinRange(dataBinRange);
}
- throw new Exception()
+ throw new Exception();
}
public static GetVisualBinRange(distinctAttributeParameters: AttributeParameters | undefined, dataBinRange: BinRange, histoResult: HistogramResult, attr: AttributeTransformationModel, chartType: ChartType): VisualBinRange {
@@ -39,8 +39,7 @@ export class VisualBinRangeHelper {
var aggregateKey = ModelHelpers.CreateAggregateKey(distinctAttributeParameters, attr, histoResult, ModelHelpers.AllBrushIndex(histoResult));
var minValue = Number.MAX_VALUE;
var maxValue = Number.MIN_VALUE;
- for (var b = 0; b < histoResult.brushes!.length; b++) {
- var brush = histoResult.brushes![b];
+ for (const brush of histoResult.brushes!) {
aggregateKey.brushIndex = brush.brushIndex;
for (var key in histoResult.bins) {
if (histoResult.bins.hasOwnProperty(key)) {
@@ -52,16 +51,16 @@ export class VisualBinRangeHelper {
}
}
}
- };
+ }
let visualBinRange = QuantitativeVisualBinRange.Initialize(minValue, maxValue, 10, false);
- if (chartType == ChartType.HorizontalBar || chartType == ChartType.VerticalBar) {
+ if (chartType === ChartType.HorizontalBar || chartType === ChartType.VerticalBar) {
visualBinRange = QuantitativeVisualBinRange.Initialize(Math.min(0, minValue),
- Math.max(0, (visualBinRange as QuantitativeVisualBinRange).DataBinRange.maxValue!),
+ Math.max(0, (visualBinRange).DataBinRange.maxValue!),
SETTINGS_X_BINS, false);
}
- else if (chartType == ChartType.SinglePoint) {
+ else if (chartType === ChartType.SinglePoint) {
visualBinRange = QuantitativeVisualBinRange.Initialize(Math.min(0, minValue), Math.max(0, maxValue),
SETTINGS_X_BINS, false);
}
diff --git a/src/client/northstar/model/idea/MetricTypeMapping.ts b/src/client/northstar/model/idea/MetricTypeMapping.ts
index 11e0c871a..e9759cf16 100644
--- a/src/client/northstar/model/idea/MetricTypeMapping.ts
+++ b/src/client/northstar/model/idea/MetricTypeMapping.ts
@@ -5,20 +5,20 @@ import { Dictionary } from 'typescript-collections';
export class MetricTypeMapping {
public static GetMetricInterpretation(metricType: MetricType): MetricInterpretation {
- if (metricType == MetricType.Accuracy ||
- metricType == MetricType.F1 ||
- metricType == MetricType.F1Macro ||
- metricType == MetricType.F1Micro ||
- metricType == MetricType.JaccardSimilarityScore ||
- metricType == MetricType.ObjectDetectionAveragePrecision ||
- metricType == MetricType.Precision ||
- metricType == MetricType.PrecisionAtTopK ||
- metricType == MetricType.NormalizedMutualInformation ||
- metricType == MetricType.Recall ||
- metricType == MetricType.RocAucMacro ||
- metricType == MetricType.RocAuc ||
- metricType == MetricType.RocAucMicro ||
- metricType == MetricType.RSquared) {
+ if (metricType === MetricType.Accuracy ||
+ metricType === MetricType.F1 ||
+ metricType === MetricType.F1Macro ||
+ metricType === MetricType.F1Micro ||
+ metricType === MetricType.JaccardSimilarityScore ||
+ metricType === MetricType.ObjectDetectionAveragePrecision ||
+ metricType === MetricType.Precision ||
+ metricType === MetricType.PrecisionAtTopK ||
+ metricType === MetricType.NormalizedMutualInformation ||
+ metricType === MetricType.Recall ||
+ metricType === MetricType.RocAucMacro ||
+ metricType === MetricType.RocAuc ||
+ metricType === MetricType.RocAucMicro ||
+ metricType === MetricType.RSquared) {
return MetricInterpretation.HigherIsBetter;
}
return MetricInterpretation.LowerIsBetter;
diff --git a/src/client/northstar/operations/BaseOperation.ts b/src/client/northstar/operations/BaseOperation.ts
index f545b2c58..c6d5f0a15 100644
--- a/src/client/northstar/operations/BaseOperation.ts
+++ b/src/client/northstar/operations/BaseOperation.ts
@@ -1,4 +1,4 @@
-import { FilterModel } from '../core/filter/FilterModel'
+import { FilterModel } from '../core/filter/FilterModel';
import { ErrorResult, Exception, OperationParameters, OperationReference, Result, ResultParameters } from '../model/idea/idea';
import { action, computed, observable } from "mobx";
import { Gateway } from '../manager/Gateway';
@@ -29,11 +29,11 @@ export abstract class BaseOperation {
// let filterModels: FilterModel[] = [];
// return FilterModel.GetFilterModelsRecursive(this, new Set<GraphNode<BaseOperationViewModel, FilterLinkViewModel>>(), filterModels, true)
// if (this.OverridingFilters.length > 0) {
- // return "(" + this.OverridingFilters.filter(fm => fm != null).map(fm => fm.ToPythonString()).join(" || ") + ")";
+ // return "(" + this.OverridingFilters.filter(fm => fm !== null).map(fm => fm.ToPythonString()).join(" || ") + ")";
// }
// let rdg = MainManager.Instance.MainViewModel.FilterReverseDependencyGraph;
// let sliceModel = this.TypedViewModel.IncomingSliceModel;
- // if (sliceModel != null && sliceModel.Source != null && instanceOfIBaseFilterProvider(sliceModel.Source) && rdg.has(sliceModel.Source)) {
+ // if (sliceModel !== null && sliceModel.Source !== null && instanceOfIBaseFilterProvider(sliceModel.Source) && rdg.has(sliceModel.Source)) {
// let filterModels = sliceModel.Source.FilterModels.map(f => f);
// return FilterModel.GetFilterModelsRecursive(rdg.get(sliceModel.Source), new Set<GraphNode<BaseOperationViewModel, FilterLinkViewModel>>(), filterModels, false);
// }
@@ -62,8 +62,9 @@ export abstract class BaseOperation {
}
let operationParameters = this.CreateOperationParameters();
- if (this.Result)
- this.Result.progress = 0; // bcz: used to set Result to undefined, but that causes the display to blink
+ if (this.Result) {
+ this.Result.progress = 0;
+ } // bcz: used to set Result to undefined, but that causes the display to blink
this.Error = "";
let salt = Math.random().toString();
this.RequestSalt = salt;
@@ -97,10 +98,10 @@ export abstract class BaseOperation {
pollPromise.Start(async () => {
let result = await Gateway.Instance.GetResult(resultParameters.toJSON());
if (result instanceof ErrorResult) {
- throw new Error((result as ErrorResult).message);
+ throw new Error((result).message);
}
- if (this.RequestSalt == pollPromise.RequestSalt) {
- if (result && (!this.Result || this.Result.progress != result.progress)) {
+ if (this.RequestSalt === pollPromise.RequestSalt) {
+ if (result && (!this.Result || this.Result.progress !== result.progress)) {
/*if (operationViewModel.Result !== null && operationViewModel.Result !== undefined) {
let t1 = performance.now();
console.log((t1 - start) + " milliseconds.");
diff --git a/src/client/northstar/operations/HistogramOperation.ts b/src/client/northstar/operations/HistogramOperation.ts
index e63de1632..760106023 100644
--- a/src/client/northstar/operations/HistogramOperation.ts
+++ b/src/client/northstar/operations/HistogramOperation.ts
@@ -42,6 +42,10 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
this.SchemaName = schemaName;
}
+ Copy(): HistogramOperation {
+ return new HistogramOperation(this.SchemaName, this.X, this.Y, this.V, this.Normalization);
+ }
+
Equals(other: Object): boolean {
throw new Error("Method not implemented.");
}
@@ -58,7 +62,7 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
@computed
public get FilterString(): string {
let filterModels: FilterModel[] = [];
- return FilterModel.GetFilterModelsRecursive(this, new Set<IBaseFilterProvider>(), filterModels, true)
+ return FilterModel.GetFilterModelsRecursive(this, new Set<IBaseFilterProvider>(), filterModels, true);
}
@computed
@@ -67,7 +71,7 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
let brushes: string[] = [];
this.BrushLinks.map(brushLink => {
let brushHistogram = brushLink.b.GetT(KeyStore.Data, HistogramField);
- if (brushHistogram && brushHistogram != FieldWaiting) {
+ if (brushHistogram && brushHistogram !== FieldWaiting) {
let filterModels: FilterModel[] = [];
brushes.push(FilterModel.GetFilterModelsRecursive(brushHistogram.Data, new Set<IBaseFilterProvider>(), filterModels, false));
}
diff --git a/src/client/northstar/utils/ArrayUtil.ts b/src/client/northstar/utils/ArrayUtil.ts
index f35c98317..12b8d8e77 100644
--- a/src/client/northstar/utils/ArrayUtil.ts
+++ b/src/client/northstar/utils/ArrayUtil.ts
@@ -7,14 +7,14 @@ export class ArrayUtil {
return false;
}
let isComplex = typeof arr1[0] === "object";
- for (let i = 0; i < arr1.length; i++) {
- if (isComplex && "Equals" in arr1[i]) {
- if (arr1[i].Equals(arr2)) {
+ for (const ele of arr1) {
+ if (isComplex && "Equals" in ele) {
+ if (ele.Equals(arr2)) {
return true;
}
}
else {
- if (arr1[i] === arr2) {
+ if (ele === arr2) {
return true;
}
}
@@ -50,7 +50,7 @@ export class ArrayUtil {
if (filtered.length > 0) {
return filtered[0];
}
- throw new Exception()
+ throw new Exception();
}
public static FirstOrDefault<T>(arr: T[], predicate: (x: any) => boolean): T | undefined {
@@ -63,9 +63,9 @@ export class ArrayUtil {
public static Distinct(arr: any[]): any[] {
let ret = [];
- for (let i = 0; i < arr.length; i++) {
- if (!ArrayUtil.Contains(ret, arr[i])) {
- ret.push(arr[i]);
+ for (const ele of arr) {
+ if (!ArrayUtil.Contains(ret, ele)) {
+ ret.push(ele);
}
}
return ret;
diff --git a/src/client/northstar/utils/Extensions.ts b/src/client/northstar/utils/Extensions.ts
index 7c2b7fc9d..df14d4da0 100644
--- a/src/client/northstar/utils/Extensions.ts
+++ b/src/client/northstar/utils/Extensions.ts
@@ -6,7 +6,7 @@ interface String {
String.prototype.ReplaceAll = function (toReplace: string, replacement: string): string {
var target = this;
return target.split(toReplace).join(replacement);
-}
+};
String.prototype.Truncate = function (length: number, replacement: string): String {
var target = this;
@@ -14,7 +14,7 @@ String.prototype.Truncate = function (length: number, replacement: string): Stri
target = target.slice(0, Math.max(0, length - replacement.length)) + replacement;
}
return target;
-}
+};
interface Math {
log10(val: number): number;
@@ -22,7 +22,7 @@ interface Math {
Math.log10 = function (val: number): number {
return Math.log(val) / Math.LN10;
-}
+};
declare interface ObjectConstructor {
assign(...objects: Object[]): Object;
diff --git a/src/client/northstar/utils/GeometryUtil.ts b/src/client/northstar/utils/GeometryUtil.ts
index d5f3ba631..d5220c479 100644
--- a/src/client/northstar/utils/GeometryUtil.ts
+++ b/src/client/northstar/utils/GeometryUtil.ts
@@ -8,15 +8,19 @@ export class GeometryUtil {
let minY: number = Number.MAX_VALUE;
let maxX: number = Number.MIN_VALUE;
let maxY: number = Number.MIN_VALUE;
- for (var i = 0; i < points.length; i++) {
- if (points[i].x < minX)
- minX = points[i].x;
- if (points[i].y < minY)
- minY = points[i].y;
- if (points[i].x > maxX)
- maxX = points[i].x;
- if (points[i].y > maxY)
- maxY = points[i].y;
+ for (const point of points) {
+ if (point.x < minX) {
+ minX = point.x;
+ }
+ if (point.y < minY) {
+ minY = point.y;
+ }
+ if (point.x > maxX) {
+ maxX = point.x;
+ }
+ if (point.y > maxY) {
+ maxY = point.y;
+ }
}
return new PIXIRectangle(minX * scale - padding, minY * scale - padding, (maxX - minX) * scale + padding * 2, (maxY - minY) * scale + padding * 2);
}
@@ -35,7 +39,7 @@ export class GeometryUtil {
nx = (cos * (x - cx)) + (sin * (y - cy)) + cx,
ny = (cos * (y - cy)) - (sin * (x - cx)) + cy;
return new PIXIPoint(nx, ny);
- }
+ };
return points.map(p => rotate(center.x, center.y, p.x, p.y, angle));
}
@@ -54,9 +58,9 @@ export class GeometryUtil {
return [];
}
- for (let v = 0; v < points.length; v++) {
- x = points[v].x;
- y = points[v].y;
+ for (const point of points) {
+ x = point.x;
+ y = point.y;
sum_x += x;
sum_y += y;
sum_xx += x * x;
@@ -68,8 +72,8 @@ export class GeometryUtil {
let b = (sum_y / count) - (m * sum_x) / count;
let result: PIXIPoint[] = new Array<PIXIPoint>();
- for (let v = 0; v < points.length; v++) {
- x = points[v].x;
+ for (const point of points) {
+ x = point.x;
y = x * m + b;
result.push(new PIXIPoint(x, y));
}
@@ -85,7 +89,7 @@ export class GeometryUtil {
// var xi = vs[i].x, yi = vs[i].y;
// var xj = vs[j].x, yj = vs[j].y;
- // var intersect = ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
+ // var intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
// if (intersect)
// inside = !inside;
// }
diff --git a/src/client/northstar/utils/MathUtil.ts b/src/client/northstar/utils/MathUtil.ts
index bb7e73871..4b44f40c3 100644
--- a/src/client/northstar/utils/MathUtil.ts
+++ b/src/client/northstar/utils/MathUtil.ts
@@ -16,11 +16,11 @@ export class PIXIRectangle {
public x: number;
public y: number;
public width: number;
- public height: number
- public get left() { return this.x }
+ public height: number;
+ public get left() { return this.x; }
public get right() { return this.x + this.width; }
- public get top() { return this.y }
- public get bottom() { return this.top + this.height }
+ public get top() { return this.y; }
+ public get bottom() { return this.top + this.height; }
public static get EMPTY() { return new PIXIRectangle(0, 0, -1, -1); }
constructor(x: number, y: number, width: number, height: number) {
this.x = x;
@@ -93,7 +93,7 @@ export class MathUtil {
public static DistToLineSegment(v: PIXIPoint, w: PIXIPoint, p: PIXIPoint) {
// Return minimum distance between line segment vw and point p
var l2 = MathUtil.DistSquared(v, w); // i.e. |w-v|^2 - avoid a sqrt
- if (l2 == 0.0) return MathUtil.Dist(p, v); // v == w case
+ if (l2 === 0.0) return MathUtil.Dist(p, v); // v === w case
// Consider the line extending the segment, parameterized as v + t (w - v).
// We find projection of point p onto the line.
// It falls where t = [(p-v) . (w-v)] / |w-v|^2
@@ -117,7 +117,7 @@ export class MathUtil {
var b2 = ps2.x - pe2.x;
var delta = a1 * b2 - a2 * b1;
- if (delta == 0) {
+ if (delta === 0) {
return undefined;
}
var c2 = a2 * ps2.x + b2 * ps2.y;
@@ -127,14 +127,18 @@ export class MathUtil {
}
public static PointInPIXIRectangle(p: PIXIPoint, rect: PIXIRectangle): boolean {
- if (p.x < rect.left - this.EPSILON)
+ if (p.x < rect.left - this.EPSILON) {
return false;
- if (p.x > rect.right + this.EPSILON)
+ }
+ if (p.x > rect.right + this.EPSILON) {
return false;
- if (p.y < rect.top - this.EPSILON)
+ }
+ if (p.y < rect.top - this.EPSILON) {
return false;
- if (p.y > rect.bottom + this.EPSILON)
+ }
+ if (p.y > rect.bottom + this.EPSILON) {
return false;
+ }
return true;
}
@@ -145,23 +149,27 @@ export class MathUtil {
var r3 = new PIXIPoint(rect.right, rect.bottom);
var r4 = new PIXIPoint(rect.left, rect.bottom);
var ret = new Array<PIXIPoint>();
- var dist = this.Dist(lineFrom, lineTo)
+ var dist = this.Dist(lineFrom, lineTo);
var inter = this.LineSegmentIntersection(lineFrom, lineTo, r1, r2);
- if (inter != null && this.PointInPIXIRectangle(inter, rect) &&
- this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist)
+ if (inter && this.PointInPIXIRectangle(inter, rect) &&
+ this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) {
ret.push(inter);
+ }
inter = this.LineSegmentIntersection(lineFrom, lineTo, r2, r3);
- if (inter != null && this.PointInPIXIRectangle(inter, rect) &&
- this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist)
+ if (inter && this.PointInPIXIRectangle(inter, rect) &&
+ this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) {
ret.push(inter);
+ }
inter = this.LineSegmentIntersection(lineFrom, lineTo, r3, r4);
- if (inter != null && this.PointInPIXIRectangle(inter, rect) &&
- this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist)
+ if (inter && this.PointInPIXIRectangle(inter, rect) &&
+ this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) {
ret.push(inter);
+ }
inter = this.LineSegmentIntersection(lineFrom, lineTo, r4, r1);
- if (inter != null && this.PointInPIXIRectangle(inter, rect) &&
- this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist)
+ if (inter && this.PointInPIXIRectangle(inter, rect) &&
+ this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) {
ret.push(inter);
+ }
return ret;
}
@@ -178,7 +186,7 @@ export class MathUtil {
}
public static Dot(p1: PIXIPoint, p2: PIXIPoint): number {
- return p1.x * p2.x + p1.y * p2.y
+ return p1.x * p2.x + p1.y * p2.y;
}
public static Normalize(p1: PIXIPoint) {
@@ -193,7 +201,7 @@ export class MathUtil {
public static DistSquared(p1: PIXIPoint, p2: PIXIPoint): number {
const a = p1.x - p2.x;
const b = p1.y - p2.y;
- return (a * a + b * b)
+ return (a * a + b * b);
}
public static RectIntersectsRect(r1: PIXIRectangle, r2: PIXIRectangle): boolean {
diff --git a/src/client/northstar/utils/SizeConverter.ts b/src/client/northstar/utils/SizeConverter.ts
index bb91ed4a7..a52890ed9 100644
--- a/src/client/northstar/utils/SizeConverter.ts
+++ b/src/client/northstar/utils/SizeConverter.ts
@@ -32,8 +32,8 @@ export class SizeConverter {
this.Initialized++;
var xLabels = visualBinRanges[0].GetLabels();
var yLabels = visualBinRanges[1].GetLabels();
- var xLabelStrings = xLabels.map(l => l.label!).sort(function (a, b) { return b.length - a.length });
- var yLabelStrings = yLabels.map(l => l.label!).sort(function (a, b) { return b.length - a.length });
+ var xLabelStrings = xLabels.map(l => l.label!).sort(function (a, b) { return b.length - a.length; });
+ var yLabelStrings = yLabels.map(l => l.label!).sort(function (a, b) { return b.length - a.length; });
var metricsX = { width: 75 }; // RenderUtils.MeasureText(FontStyles.Default.fontFamily.toString(), 12, // FontStyles.AxisLabel.fontSize as number,
//xLabelStrings[0]!.slice(0, 20)) // StyleConstants.MAX_CHAR_FOR_HISTOGRAM_LABELS));
@@ -54,7 +54,7 @@ export class SizeConverter {
}
public DataToScreenNormalizedRange(dataValue: number, normalization: number, axis: number, binBrushMaxAxis: number) {
- var value = normalization != 1 - axis || binBrushMaxAxis == 0 ? dataValue : (dataValue - 0) / (binBrushMaxAxis - 0) * this.DataRanges[axis];
+ var value = normalization !== 1 - axis || binBrushMaxAxis === 0 ? dataValue : (dataValue - 0) / (binBrushMaxAxis - 0) * this.DataRanges[axis];
var from = this.DataToScreenCoord(Math.min(0, value), axis);
var to = this.DataToScreenCoord(Math.max(0, value), axis);
return [from, value, to];
@@ -62,19 +62,20 @@ export class SizeConverter {
public DataToScreenPointRange(axis: number, bin: Bin, aggregateKey: AggregateKey) {
var value = ModelHelpers.GetAggregateResult(bin, aggregateKey) as DoubleValueAggregateResult;
- if (value && value.hasResult)
+ if (value && value.hasResult) {
return [this.DataToScreenCoord(value.result!, axis) - 5,
this.DataToScreenCoord(value.result!, axis) + 5];
+ }
return [undefined, undefined];
}
public DataToScreenXAxisRange(visualBinRanges: VisualBinRange[], index: number, bin: Bin) {
var value = visualBinRanges[0].GetValueFromIndex(bin.binIndex!.indices![index]);
- return [this.DataToScreenX(value), this.DataToScreenX(visualBinRanges[index].AddStep(value))]
+ return [this.DataToScreenX(value), this.DataToScreenX(visualBinRanges[index].AddStep(value))];
}
public DataToScreenYAxisRange(visualBinRanges: VisualBinRange[], index: number, bin: Bin) {
var value = visualBinRanges[1].GetValueFromIndex(bin.binIndex!.indices![index]);
- return [this.DataToScreenY(value), this.DataToScreenY(visualBinRanges[index].AddStep(value))]
+ return [this.DataToScreenY(value), this.DataToScreenY(visualBinRanges[index].AddStep(value))];
}
public DataToScreenX(x: number): number {
@@ -85,8 +86,9 @@ export class SizeConverter {
return flip ? (this.RenderDimension) - retY : retY;
}
public DataToScreenCoord(v: number, axis: number) {
- if (axis == 0)
+ if (axis === 0) {
return this.DataToScreenX(v);
+ }
return this.DataToScreenY(v);
}
public DataToScreenRange(minVal: number, maxVal: number, axis: number) {
@@ -94,6 +96,6 @@ export class SizeConverter {
let xTo = this.DataToScreenX(axis === 0 ? maxVal : this.DataMaxs[0]);
let yFrom = this.DataToScreenY(axis === 1 ? minVal : this.DataMins[1]);
let yTo = this.DataToScreenY(axis === 1 ? maxVal : this.DataMaxs[1]);
- return { xFrom, yFrom, xTo, yTo }
+ return { xFrom, yFrom, xTo, yTo };
}
} \ No newline at end of file
diff --git a/src/client/northstar/utils/StyleContants.ts b/src/client/northstar/utils/StyleContants.ts
index ac8617e3b..e9b6e0297 100644
--- a/src/client/northstar/utils/StyleContants.ts
+++ b/src/client/northstar/utils/StyleContants.ts
@@ -11,7 +11,7 @@ export class StyleConstants {
static OPERATOR_MENU_LARGE: number = 35;
static OPERATOR_MENU_SMALL: number = 25;
- static BRUSH_PALETTE: number[] = [0x42b43c, 0xfa217f, 0x6a9c75, 0xfb5de7, 0x25b8ea, 0x9b5bc4, 0xda9f63, 0xe23209, 0xfb899b, 0x94a6fd]
+ static BRUSH_PALETTE: number[] = [0x42b43c, 0xfa217f, 0x6a9c75, 0xfb5de7, 0x25b8ea, 0x9b5bc4, 0xda9f63, 0xe23209, 0xfb899b, 0x94a6fd];
static GAP: number = 3;
static BACKGROUND_COLOR: number = 0xF3F3F3;
diff --git a/src/client/northstar/utils/Utils.ts b/src/client/northstar/utils/Utils.ts
index b35dce820..d071dec62 100644
--- a/src/client/northstar/utils/Utils.ts
+++ b/src/client/northstar/utils/Utils.ts
@@ -1,7 +1,7 @@
-import { IBaseBrushable } from '../core/brusher/IBaseBrushable'
-import { IBaseFilterConsumer } from '../core/filter/IBaseFilterConsumer'
-import { IBaseFilterProvider } from '../core/filter/IBaseFilterProvider'
-import { AggregateFunction } from '../model/idea/idea'
+import { IBaseBrushable } from '../core/brusher/IBaseBrushable';
+import { IBaseFilterConsumer } from '../core/filter/IBaseFilterConsumer';
+import { IBaseFilterProvider } from '../core/filter/IBaseFilterProvider';
+import { AggregateFunction } from '../model/idea/idea';
export class Utils {
@@ -27,17 +27,17 @@ export class Utils {
public static isBaseBrushable<T>(obj: Object): obj is IBaseBrushable<T> {
let typed = <IBaseBrushable<T>>obj;
- return typed != null && typed.BrusherModels !== undefined;
+ return typed !== null && typed.BrusherModels !== undefined;
}
public static isBaseFilterProvider(obj: Object): obj is IBaseFilterProvider {
let typed = <IBaseFilterProvider>obj;
- return typed != null && typed.FilterModels !== undefined;
+ return typed !== null && typed.FilterModels !== undefined;
}
public static isBaseFilterConsumer(obj: Object): obj is IBaseFilterConsumer {
let typed = <IBaseFilterConsumer>obj;
- return typed != null && typed.FilterOperand !== undefined;
+ return typed !== null && typed.FilterOperand !== undefined;
}
public static EncodeQueryData(data: any): string {
@@ -63,9 +63,9 @@ export class Utils {
public static GetQueryVariable(variable: string) {
let query = window.location.search.substring(1);
let vars = query.split("&");
- for (let i = 0; i < vars.length; i++) {
- let pair = vars[i].split("=");
- if (decodeURIComponent(pair[0]) == variable) {
+ for (const variable of vars) {
+ let pair = variable.split("=");
+ if (decodeURIComponent(pair[0]) === variable) {
return decodeURIComponent(pair[1]);
}
}
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index bf59fbb43..3b5a5b470 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,11 +1,9 @@
-import React = require('react')
-import { observer } from 'mobx-react';
-import { observable, action, computed } from 'mobx';
-import { Document } from "../../fields/Document"
-import { DocumentView } from '../views/nodes/DocumentView';
-import { KeyStore } from '../../fields/KeyStore';
+import { computed, observable } from 'mobx';
+import { Document } from "../../fields/Document";
import { FieldWaiting } from '../../fields/Field';
+import { KeyStore } from '../../fields/KeyStore';
import { ListField } from '../../fields/ListField';
+import { DocumentView } from '../views/nodes/DocumentView';
export class DocumentManager {
@@ -27,11 +25,6 @@ export class DocumentManager {
// this.DocumentViews = new Array<DocumentView>();
}
- public getAllDocumentViews(collection: Document) {
- return this.DocumentViews.filter(dv =>
- dv.props.ContainingCollectionView && dv.props.ContainingCollectionView.props.Document == collection);
- }
-
public getDocumentView(toFind: Document): DocumentView | null {
let toReturn: DocumentView | null;
@@ -40,19 +33,18 @@ export class DocumentManager {
//gets document view that is in a freeform canvas collection
DocumentManager.Instance.DocumentViews.map(view => {
let doc = view.props.Document;
- // if (view.props.ContainingCollectionView instanceof CollectionFreeFormView) {
- if (Object.is(doc, toFind)) {
+ if (doc === toFind) {
toReturn = view;
return;
}
let docSrc = doc.GetT(KeyStore.Prototype, Document);
- if (docSrc && docSrc != FieldWaiting && Object.is(docSrc, toFind)) {
+ if (docSrc && docSrc !== FieldWaiting && Object.is(docSrc, toFind)) {
toReturn = view;
}
- })
+ });
- return (toReturn);
+ return toReturn;
}
public getDocumentViews(toFind: Document): DocumentView[] {
@@ -63,31 +55,30 @@ export class DocumentManager {
let doc = view.props.Document;
// if (view.props.ContainingCollectionView instanceof CollectionFreeFormView) {
- if (Object.is(doc, toFind)) {
+ if (doc === toFind) {
toReturn.push(view);
} else {
let docSrc = doc.GetT(KeyStore.Prototype, Document);
- if (docSrc && docSrc != FieldWaiting && Object.is(docSrc, toFind)) {
+ if (docSrc && docSrc !== FieldWaiting && Object.is(docSrc, toFind)) {
toReturn.push(view);
}
}
- })
+ });
- return (toReturn);
+ return toReturn;
}
@computed
public get LinkedDocumentViews() {
return DocumentManager.Instance.DocumentViews.reduce((pairs, dv) => {
let linksList = dv.props.Document.GetT(KeyStore.LinkedToDocs, ListField);
- if (linksList && linksList != FieldWaiting && linksList.Data.length) {
+ if (linksList && linksList !== FieldWaiting && linksList.Data.length) {
pairs.push(...linksList.Data.reduce((pairs, link) => {
if (link instanceof Document) {
let linkToDoc = link.GetT(KeyStore.LinkedToDocs, Document);
- if (linkToDoc && linkToDoc != FieldWaiting) {
- DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => {
- pairs.push({ a: dv, b: docView1, l: link })
- })
+ if (linkToDoc && linkToDoc !== FieldWaiting) {
+ DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 =>
+ pairs.push({ a: dv, b: docView1, l: link }));
}
}
return pairs;
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 9ffe964ef..4bd654e15 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,13 +1,14 @@
-import { DocumentDecorations } from "../views/DocumentDecorations";
-import { CollectionDockingView } from "../views/collections/CollectionDockingView";
-import { Document } from "../../fields/Document"
import { action } from "mobx";
-import { ImageField } from "../../fields/ImageField";
+import { Document } from "../../fields/Document";
+import { FieldWaiting } from "../../fields/Field";
import { KeyStore } from "../../fields/KeyStore";
-import { CollectionView } from "../views/collections/CollectionView";
-import { DocumentView } from "../views/nodes/DocumentView";
+import { emptyFunction } from "../../Utils";
+import { CollectionDockingView } from "../views/collections/CollectionDockingView";
+import { DocumentDecorations } from "../views/DocumentDecorations";
+import * as globalCssVariables from "../views/globalCssVariables.scss";
+import { MainOverlayTextBox } from "../views/MainOverlayTextBox";
-export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Document, removeFunc: (containingCollection: CollectionView) => void = () => { }) {
+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();
@@ -15,8 +16,9 @@ export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc:
document.removeEventListener("pointermove", onRowMove);
document.removeEventListener('pointerup', onRowUp);
var dragData = new DragManager.DocumentDragData([docFunc()]);
- dragData.removeDocument = removeFunc;
- DragManager.StartDocumentDrag([_reference.current!], dragData);
+ dragData.copyOnDrop = copyOnDrop;
+ dragData.moveDocument = moveFunc;
+ DragManager.StartDocumentDrag([_reference.current!], dragData, e.x, e.y);
});
let onRowUp = action((e: PointerEvent): void => {
document.removeEventListener("pointermove", onRowMove);
@@ -24,20 +26,45 @@ export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc:
});
let onItemDown = (e: React.PointerEvent) => {
// if (this.props.isSelected() || this.props.isTopMost) {
- if (e.button == 0) {
+ if (e.button === 0) {
e.stopPropagation();
if (e.shiftKey) {
CollectionDockingView.Instance.StartOtherDrag([docFunc()], e);
} else {
document.addEventListener("pointermove", onRowMove);
- document.addEventListener('pointerup', onRowUp);
+ document.addEventListener("pointerup", onRowUp);
}
}
//}
- }
+ };
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");
@@ -50,7 +77,9 @@ export namespace DragManager {
let dragDiv: HTMLDivElement;
export enum DragButtons {
- Left = 1, Right = 2, Both = Left | Right
+ Left = 1,
+ Right = 2,
+ Both = Left | Right
}
interface DragOptions {
@@ -63,8 +92,7 @@ export namespace DragManager {
(): void;
}
- export class DragCompleteEvent {
- }
+ export class DragCompleteEvent { }
export interface DragHandlers {
dragComplete: (e: DragCompleteEvent) => void;
@@ -74,21 +102,27 @@ export namespace DragManager {
handlers: DropHandlers;
}
export class DropEvent {
- constructor(readonly x: number, readonly y: number, readonly data: { [id: string]: any }) { }
+ constructor(
+ readonly x: number,
+ readonly y: number,
+ readonly data: { [id: string]: any }
+ ) { }
}
-
-
export interface DropHandlers {
drop: (e: Event, de: DropEvent) => void;
}
-
- export function MakeDropTarget(element: HTMLElement, options: DropOptions): DragDropDisposer {
+ export function MakeDropTarget(
+ element: HTMLElement,
+ options: DropOptions
+ ): DragDropDisposer {
if ("canDrop" in element.dataset) {
- throw new Error("Element is already droppable, can't make it droppable again");
+ throw new Error(
+ "Element is already droppable, can't make it droppable again"
+ );
}
- element.dataset["canDrop"] = "true";
+ element.dataset.canDrop = "true";
const handler = (e: Event) => {
const ce = e as CustomEvent<DropEvent>;
options.handlers.drop(e, ce.detail);
@@ -96,84 +130,102 @@ export namespace DragManager {
element.addEventListener("dashOnDrop", handler);
return () => {
element.removeEventListener("dashOnDrop", handler);
- delete element.dataset["canDrop"]
+ delete element.dataset.canDrop;
};
}
+ export type MoveFunction = (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
export class DocumentDragData {
constructor(dragDoc: Document[]) {
this.draggedDocuments = dragDoc;
this.droppedDocuments = dragDoc;
+ this.xOffset = 0;
+ this.yOffset = 0;
}
draggedDocuments: Document[];
droppedDocuments: Document[];
- xOffset?: number;
- yOffset?: number;
+ xOffset: number;
+ yOffset: number;
aliasOnDrop?: boolean;
- removeDocument?: (collectionDrop: CollectionView) => void;
+ copyOnDrop?: boolean;
+ moveDocument?: MoveFunction;
[id: string]: any;
}
- export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, options?: DragOptions) {
- StartDrag(eles, dragData, options, (dropData: { [id: string]: any }) => dropData.droppedDocuments = dragData.aliasOnDrop ? dragData.draggedDocuments.map(d => d.CreateAlias()) : dragData.draggedDocuments);
+ export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) {
+ StartDrag(eles, dragData, downX, downY, options,
+ (dropData: { [id: string]: any }) => (dropData.droppedDocuments = dragData.aliasOnDrop ? dragData.draggedDocuments.map(d => d.CreateAlias()) : dragData.copyOnDrop ? dragData.draggedDocuments.map(d => d.Copy(true) as Document) : dragData.draggedDocuments));
}
export class LinkDragData {
- constructor(linkSourceDoc: DocumentView) {
- this.linkSourceDocumentView = linkSourceDoc;
+ constructor(linkSourceDoc: Document) {
+ this.linkSourceDocument = linkSourceDoc;
}
- linkSourceDocumentView: DocumentView;
+ droppedDocuments: Document[] = [];
+ linkSourceDocument: Document;
[id: string]: any;
}
- export function StartLinkDrag(ele: HTMLElement, dragData: LinkDragData, options?: DragOptions) {
- StartDrag([ele], dragData, options);
+
+ export function StartLinkDrag(ele: HTMLElement, dragData: LinkDragData, downX: number, downY: number, options?: DragOptions) {
+ StartDrag([ele], dragData, downX, downY, options);
}
- function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, options?: DragOptions, finishDrag?: (dropData: { [id: string]: any }) => void) {
+
+ function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: { [id: string]: any }) => void) {
if (!dragDiv) {
dragDiv = document.createElement("div");
- dragDiv.className = "dragManager-dragDiv"
+ dragDiv.className = "dragManager-dragDiv";
DragManager.Root().appendChild(dragDiv);
}
+ MainOverlayTextBox.Instance.SetTextDoc();
let scaleXs: number[] = [];
let scaleYs: number[] = [];
let xs: number[] = [];
let ys: number[] = [];
- const docs: Document[] = dragData instanceof DocumentDragData ? dragData.draggedDocuments : [];
+ const docs: Document[] =
+ dragData instanceof DocumentDragData ? dragData.draggedDocuments : [];
let dragElements = eles.map(ele => {
- const w = ele.offsetWidth, h = ele.offsetHeight;
+ const w = ele.offsetWidth,
+ h = ele.offsetHeight;
const rect = ele.getBoundingClientRect();
- const scaleX = rect.width / w, scaleY = rect.height / h;
- let x = rect.left, y = rect.top;
- xs.push(x); ys.push(y);
- scaleXs.push(scaleX); scaleYs.push(scaleY);
+ const scaleX = rect.width / w,
+ scaleY = rect.height / h;
+ let x = rect.left,
+ y = rect.top;
+ xs.push(x);
+ ys.push(y);
+ scaleXs.push(scaleX);
+ scaleYs.push(scaleY);
let dragElement = ele.cloneNode(true) as HTMLElement;
dragElement.style.opacity = "0.7";
dragElement.style.position = "absolute";
+ dragElement.style.margin = "0";
+ dragElement.style.top = "0";
dragElement.style.bottom = "";
- dragElement.style.left = "";
+ 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`;
- // bcz: PDFs don't show up if you clone them because they contain a canvas.
+ // bcz: if PDFs are rendered with svg's, then this code isn't needed
+ // bcz: PDFs don't show up if you clone them when rendered using a canvas.
// however, PDF's have a thumbnail field that contains an image of their canvas.
// So we replace the pdf's canvas with the image thumbnail
- if (docs.length) {
- var pdfBox = dragElement.getElementsByClassName("pdfBox-cont")[0] as HTMLElement;
- let thumbnail = docs[0].GetT(KeyStore.Thumbnail, ImageField);
- if (pdfBox && pdfBox.childElementCount && thumbnail) {
- let img = new Image();
- img!.src = thumbnail.toString();
- img!.style.position = "absolute";
- img!.style.width = `${rect.width / scaleX}px`;
- img!.style.height = `${rect.height / scaleY}px`;
- pdfBox.replaceChild(img!, pdfBox.children[0])
- }
- }
+ // if (docs.length) {
+ // var pdfBox = dragElement.getElementsByClassName("pdfBox-cont")[0] as HTMLElement;
+ // let thumbnail = docs[0].GetT(KeyStore.Thumbnail, ImageField);
+ // if (pdfBox && pdfBox.childElementCount && thumbnail) {
+ // let img = new Image();
+ // img.src = thumbnail.toString();
+ // img.style.position = "absolute";
+ // img.style.width = `${rect.width / scaleX}px`;
+ // img.style.height = `${rect.height / scaleY}px`;
+ // pdfBox.replaceChild(img, pdfBox.children[0])
+ // }
+ // }
dragDiv.appendChild(dragElement);
return dragElement;
@@ -187,26 +239,42 @@ export namespace DragManager {
hideSource = options.hideSource();
}
}
- eles.map(ele => ele.hidden = hideSource);
+ eles.map(ele => (ele.hidden = hideSource));
+ let lastX = downX;
+ let lastY = downY;
const moveHandler = (e: PointerEvent) => {
e.stopPropagation();
e.preventDefault();
- if (dragData instanceof DocumentDragData)
+ if (dragData instanceof DocumentDragData) {
dragData.aliasOnDrop = e.ctrlKey || e.altKey;
+ }
if (e.shiftKey) {
abortDrag();
- CollectionDockingView.Instance.StartOtherDrag(docs, { pageX: e.pageX, pageY: e.pageY, preventDefault: () => { }, button: 0 });
+ CollectionDockingView.Instance.StartOtherDrag(docs, {
+ pageX: e.pageX,
+ pageY: e.pageY,
+ preventDefault: emptyFunction,
+ button: 0
+ });
}
- dragElements.map((dragElement, i) => dragElement.style.transform = `translate(${xs[i] += e.movementX}px, ${ys[i] += e.movementY}px) scale(${scaleXs[i]}, ${scaleYs[i]})`);
+ //TODO: Why can't we use e.movementX and e.movementY?
+ let moveX = e.pageX - lastX;
+ let moveY = e.pageY - lastY;
+ lastX = e.pageX;
+ lastY = e.pageY;
+ dragElements.map((dragElement, i) => (dragElement.style.transform =
+ `translate(${(xs[i] += moveX)}px, ${(ys[i] += moveY)}px)
+ scale(${scaleXs[i]}, ${scaleYs[i]})`)
+ );
};
const abortDrag = () => {
document.removeEventListener("pointermove", moveHandler, true);
document.removeEventListener("pointerup", upHandler);
dragElements.map(dragElement => dragDiv.removeChild(dragElement));
- eles.map(ele => ele.hidden = false);
- }
+ eles.map(ele => (ele.hidden = false));
+ };
const upHandler = (e: PointerEvent) => {
abortDrag();
FinishDrag(eles, e, dragData, options, finishDrag);
@@ -218,29 +286,28 @@ export namespace DragManager {
function FinishDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (dragData: { [index: string]: any }) => void) {
let removed = dragEles.map(dragEle => {
let parent = dragEle.parentElement;
- if (parent)
- parent.removeChild(dragEle);
+ if (parent) parent.removeChild(dragEle);
return [dragEle, parent];
});
const target = document.elementFromPoint(e.x, e.y);
removed.map(r => {
- let dragEle: HTMLElement = r[0]!;
- let parent: HTMLElement | null = r[1];
- if (parent)
- parent.appendChild(dragEle);
+ let dragEle = r[0];
+ let parent = r[1];
+ if (parent && dragEle) parent.appendChild(dragEle);
});
if (target) {
- if (finishDrag)
- finishDrag(dragData);
-
- target.dispatchEvent(new CustomEvent<DropEvent>("dashOnDrop", {
- bubbles: true,
- detail: {
- x: e.x,
- y: e.y,
- data: dragData
- }
- }));
+ if (finishDrag) finishDrag(dragData);
+
+ target.dispatchEvent(
+ new CustomEvent<DropEvent>("dashOnDrop", {
+ bubbles: true,
+ detail: {
+ x: e.x,
+ y: e.y,
+ data: dragData
+ }
+ })
+ );
if (options) {
options.handlers.dragComplete({});
@@ -248,4 +315,4 @@ export namespace DragManager {
}
DocumentDecorations.Instance.Hidden = false;
}
-} \ No newline at end of file
+}
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 2a3c1da6e..92944bec0 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -1,13 +1,13 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Schema, NodeSpec, MarkSpec, DOMOutputSpecArray, NodeType } from "prosemirror-model"
-import { joinUp, lift, setBlockType, toggleMark, wrapIn } from 'prosemirror-commands'
-import { redo, undo } from 'prosemirror-history'
-import { orderedList, bulletList, listItem, } from 'prosemirror-schema-list'
+import { Schema, NodeSpec, MarkSpec, DOMOutputSpecArray, NodeType } from "prosemirror-model";
+import { joinUp, lift, setBlockType, toggleMark, wrapIn } from 'prosemirror-commands';
+import { redo, undo } from 'prosemirror-history';
+import { orderedList, bulletList, listItem, } from 'prosemirror-schema-list';
import { EditorState, Transaction, NodeSelection, } from "prosemirror-state";
import { EditorView, } from "prosemirror-view";
const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
- preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]
+ preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
// :: Object
@@ -24,7 +24,7 @@ export const nodes: { [index: string]: NodeSpec } = {
content: "inline*",
group: "block",
parseDOM: [{ tag: "p" }],
- toDOM() { return pDOM }
+ toDOM() { return pDOM; }
},
// :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks.
@@ -33,14 +33,14 @@ export const nodes: { [index: string]: NodeSpec } = {
group: "block",
defining: true,
parseDOM: [{ tag: "blockquote" }],
- toDOM() { return blockquoteDOM }
+ toDOM() { return blockquoteDOM; }
},
// :: NodeSpec A horizontal rule (`<hr>`).
horizontal_rule: {
group: "block",
parseDOM: [{ tag: "hr" }],
- toDOM() { return hrDOM }
+ toDOM() { return hrDOM; }
},
// :: NodeSpec A heading textblock, with a `level` attribute that
@@ -57,7 +57,7 @@ export const nodes: { [index: string]: NodeSpec } = {
{ tag: "h4", attrs: { level: 4 } },
{ tag: "h5", attrs: { level: 5 } },
{ tag: "h6", attrs: { level: 6 } }],
- toDOM(node: any) { return ["h" + node.attrs.level, 0] }
+ toDOM(node: any) { return ["h" + node.attrs.level, 0]; }
},
// :: NodeSpec A code listing. Disallows marks or non-text inline
@@ -70,7 +70,7 @@ export const nodes: { [index: string]: NodeSpec } = {
code: true,
defining: true,
parseDOM: [{ tag: "pre", preserveWhitespace: "full" }],
- toDOM() { return preDOM }
+ toDOM() { return preDOM; }
},
// :: NodeSpec The text node.
@@ -96,10 +96,10 @@ export const nodes: { [index: string]: NodeSpec } = {
src: dom.getAttribute("src"),
title: dom.getAttribute("title"),
alt: dom.getAttribute("alt")
- }
+ };
}
}],
- toDOM(node: any) { return ["img", node.attrs] }
+ toDOM(node: any) { return ["img", node.attrs]; }
},
// :: NodeSpec A hard line break, represented in the DOM as `<br>`.
@@ -108,7 +108,7 @@ export const nodes: { [index: string]: NodeSpec } = {
group: "inline",
selectable: false,
parseDOM: [{ tag: "br" }],
- toDOM() { return brDOM }
+ toDOM() { return brDOM; }
},
ordered_list: {
@@ -136,7 +136,7 @@ export const nodes: { [index: string]: NodeSpec } = {
...listItem,
content: 'paragraph block*'
}
-}
+};
const emDOM: DOMOutputSpecArray = ["em", 0];
const strongDOM: DOMOutputSpecArray = ["strong", 0];
@@ -156,17 +156,17 @@ export const marks: { [index: string]: MarkSpec } = {
inclusive: false,
parseDOM: [{
tag: "a[href]", getAttrs(dom: any) {
- return { href: dom.getAttribute("href"), title: dom.getAttribute("title") }
+ return { href: dom.getAttribute("href"), title: dom.getAttribute("title") };
}
}],
- toDOM(node: any) { return ["a", node.attrs, 0] }
+ toDOM(node: any) { return ["a", node.attrs, 0]; }
},
// :: MarkSpec An emphasis mark. Rendered as an `<em>` element.
// Has parse rules that also match `<i>` and `font-style: italic`.
em: {
parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style=italic" }],
- toDOM() { return emDOM }
+ toDOM() { return emDOM; }
},
// :: MarkSpec A strong mark. Rendered as `<strong>`, parse rules
@@ -175,7 +175,7 @@ export const marks: { [index: string]: MarkSpec } = {
parseDOM: [{ tag: "strong" },
{ tag: "b" },
{ style: "font-weight" }],
- toDOM() { return strongDOM }
+ toDOM() { return strongDOM; }
},
underline: {
@@ -221,9 +221,9 @@ export const marks: { [index: string]: MarkSpec } = {
// :: MarkSpec Code font mark. Represented as a `<code>` element.
code: {
parseDOM: [{ tag: "code" }],
- toDOM() { return codeDOM }
+ toDOM() { return codeDOM; }
}
-}
+};
// :: Schema
// This schema rougly corresponds to the document schema used by
@@ -233,4 +233,4 @@ export const marks: { [index: string]: MarkSpec } = {
//
// To reuse elements from this schema, extend or read from its
// `spec.nodes` and `spec.marks` [properties](#model.Schema.spec).
-export const schema = new Schema({ nodes, marks }) \ No newline at end of file
+export const schema = new Schema({ nodes, marks }); \ No newline at end of file
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 4e97b9401..c67cc067a 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -14,45 +14,68 @@ import { ListField } from "../../fields/ListField";
// import * as typescriptes5 from '!!raw-loader!../../../node_modules/typescript/lib/lib.es5.d.ts'
// @ts-ignore
-import * as typescriptlib from '!!raw-loader!./type_decls.d'
+import * as typescriptlib from '!!raw-loader!./type_decls.d';
import { Documents } from "../documents/Documents";
import { Key } from "../../fields/Key";
+export interface ScriptSucccess {
+ success: true;
+ result: any;
+}
+
+export interface ScriptError {
+ success: false;
+ error: any;
+}
-export interface ExecutableScript {
- (): any;
+export type ScriptResult = ScriptSucccess | ScriptError;
+
+export interface CompiledScript {
+ readonly compiled: true;
+ readonly originalScript: string;
+ readonly options: Readonly<ScriptOptions>;
+ run(args?: { [name: string]: any }): ScriptResult;
+}
- compiled: boolean;
+export interface CompileError {
+ compiled: false;
+ errors: any[];
}
-function Compile(script: string | undefined, diagnostics: Opt<any[]>, scope: { [name: string]: any }): ExecutableScript {
- const compiled = !(diagnostics && diagnostics.some(diag => diag.category == ts.DiagnosticCategory.Error));
+export type CompileResult = CompiledScript | CompileError;
- let func: () => Opt<Field>;
- if (compiled && script) {
- let fieldTypes = [Document, NumberField, TextField, ImageField, RichTextField, ListField, Key];
- let paramNames = ["KeyStore", "Documents", ...fieldTypes.map(fn => fn.name)];
- let params: any[] = [KeyStore, Documents, ...fieldTypes]
- for (let prop in scope) {
- if (prop === "this") {
+function Run(script: string | undefined, customParams: string[], diagnostics: any[], originalScript: string, options: ScriptOptions): CompileResult {
+ const errors = diagnostics.some(diag => diag.category === ts.DiagnosticCategory.Error);
+ if (errors || !script) {
+ return { compiled: false, errors: diagnostics };
+ }
+
+ let fieldTypes = [Document, NumberField, TextField, ImageField, RichTextField, ListField, Key];
+ 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;
}
- paramNames.push(prop);
- params.push(scope[prop]);
+ if (name in args) {
+ argsArray.push(args[name]);
+ } else {
+ argsArray.push(capturedVariables[name]);
+ }
}
- let thisParam = scope["this"];
- let compiledFunction = new Function(...paramNames, script);
- func = function (): Opt<Field> {
- return compiledFunction.apply(thisParam, params)
- };
- } else {
- func = () => undefined;
- }
-
- return Object.assign(func,
- {
- compiled
- });
+ let thisParam = args.this || capturedVariables.this;
+ try {
+ const result = compiledFunction.apply(thisParam, params).apply(thisParam, argsArray);
+ return { success: true, result };
+ } catch (error) {
+ return { success: false, error };
+ }
+ };
+ return { compiled: true, run, originalScript, options };
}
interface File {
@@ -74,14 +97,14 @@ class ScriptingCompilerHost {
}
// getDefaultLibFileName(options: ts.CompilerOptions): string {
getDefaultLibFileName(options: any): string {
- return 'node_modules/typescript/lib/lib.d.ts' // No idea what this means...
+ return 'node_modules/typescript/lib/lib.d.ts'; // No idea what this means...
}
writeFile(fileName: string, content: string) {
const file = this.files.find(file => file.fileName === fileName);
if (file) {
file.content = content;
} else {
- this.files.push({ fileName, content })
+ this.files.push({ fileName, content });
}
}
getCurrentDirectory(): string {
@@ -108,26 +131,56 @@ class ScriptingCompilerHost {
}
}
-export function CompileScript(script: string, scope?: { [name: string]: any }, addReturn: boolean = false): ExecutableScript {
+export interface ScriptOptions {
+ requiredType?: string;
+ addReturn?: boolean;
+ params?: { [name: string]: string };
+ capturedVariables?: { [name: string]: Field };
+}
+
+export function CompileScript(script: string, options: ScriptOptions = {}): CompileResult {
+ const { requiredType = "", addReturn = false, params = {}, capturedVariables = {} } = options;
let host = new ScriptingCompilerHost;
- let funcScript = `(function() {
+ let paramNames: string[] = [];
+ if ("this" in params || "this" in capturedVariables) {
+ paramNames.push("this");
+ }
+ for (const key in params) {
+ if (key === "this") continue;
+ paramNames.push(key);
+ }
+ let paramList = paramNames.map(key => {
+ const val = params[key];
+ return `${key}: ${val}`;
+ });
+ 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}
- }).apply(this)`
+ })`;
host.writeFile("file.ts", funcScript);
host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib);
let program = ts.createProgram(["file.ts"], {}, host);
let testResult = program.emit();
- let outputText = "return " + host.readFile("file.js");
+ let outputText = host.readFile("file.js");
let diagnostics = ts.getPreEmitDiagnostics(program).concat(testResult.diagnostics);
- return Compile(outputText, diagnostics, scope || {});
+ return Run(outputText, paramNames, diagnostics, script, options);
+}
+
+export function OrLiteralType(returnType: string): string {
+ return `${returnType} | string | number`;
}
export function ToField(data: any): Opt<Field> {
- if (typeof data == "string") {
+ if (typeof data === "string") {
return new TextField(data);
- } else if (typeof data == "number") {
+ } else if (typeof data === "number") {
return new NumberField(data);
}
return undefined;
diff --git a/src/client/util/ScrollBox.tsx b/src/client/util/ScrollBox.tsx
index b6b088170..a209874a3 100644
--- a/src/client/util/ScrollBox.tsx
+++ b/src/client/util/ScrollBox.tsx
@@ -1,4 +1,4 @@
-import React = require("react")
+import React = require("react");
export class ScrollBox extends React.Component {
onWheel = (e: React.WheelEvent) => {
@@ -16,6 +16,6 @@ export class ScrollBox extends React.Component {
}} onWheel={this.onWheel}>
{this.props.children}
</div>
- )
+ );
}
} \ No newline at end of file
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 1354e32e1..c56f6a4ff 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,6 +1,8 @@
import { observable, action } from "mobx";
import { DocumentView } from "../views/nodes/DocumentView";
-import { Document } from "../../fields/Document"
+import { Document } from "../../fields/Document";
+import { Main } from "../views/Main";
+import { MainOverlayTextBox } from "../views/MainOverlayTextBox";
export namespace SelectionManager {
class Manager {
@@ -11,19 +13,27 @@ export namespace SelectionManager {
SelectDoc(doc: DocumentView, ctrlPressed: boolean): void {
// if doc is not in SelectedDocuments, add it
if (!ctrlPressed) {
- manager.SelectedDocuments = [];
+ this.DeselectAll();
}
if (manager.SelectedDocuments.indexOf(doc) === -1) {
- manager.SelectedDocuments.push(doc)
+ manager.SelectedDocuments.push(doc);
+ doc.props.onActiveChanged(true);
}
}
+
+ @action
+ DeselectAll(): void {
+ manager.SelectedDocuments.map(dv => dv.props.onActiveChanged(false));
+ manager.SelectedDocuments = [];
+ MainOverlayTextBox.Instance.SetTextDoc();
+ }
}
- const manager = new Manager;
+ const manager = new Manager();
export function SelectDoc(doc: DocumentView, ctrlPressed: boolean): void {
- manager.SelectDoc(doc, ctrlPressed)
+ manager.SelectDoc(doc, ctrlPressed);
}
export function IsSelected(doc: DocumentView): boolean {
@@ -33,18 +43,16 @@ export namespace SelectionManager {
export function DeselectAll(except?: Document): void {
let found: DocumentView | undefined = undefined;
if (except) {
- for (let i = 0; i < manager.SelectedDocuments.length; i++) {
- let view = manager.SelectedDocuments[i];
- if (view.props.Document == except)
- found = view;
+ for (const view of manager.SelectedDocuments) {
+ if (view.props.Document === except) found = view;
}
}
- manager.SelectedDocuments.length = 0;
- if (found)
- manager.SelectedDocuments.push(found);
+
+ manager.DeselectAll();
+ if (found) manager.SelectDoc(found, false);
}
export function SelectedDocuments(): Array<DocumentView> {
return manager.SelectedDocuments;
}
-} \ No newline at end of file
+}
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 2a613ba8b..7951e5686 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -5,135 +5,137 @@ import { keymap } from "prosemirror-keymap";
import { EditorState, Transaction, NodeSelection } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { schema } from "./RichTextSchema";
-import { Schema, NodeType } from "prosemirror-model"
-import React = require("react")
+import { Schema, NodeType } from "prosemirror-model";
+import React = require("react");
import "./TooltipTextMenu.scss";
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 { 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;
-
- constructor(view: EditorView) {
- this.tooltip = document.createElement("div");
- this.tooltip.className = "tooltipMenu";
-
- //add the div which is the tooltip
- view.dom.parentNode!.appendChild(this.tooltip);
-
- //add additional icons
- library.add(faListUl);
-
- //add the buttons to the tooltip
- let items = [
- { command: toggleMark(schema.marks.strong), dom: this.icon("B", "strong") },
- { command: toggleMark(schema.marks.em), dom: this.icon("i", "em") },
- { command: toggleMark(schema.marks.underline), dom: this.icon("U", "underline") },
- { command: toggleMark(schema.marks.strikethrough), dom: this.icon("S", "strikethrough") },
- { command: toggleMark(schema.marks.superscript), dom: this.icon("s", "superscript") },
- { command: toggleMark(schema.marks.subscript), dom: this.icon("s", "subscript") },
- //this doesn't work currently - look into notion of active block
- { command: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") },
- ]
- items.forEach(({ dom }) => this.tooltip.appendChild(dom));
-
- //pointer down handler to activate button effects
- this.tooltip.addEventListener("pointerdown", e => {
- e.preventDefault();
- view.focus();
- items.forEach(({ command, dom }) => {
- if (dom.contains(e.srcElement)) {
- let active = command(view.state, view.dispatch, view);
- //uncomment this if we want the bullet button to disappear if current selection is bulleted
- // dom.style.display = active ? "" : "none"
+ private tooltip: HTMLElement;
+ private editorProps: FieldViewProps;
+
+ 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!.parentNode!.appendChild(this.tooltip);
+
+ //add additional icons
+ library.add(faListUl);
+
+ //add the buttons to the tooltip
+ let items = [
+ { command: toggleMark(schema.marks.strong), dom: this.icon("B", "strong") },
+ { command: toggleMark(schema.marks.em), dom: this.icon("i", "em") },
+ { command: toggleMark(schema.marks.underline), dom: this.icon("U", "underline") },
+ { command: toggleMark(schema.marks.strikethrough), dom: this.icon("S", "strikethrough") },
+ { command: toggleMark(schema.marks.superscript), dom: this.icon("s", "superscript") },
+ { command: toggleMark(schema.marks.subscript), dom: this.icon("s", "subscript") },
+ //this doesn't work currently - look into notion of active block
+ { command: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") },
+ ];
+ items.forEach(({ dom }) => this.tooltip.appendChild(dom));
+
+ //pointer down handler to activate button effects
+ this.tooltip.addEventListener("pointerdown", e => {
+ e.preventDefault();
+ view.focus();
+ items.forEach(({ command, dom }) => {
+ if (dom.contains(e.target as Node)) {
+ let active = command(view.state, view.dispatch, view);
+ //uncomment this if we want the bullet button to disappear if current selection is bulleted
+ // dom.style.display = active ? "" : "none"
+ }
+ });
+ });
+
+ this.update(view, undefined);
+ }
+
+ // Helper function to create menu icons
+ icon(text: string, name: string) {
+ let span = document.createElement("span");
+ span.className = "menuicon " + name;
+ span.title = name;
+ span.textContent = text;
+ return span;
+ }
+
+ //adapted this method - use it to check if block has a tag (ie bulleting)
+ blockActive(type: NodeType<Schema<string, string>>, state: EditorState) {
+ let attrs = {};
+
+ if (state.selection instanceof NodeSelection) {
+ const sel: NodeSelection = state.selection;
+ let $from = sel.$from;
+ let to = sel.to;
+ let node = sel.node;
+
+ if (node) {
+ return node.hasMarkup(type, attrs);
+ }
+
+ return to <= $from.end() && $from.parent.hasMarkup(type, attrs);
}
- })
- })
-
- this.update(view, undefined);
- }
-
- // Helper function to create menu icons
- icon(text: string, name: string) {
- let span = document.createElement("span");
- span.className = "menuicon " + name;
- span.title = name;
- span.textContent = text;
- return span;
- }
-
- //adapted this method - use it to check if block has a tag (ie bulleting)
- blockActive(type: NodeType<Schema<string, string>>, state: EditorState) {
- let attrs = {};
-
- if (state.selection instanceof NodeSelection) {
- const sel: NodeSelection = state.selection;
- let $from = sel.$from;
- let to = sel.to;
- let node = sel.node;
-
- if (node) {
- return node.hasMarkup(type, attrs);
- }
-
- return to <= $from.end() && $from.parent.hasMarkup(type, attrs);
}
- }
-
- //this doesn't currently work but could be used to use icons for buttons
- unorderedListIcon(): HTMLSpanElement {
- let span = document.createElement("span");
- let icon = document.createElement("FontAwesomeIcon");
- icon.className = "menuicon fa fa-smile-o";
- span.appendChild(icon);
- return span;
- }
-
- // Create an icon for a heading at the given level
- heading(level: number) {
- return {
- command: setBlockType(schema.nodes.heading, { level }),
- dom: this.icon("H" + level, "heading")
+
+ //this doesn't currently work but could be used to use icons for buttons
+ unorderedListIcon(): HTMLSpanElement {
+ let span = document.createElement("span");
+ let icon = document.createElement("FontAwesomeIcon");
+ icon.className = "menuicon fa fa-smile-o";
+ span.appendChild(icon);
+ return span;
+ }
+
+ // Create an icon for a heading at the given level
+ heading(level: number) {
+ return {
+ command: setBlockType(schema.nodes.heading, { level }),
+ dom: this.icon("H" + level, "heading")
+ };
}
- }
-
- //updates the tooltip menu when the selection changes
- update(view: EditorView, lastState: EditorState | undefined) {
- let state = view.state
- // Don't do anything if the document/selection didn't change
- if (lastState && lastState.doc.eq(state.doc) &&
- lastState.selection.eq(state.selection)) return
-
- // Hide the tooltip if the selection is empty
- if (state.selection.empty) {
- this.tooltip.style.display = "none"
- return
+
+ //updates the tooltip menu when the selection changes
+ update(view: EditorView, lastState: EditorState | undefined) {
+ let state = view.state;
+ // Don't do anything if the document/selection didn't change
+ if (lastState && lastState.doc.eq(state.doc) &&
+ lastState.selection.eq(state.selection)) return;
+
+ // Hide the tooltip if the selection is empty
+ if (state.selection.empty) {
+ this.tooltip.style.display = "none";
+ return;
+ }
+
+ // Otherwise, reposition it and update its content
+ this.tooltip.style.display = "";
+ let { from, to } = state.selection;
+ let start = view.coordsAtPos(from), end = view.coordsAtPos(to);
+ // The box in which the tooltip is positioned, to use as base
+ let box = this.tooltip.offsetParent!.getBoundingClientRect();
+ // 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) * 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) * this.editorProps.ScreenToLocalTransform().Scale + "px";
}
- // Otherwise, reposition it and update its content
- this.tooltip.style.display = ""
- let { from, to } = state.selection
- let start = view.coordsAtPos(from), end = view.coordsAtPos(to)
- // The box in which the tooltip is positioned, to use as base
- let box = this.tooltip.offsetParent!.getBoundingClientRect()
- // 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;
- 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";
- }
-
- destroy() { this.tooltip.remove() }
+ destroy() { this.tooltip.remove(); }
} \ No newline at end of file
diff --git a/src/client/util/Transform.ts b/src/client/util/Transform.ts
index 3e1039166..e9170ec36 100644
--- a/src/client/util/Transform.ts
+++ b/src/client/util/Transform.ts
@@ -3,7 +3,7 @@ export class Transform {
private _translateY: number = 0;
private _scale: number = 1;
- static get Identity(): Transform {
+ static Identity(): Transform {
return new Transform(0, 0, 1);
}
@@ -17,78 +17,64 @@ export class Transform {
this._scale = scale;
}
- translate = (x: number, y: number): Transform => {
+ translate = (x: number, y: number): this => {
this._translateX += x;
this._translateY += y;
return this;
}
- scale = (scale: number): Transform => {
+ scale = (scale: number): this => {
this._scale *= scale;
this._translateX *= scale;
this._translateY *= scale;
return this;
}
- scaleAbout = (scale: number, x: number, y: number): Transform => {
+ scaleAbout = (scale: number, x: number, y: number): this => {
this._translateX += x * this._scale - x * this._scale * scale;
this._translateY += y * this._scale - y * this._scale * scale;
this._scale *= scale;
return this;
}
- transform = (transform: Transform): Transform => {
+ transform = (transform: Transform): this => {
this._translateX = transform._translateX + transform._scale * this._translateX;
this._translateY = transform._translateY + transform._scale * this._translateY;
this._scale *= transform._scale;
return this;
}
- preTranslate = (x: number, y: number): Transform => {
+ preTranslate = (x: number, y: number): this => {
this._translateX += this._scale * x;
this._translateY += this._scale * y;
return this;
}
- preScale = (scale: number): Transform => {
+ preScale = (scale: number): this => {
this._scale *= scale;
return this;
}
- preTransform = (transform: Transform): Transform => {
+ preTransform = (transform: Transform): this => {
this._translateX += transform._translateX * this._scale;
this._translateY += transform._translateY * this._scale;
this._scale *= transform._scale;
return this;
}
- translated = (x: number, y: number): Transform => {
- return this.copy().translate(x, y);
- }
+ translated = (x: number, y: number): Transform => this.copy().translate(x, y);
- preTranslated = (x: number, y: number): Transform => {
- return this.copy().preTranslate(x, y);
- }
+ preTranslated = (x: number, y: number): Transform => this.copy().preTranslate(x, y);
- scaled = (scale: number): Transform => {
- return this.copy().scale(scale);
- }
+ scaled = (scale: number): Transform => this.copy().scale(scale);
- scaledAbout = (scale: number, x: number, y: number): Transform => {
- return this.copy().scaleAbout(scale, x, y);
- }
+ scaledAbout = (scale: number, x: number, y: number): Transform => this.copy().scaleAbout(scale, x, y);
- preScaled = (scale: number): Transform => {
- return this.copy().preScale(scale);
- }
+ preScaled = (scale: number): Transform => this.copy().preScale(scale);
- transformed = (transform: Transform): Transform => {
- return this.copy().transform(transform);
- }
+ transformed = (transform: Transform): Transform => this.copy().transform(transform);
- preTransformed = (transform: Transform): Transform => {
- return this.copy().preTransform(transform);
- }
+ preTransformed = (transform: Transform): Transform => this.copy().preTransform(transform);
transformPoint = (x: number, y: number): [number, number] => {
x *= this._scale;
@@ -98,9 +84,7 @@ export class Transform {
return [x, y];
}
- transformDirection = (x: number, y: number): [number, number] => {
- return [x * this._scale, y * this._scale];
- }
+ transformDirection = (x: number, y: number): [number, number] => [x * this._scale, y * this._scale];
transformBounds(x: number, y: number, width: number, height: number): { x: number, y: number, width: number, height: number } {
[x, y] = this.transformPoint(x, y);
@@ -108,12 +92,8 @@ export class Transform {
return { x, y, width, height };
}
- inverse = () => {
- return new Transform(-this._translateX / this._scale, -this._translateY / this._scale, 1 / this._scale)
- }
+ inverse = () => new Transform(-this._translateX / this._scale, -this._translateY / this._scale, 1 / this._scale);
- copy = () => {
- return new Transform(this._translateX, this._translateY, this._scale);
- }
+ copy = () => new Transform(this._translateX, this._translateY, this._scale);
} \ No newline at end of file
diff --git a/src/client/util/TypedEvent.ts b/src/client/util/TypedEvent.ts
index 0714a7f5c..532ba78eb 100644
--- a/src/client/util/TypedEvent.ts
+++ b/src/client/util/TypedEvent.ts
@@ -36,7 +36,5 @@ export class TypedEvent<T> {
this.listenersOncer = [];
}
- pipe = (te: TypedEvent<T>): Disposable => {
- return this.on((e) => te.emit(e));
- }
+ pipe = (te: TypedEvent<T>): Disposable => this.on((e) => te.emit(e));
} \ No newline at end of file
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index 6d1b2f1b8..27aed4bac 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -1,6 +1,7 @@
import { observable, action } from "mobx";
-import 'source-map-support/register'
+import 'source-map-support/register';
import { Without } from "../../Utils";
+import { string } from "prop-types";
function getBatchName(target: any, key: string | symbol): string {
let keyName = key.toString();
@@ -30,11 +31,24 @@ function propertyDecorator(target: any, key: string | symbol) {
batch.end();
}
}
- })
+ });
}
- })
+ });
}
-export function undoBatch(target: any, key: string | symbol, descriptor?: TypedPropertyDescriptor<any>): any {
+
+export function undoBatch(target: any, key: string | symbol, descriptor?: TypedPropertyDescriptor<any>): any;
+export function undoBatch(fn: (...args: any[]) => any): (...args: any[]) => any;
+export function undoBatch(target: any, key?: string | symbol, descriptor?: TypedPropertyDescriptor<any>): any {
+ if (!key) {
+ return function () {
+ let batch = UndoManager.StartBatch("");
+ try {
+ return target.apply(undefined, arguments);
+ } finally {
+ batch.end();
+ }
+ };
+ }
if (!descriptor) {
propertyDecorator(target, key);
return;
@@ -44,11 +58,11 @@ export function undoBatch(target: any, key: string | symbol, descriptor?: TypedP
descriptor.value = function (...args: any[]) {
let batch = UndoManager.StartBatch(getBatchName(target, key));
try {
- return oldFunction.apply(this, args)
+ return oldFunction.apply(this, args);
} finally {
batch.end();
}
- }
+ };
return descriptor;
}
@@ -84,6 +98,9 @@ export namespace UndoManager {
export function GetOpenBatches(): Without<Batch, 'end'>[] {
return openBatches;
}
+ export function TraceOpenBatches() {
+ console.log(`Open batches:\n\t${openBatches.map(batch => batch.batchName).join("\n\t")}\n`);
+ }
export class Batch {
private disposed: boolean = false;
@@ -100,8 +117,8 @@ export namespace UndoManager {
EndBatch(cancel);
}
- end = () => { this.dispose(false); }
- cancel = () => { this.dispose(true); }
+ end = () => { this.dispose(false); };
+ cancel = () => { this.dispose(true); };
}
export function StartBatch(batchName: string): Batch {
@@ -121,12 +138,15 @@ export namespace UndoManager {
redoStack.length = 0;
currentBatch = undefined;
}
- })
+ });
export function RunInBatch(fn: () => void, batchName: string) {
let batch = StartBatch(batchName);
- fn();
- batch.end();
+ try {
+ fn();
+ } finally {
+ batch.end();
+ }
}
export const Undo = action(() => {
@@ -146,7 +166,7 @@ export namespace UndoManager {
undoing = false;
redoStack.push(commands);
- })
+ });
export const Redo = action(() => {
if (redoStack.length === 0) {
@@ -159,12 +179,12 @@ export namespace UndoManager {
}
undoing = true;
- for (let i = 0; i < commands.length; i++) {
- commands[i].redo();
+ for (const command of commands) {
+ command.redo();
}
undoing = false;
undoStack.push(commands);
- })
+ });
} \ No newline at end of file
diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d
index 4f69053b1..47c3481b2 100644
--- a/src/client/util/type_decls.d
+++ b/src/client/util/type_decls.d
@@ -181,7 +181,7 @@ declare class Key extends Field {
Copy(): Field;
ToScriptString(): string;
}
-declare type FIELD_WAITING = "<Waiting>";
+declare type FIELD_WAITING = null;
declare type Opt<T> = T | undefined;
declare type FieldValue<T> = Opt<T> | FIELD_WAITING;
// @ts-ignore
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/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index cfa8ea7b7..615a928ad 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -2,11 +2,11 @@ import React = require("react");
import { ContextMenuItem, ContextMenuProps } from "./ContextMenuItem";
import { observable, action } from "mobx";
import { observer } from "mobx-react";
-import "./ContextMenu.scss"
+import "./ContextMenu.scss";
@observer
export class ContextMenu extends React.Component {
- static Instance: ContextMenu
+ static Instance: ContextMenu;
@observable private _items: Array<ContextMenuProps> = [{ description: "test", event: (e: React.MouseEvent) => e.preventDefault() }];
@observable private _pageX: number = 0;
@@ -22,15 +22,15 @@ export class ContextMenu extends React.Component {
constructor(props: Readonly<{}>) {
super(props);
- this.ref = React.createRef()
+ this.ref = React.createRef();
ContextMenu.Instance = this;
}
@action
clearItems() {
- this._items = []
- this._display = "none"
+ this._items = [];
+ this._display = "none";
}
@action
@@ -56,7 +56,7 @@ export class ContextMenu extends React.Component {
this._searchString = "";
- this._display = "flex"
+ this._display = "flex";
}
intersects = (x: number, y: number): boolean => {
@@ -86,7 +86,7 @@ export class ContextMenu extends React.Component {
{this._items.filter(prop => prop.description.toLowerCase().indexOf(this._searchString.toLowerCase()) !== -1).
map(prop => <ContextMenuItem {...prop} key={prop.description} />)}
</div>
- )
+ );
}
@action
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 4801c1555..70813f0dd 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -11,7 +11,7 @@ export interface SubmenuProps {
}
export interface ContextMenuItemProps {
- type: ContextMenuProps | SubmenuProps
+ type: ContextMenuProps | SubmenuProps;
}
export class ContextMenuItem extends React.Component<ContextMenuProps> {
@@ -20,6 +20,6 @@ export class ContextMenuItem extends React.Component<ContextMenuProps> {
<div className="contextMenu-item" onClick={this.props.event}>
<div className="contextMenu-description">{this.props.description}</div>
</div>
- )
+ );
}
} \ No newline at end of file
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 7a43f3087..c1a949639 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -1,76 +1,103 @@
-@import "global_variables";
+@import "globalCssVariables";
+
+.documentDecorations {
+ position: absolute;
+}
#documentDecorations-container {
position: absolute;
+ top: 0;
+ left:0;
display: grid;
- z-index: 1000;
- grid-template-rows: 8px 1fr 8px 30px;
- grid-template-columns: 8px 1fr 8px;
+ z-index: $docDecorations-zindex;
+ grid-template-rows: 20px 8px 1fr 8px;
+ grid-template-columns: 8px 8px 1fr 8px 8px;
pointer-events: none;
+
#documentDecorations-centerCont {
+ grid-column:3;
background: none;
}
+
.documentDecorations-resizer {
pointer-events: auto;
background: $alt-accent;
opacity: 0.8;
}
+
+ #documentDecorations-topLeftResizer,
+ #documentDecorations-leftResizer,
+ #documentDecorations-bottomLeftResizer {
+ grid-column: 1
+ }
+
+ #documentDecorations-topResizer,
+ #documentDecorations-bottomResizer {
+ grid-column-start: 2;
+ grid-column-end: 5;
+ }
+
+ #documentDecorations-bottomRightResizer,
+ #documentDecorations-topRightResizer,
+ #documentDecorations-rightResizer {
+ grid-column-start:5;
+ grid-column-end:7;
+ }
+
#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;
}
+ .title{
+ width:100%;
+ background: lightblue;
+ grid-column-start:3;
+ grid-column-end: 4;
+ pointer-events: auto;
+ }
}
+.documentDecorations-closeButton {
+ background:$alt-accent;
+ opacity: 0.8;
+ grid-column-start: 4;
+ grid-column-end: 6;
+ pointer-events: all;
+ text-align: center;
+ cursor: pointer;
+}
+.documentDecorations-minimizeButton {
+ background:$alt-accent;
+ opacity: 0.8;
+ grid-column-start: 1;
+ grid-column-end: 3;
+ pointer-events: all;
+ text-align: center;
+ cursor: pointer;
+}
.documentDecorations-background {
- background:lightblue;
+ background: lightblue;
position: absolute;
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
+ grid-column: 1/4;
+ margin-left: 25px;
}
.linkButton-empty:hover {
@@ -85,6 +112,31 @@
cursor: pointer;
}
+.linkButton-linker {
+ position:absolute;
+ bottom:0px;
+ left: 0px;
+ height: 20px;
+ width: 20px;
+ margin-top: 10px;
+ margin-right: 5px;
+ border-radius: 50%;
+ opacity: 0.9;
+ pointer-events: auto;
+ color: $dark-color;
+ border: $dark-color 1px solid;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ font-size: 75%;
+ transition: transform 0.2s;
+ text-align: center;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+.linkButton-tray {
+ grid-column: 1/4;
+}
.linkButton-empty {
height: 20px;
width: 20px;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index faba3b6ea..b97a47a3c 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,12 +1,18 @@
-import { action, computed, observable, trace } from "mobx";
+import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
-import { KeyStore } from '../../fields/KeyStore';
+import { Key } from "../../fields/Key";
+//import ContentEditable from 'react-contenteditable'
+import { KeyStore } from "../../fields/KeyStore";
import { ListField } from "../../fields/ListField";
import { NumberField } from "../../fields/NumberField";
-import { DragManager } from "../util/DragManager";
+import { TextField } from "../../fields/TextField";
+import { emptyFunction } from "../../Utils";
+import { DragLinksAsDocuments, DragManager } from "../util/DragManager";
import { SelectionManager } from "../util/SelectionManager";
-import { CollectionView } from "./collections/CollectionView";
+import { undoBatch } from "../util/UndoManager";
import './DocumentDecorations.scss';
+import { MainOverlayTextBox } from "./MainOverlayTextBox";
+import { DocumentView } from "./nodes/DocumentView";
import { LinkMenu } from "./nodes/LinkMenu";
import React = require("react");
const higflyout = require("@hig/flyout");
@@ -14,24 +20,65 @@ export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@observer
-export class DocumentDecorations extends React.Component {
- static Instance: DocumentDecorations
- private _resizer = ""
+export class DocumentDecorations extends React.Component<{}, { value: string }> {
+ static Instance: DocumentDecorations;
+ private _resizer = "";
private _isPointerDown = false;
-
+ private keyinput: React.RefObject<HTMLInputElement>;
+ private _documents: DocumentView[] = SelectionManager.SelectedDocuments();
private _resizeBorderWidth = 16;
+ private _linkBoxHeight = 30;
+ private _titleHeight = 20;
private _linkButton = React.createRef<HTMLDivElement>();
+ private _linkerButton = React.createRef<HTMLDivElement>();
+ //@observable private _title: string = this._documents[0].props.Document.Title;
+ @observable private _title: string = this._documents.length > 0 ? this._documents[0].props.Document.Title : "";
+ @observable private _fieldKey: Key = KeyStore.Title;
@observable private _hidden = false;
+ @observable private _opacity = 1;
@observable private _dragging = false;
+
constructor(props: Readonly<{}>) {
- super(props)
+ super(props);
+ DocumentDecorations.Instance = this;
+ this.handleChange = this.handleChange.bind(this);
+ this.keyinput = React.createRef();
+ }
- DocumentDecorations.Instance = this
+ @action
+ handleChange = (event: any) => {
+ this._title = event.target.value;
+ }
+
+ @action
+ enterPressed = (e: any) => {
+ var key = e.keyCode || e.which;
+ // enter pressed
+ if (key === 13) {
+ var text = e.target.value;
+ if (text[0] === '#') {
+ let command = text.slice(1, text.length);
+ this._fieldKey = new Key(command);
+ // if (command === "Title" || command === "title") {
+ // this._fieldKey = KeyStore.Title;
+ // }
+ // else if (command === "Width" || command === "width") {
+ // this._fieldKey = KeyStore.Width;
+ // }
+ this._title = "changed";
+ // TODO: Change field with switch statement
+ }
+ else {
+ this._title = "changed";
+ }
+ e.target.blur();
+ }
}
@computed
get Bounds(): { x: number, y: number, b: number, r: number } {
+ this._documents = SelectionManager.SelectedDocuments();
return SelectionManager.SelectedDocuments().reduce((bounds, documentView) => {
if (documentView.props.isTopMost) {
return bounds;
@@ -42,7 +89,7 @@ export class DocumentDecorations extends React.Component {
return {
x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y),
r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
- }
+ };
}, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE });
}
@@ -57,9 +104,11 @@ export class DocumentDecorations extends React.Component {
document.addEventListener("pointermove", this.onBackgroundMove);
document.removeEventListener("pointerup", this.onBackgroundUp);
document.addEventListener("pointerup", this.onBackgroundUp);
- this._lastDrag = [e.clientX, e.clientY]
+ this._lastDrag = [e.clientX, e.clientY];
e.stopPropagation();
- e.preventDefault();
+ if (e.currentTarget.localName !== "input") {
+ e.preventDefault();
+ }
}
@action
@@ -70,21 +119,17 @@ export class DocumentDecorations extends React.Component {
dragData.aliasOnDrop = false;
dragData.xOffset = e.x - left;
dragData.yOffset = e.y - top;
- dragData.removeDocument = (dropCollectionView: CollectionView) =>
- dragData.draggedDocuments.map(d => {
- if (dragDocView.props.RemoveDocument && dragDocView.props.ContainingCollectionView !== dropCollectionView) {
- dragDocView.props.RemoveDocument(d);
- }
- });
+ let move = SelectionManager.SelectedDocuments()[0].props.moveDocument;
+ dragData.moveDocument = move;
this._dragging = true;
document.removeEventListener("pointermove", this.onBackgroundMove);
document.removeEventListener("pointerup", this.onBackgroundUp);
- DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(docView => (docView as any)._mainCont!.current!), dragData, {
+ DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(docView => docView.ContentRef.current!), dragData, e.x, e.y, {
handlers: {
dragComplete: action(() => this._dragging = false),
},
hideSource: true
- })
+ });
e.stopPropagation();
}
@@ -95,6 +140,54 @@ export class DocumentDecorations extends React.Component {
e.preventDefault();
}
+ onCloseDown = (e: React.PointerEvent): void => {
+ e.stopPropagation();
+ if (e.button === 0) {
+ document.removeEventListener("pointermove", this.onCloseMove);
+ document.addEventListener("pointermove", this.onCloseMove);
+ document.removeEventListener("pointerup", this.onCloseUp);
+ document.addEventListener("pointerup", this.onCloseUp);
+ }
+ }
+ onCloseMove = (e: PointerEvent): void => {
+ e.stopPropagation();
+ if (e.button === 0) {
+ }
+ }
+ @undoBatch
+ @action
+ onCloseUp = (e: PointerEvent): void => {
+ e.stopPropagation();
+ if (e.button === 0) {
+ SelectionManager.SelectedDocuments().map(dv => dv.props.removeDocument && dv.props.removeDocument(dv.props.Document));
+ SelectionManager.DeselectAll();
+ document.removeEventListener("pointermove", this.onCloseMove);
+ document.removeEventListener("pointerup", this.onCloseUp);
+ }
+ }
+ onMinimizeDown = (e: React.PointerEvent): void => {
+ e.stopPropagation();
+ if (e.button === 0) {
+ document.removeEventListener("pointermove", this.onMinimizeMove);
+ document.addEventListener("pointermove", this.onMinimizeMove);
+ document.removeEventListener("pointerup", this.onMinimizeUp);
+ document.addEventListener("pointerup", this.onMinimizeUp);
+ }
+ }
+ onMinimizeMove = (e: PointerEvent): void => {
+ e.stopPropagation();
+ if (e.button === 0) {
+ }
+ }
+ onMinimizeUp = (e: PointerEvent): void => {
+ e.stopPropagation();
+ if (e.button === 0) {
+ SelectionManager.SelectedDocuments().map(dv => dv.minimize());
+ document.removeEventListener("pointermove", this.onMinimizeMove);
+ document.removeEventListener("pointerup", this.onMinimizeUp);
+ }
+ }
+
onPointerDown = (e: React.PointerEvent): void => {
e.stopPropagation();
if (e.button === 0) {
@@ -107,43 +200,58 @@ export class DocumentDecorations extends React.Component {
}
}
- onLinkButtonDown = (e: React.PointerEvent): void => {
- // if ()
- // let linkMenu = new LinkMenu(SelectionManager.SelectedDocuments()[0]);
- // linkMenu.Hidden = false;
- console.log("down");
+ onLinkerButtonDown = (e: React.PointerEvent): void => {
+ e.stopPropagation();
+ document.removeEventListener("pointermove", this.onLinkerButtonMoved);
+ document.addEventListener("pointermove", this.onLinkerButtonMoved);
+ document.removeEventListener("pointerup", this.onLinkerButtonUp);
+ document.addEventListener("pointerup", this.onLinkerButtonUp);
+ }
+ onLinkerButtonUp = (e: PointerEvent): void => {
+ document.removeEventListener("pointermove", this.onLinkerButtonMoved);
+ document.removeEventListener("pointerup", this.onLinkerButtonUp);
+ e.stopPropagation();
+ }
+ onLinkerButtonMoved = (e: PointerEvent): void => {
+ if (this._linkerButton.current !== null) {
+ document.removeEventListener("pointermove", this.onLinkerButtonMoved);
+ document.removeEventListener("pointerup", this.onLinkerButtonUp);
+ let dragData = new DragManager.LinkDragData(SelectionManager.SelectedDocuments()[0].props.Document);
+ DragManager.StartLinkDrag(this._linkerButton.current, dragData, e.pageX, e.pageY, {
+ handlers: {
+ dragComplete: action(emptyFunction),
+ },
+ hideSource: false
+ });
+ }
e.stopPropagation();
- document.removeEventListener("pointermove", this.onLinkButtonMoved)
+ }
+
+ onLinkButtonDown = (e: React.PointerEvent): void => {
+ e.stopPropagation();
+ document.removeEventListener("pointermove", this.onLinkButtonMoved);
document.addEventListener("pointermove", this.onLinkButtonMoved);
- document.removeEventListener("pointerup", this.onLinkButtonUp)
+ document.removeEventListener("pointerup", this.onLinkButtonUp);
document.addEventListener("pointerup", this.onLinkButtonUp);
-
}
onLinkButtonUp = (e: PointerEvent): void => {
- document.removeEventListener("pointermove", this.onLinkButtonMoved)
- document.removeEventListener("pointerup", this.onLinkButtonUp)
+ document.removeEventListener("pointermove", this.onLinkButtonMoved);
+ document.removeEventListener("pointerup", this.onLinkButtonUp);
e.stopPropagation();
}
+ onLinkButtonMoved = async (e: PointerEvent) => {
+ if (this._linkButton.current !== null && (e.movementX > 1 || e.movementY > 1)) {
+ document.removeEventListener("pointermove", this.onLinkButtonMoved);
+ document.removeEventListener("pointerup", this.onLinkButtonUp);
- onLinkButtonMoved = (e: PointerEvent): void => {
- if (this._linkButton.current != null) {
- document.removeEventListener("pointermove", this.onLinkButtonMoved)
- document.removeEventListener("pointerup", this.onLinkButtonUp)
- let dragData = new DragManager.LinkDragData(SelectionManager.SelectedDocuments()[0]);
- DragManager.StartLinkDrag(this._linkButton.current, dragData, {
- 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();
@@ -157,41 +265,42 @@ export class DocumentDecorations extends React.Component {
case "":
break;
case "documentDecorations-topLeftResizer":
- dX = -1
- dY = -1
- dW = -(e.movementX)
- dH = -(e.movementY)
+ dX = -1;
+ dY = -1;
+ dW = -(e.movementX);
+ dH = -(e.movementY);
break;
case "documentDecorations-topRightResizer":
- dW = e.movementX
- dY = -1
- dH = -(e.movementY)
+ dW = e.movementX;
+ dY = -1;
+ dH = -(e.movementY);
break;
case "documentDecorations-topResizer":
- dY = -1
- dH = -(e.movementY)
+ dY = -1;
+ dH = -(e.movementY);
break;
case "documentDecorations-bottomLeftResizer":
- dX = -1
- dW = -(e.movementX)
- dH = e.movementY
+ dX = -1;
+ dW = -(e.movementX);
+ dH = e.movementY;
break;
case "documentDecorations-bottomRightResizer":
- dW = e.movementX
- dH = e.movementY
+ dW = e.movementX;
+ dH = e.movementY;
break;
case "documentDecorations-bottomResizer":
- dH = e.movementY
+ dH = e.movementY;
break;
case "documentDecorations-leftResizer":
- dX = -1
- dW = -(e.movementX)
+ dX = -1;
+ dW = -(e.movementX);
break;
case "documentDecorations-rightResizer":
- dW = e.movementX
+ dW = e.movementX;
break;
}
+ MainOverlayTextBox.Instance.SetTextDoc();
SelectionManager.SelectedDocuments().forEach(element => {
const rect = element.screenRect();
if (rect.width !== 0) {
@@ -210,14 +319,15 @@ export class DocumentDecorations extends React.Component {
var nativeWidth = doc.GetNumber(KeyStore.NativeWidth, 0);
var nativeHeight = doc.GetNumber(KeyStore.NativeHeight, 0);
if (nativeWidth > 0 && nativeHeight > 0) {
- if (Math.abs(dW) > Math.abs(dH))
+ if (Math.abs(dW) > Math.abs(dH)) {
actualdH = nativeHeight / nativeWidth * actualdW;
+ }
else actualdW = nativeWidth / nativeHeight * actualdH;
}
doc.SetNumber(KeyStore.Width, actualdW);
doc.SetNumber(KeyStore.Height, actualdH);
}
- })
+ });
}
onPointerUp = (e: PointerEvent): void => {
@@ -230,6 +340,19 @@ export class DocumentDecorations extends React.Component {
}
}
+ getValue = (): string => {
+ if (this._title === "changed" && this._documents.length > 0) {
+ let field = this._documents[0].props.Document.Get(this._fieldKey);
+ if (field instanceof TextField) {
+ return (field).GetValue();
+ }
+ else if (field instanceof NumberField) {
+ return (field).GetValue().toString();
+ }
+ }
+ return this._title;
+ }
+
changeFlyoutContent = (): void => {
}
@@ -238,11 +361,16 @@ export class DocumentDecorations extends React.Component {
// }
render() {
var bounds = this.Bounds;
+ if (bounds.x === Number.MAX_VALUE) {
+ return (null);
+ }
+ // console.log(this._documents.length)
+ // let test = this._documents[0].props.Document.Title;
if (this.Hidden) {
return (null);
}
if (isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
- console.log("DocumentDecorations: Bounds Error")
+ console.log("DocumentDecorations: Bounds Error");
return (null);
}
@@ -254,44 +382,45 @@ export class DocumentDecorations extends React.Component {
let linkCount = linkToSize + linkFromSize;
linkButton = (<Flyout
anchorPoint={anchorPoints.RIGHT_TOP}
- content={
- <LinkMenu docView={selFirst} changeFlyout={this.changeFlyoutContent}>
- </LinkMenu>
- }>
+ content={<LinkMenu docView={selFirst}
+ changeFlyout={this.changeFlyoutContent} />}>
<div className={"linkButton-" + (selFirst.props.Document.GetData(KeyStore.LinkedToDocs, ListField, []).length ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} >{linkCount}</div>
</Flyout>);
}
- return (
- <div className="documentDecorations">
- <div className="documentDecorations-background" style={{
- width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
- height: (bounds.b - bounds.y + this._resizeBorderWidth) + "px",
- left: bounds.x - this._resizeBorderWidth / 2,
- top: bounds.y - this._resizeBorderWidth / 2,
- pointerEvents: this._dragging ? "none" : "all",
- zIndex: SelectionManager.SelectedDocuments().length > 1 ? 1000 : 0,
- }} onPointerDown={this.onBackgroundDown} onContextMenu={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation() }} >
- </div>
- <div id="documentDecorations-container" style={{
- width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
- height: (bounds.b - bounds.y + this._resizeBorderWidth + 30) + "px",
- left: bounds.x - this._resizeBorderWidth / 2,
- top: bounds.y - this._resizeBorderWidth / 2,
- }}>
- <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>
- <div id="documentDecorations-topRightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
- <div id="documentDecorations-leftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
- <div id="documentDecorations-centerCont"></div>
- <div id="documentDecorations-rightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
- <div id="documentDecorations-bottomLeftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
- <div id="documentDecorations-bottomResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
- <div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
-
- <div title="View Links" className="linkFlyout" ref={this._linkButton}>{linkButton}</div>
-
- </div >
+ return (<div className="documentDecorations">
+ <div className="documentDecorations-background" style={{
+ width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
+ height: (bounds.b - bounds.y + this._resizeBorderWidth) + "px",
+ left: bounds.x - this._resizeBorderWidth / 2,
+ top: bounds.y - this._resizeBorderWidth / 2,
+ pointerEvents: this._dragging ? "none" : "all",
+ zIndex: SelectionManager.SelectedDocuments().length > 1 ? 1000 : 0,
+ }} onPointerDown={this.onBackgroundDown} onContextMenu={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); }} >
</div>
- )
+ <div id="documentDecorations-container" style={{
+ width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
+ height: (bounds.b - bounds.y + this._resizeBorderWidth + this._linkBoxHeight + this._titleHeight) + "px",
+ left: bounds.x - this._resizeBorderWidth / 2,
+ top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight,
+ 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.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>
+ <div id="documentDecorations-topRightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+ <div id="documentDecorations-leftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+ <div id="documentDecorations-centerCont"></div>
+ <div id="documentDecorations-rightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+ <div id="documentDecorations-bottomLeftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+ <div id="documentDecorations-bottomResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+ <div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+
+ <div title="View Links" className="linkFlyout" ref={this._linkButton}> {linkButton} </div>
+ <div className="linkButton-linker" ref={this._linkerButton} onPointerDown={this.onLinkerButtonDown}>∞</div>
+ </div >
+ </div>
+ );
}
} \ No newline at end of file
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 29bf6add7..2f17c6c51 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -1,7 +1,7 @@
-import React = require('react')
+import React = require('react');
import { observer } from 'mobx-react';
import { observable, action, trace } from 'mobx';
-import "./EditableView.scss"
+import "./EditableView.scss";
export interface EditableProps {
/**
@@ -22,7 +22,7 @@ export interface EditableProps {
* The contents to render when not editing
*/
contents: any;
- height: number
+ height: number;
display?: string;
}
@@ -38,7 +38,7 @@ export class EditableView extends React.Component<EditableProps> {
@action
onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
- if (e.key == "Enter") {
+ if (e.key === "Enter") {
if (!e.ctrlKey) {
if (this.props.SetValue(e.currentTarget.value)) {
this.editing = false;
@@ -47,7 +47,7 @@ export class EditableView extends React.Component<EditableProps> {
this.props.OnFillDown(e.currentTarget.value);
this.editing = false;
}
- } else if (e.key == "Escape") {
+ } else if (e.key === "Escape") {
this.editing = false;
}
}
@@ -55,14 +55,14 @@ export class EditableView extends React.Component<EditableProps> {
render() {
if (this.editing) {
return <input defaultValue={this.props.GetValue()} onKeyDown={this.onKeyDown} autoFocus onBlur={action(() => this.editing = false)}
- style={{ display: this.props.display }}></input>
+ style={{ display: this.props.display }}></input>;
} else {
return (
<div className="editableView-container-editing" style={{ display: this.props.display, height: "auto", maxHeight: `${this.props.height}` }}
onClick={action(() => this.editing = true)} >
{this.props.contents}
</div>
- )
+ );
}
}
} \ No newline at end of file
diff --git a/src/client/views/InkingCanvas.scss b/src/client/views/InkingCanvas.scss
index 35c8ee942..2c550051c 100644
--- a/src/client/views/InkingCanvas.scss
+++ b/src/client/views/InkingCanvas.scss
@@ -1,7 +1,12 @@
-@import "global_variables";
+@import "globalCssVariables";
+.inkingCanvas {
+ opacity:0.99;
+}
.inkingCanvas-paths-ink, .inkingCanvas-paths-markers, .inkingCanvas-noSelect, .inkingCanvas-canSelect {
position: absolute;
+ top: 0;
+ left:0;
width: 8192px;
height: 8192px;
cursor:"crosshair";
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index cad4b74b1..47ee8eb85 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -10,6 +10,7 @@ import "./InkingCanvas.scss";
import { InkingControl } from "./InkingControl";
import { InkingStroke } from "./InkingStroke";
import React = require("react");
+import { undoBatch, UndoManager } from "../util/UndoManager";
interface InkCanvasProps {
getScreenTransform: () => Transform;
@@ -22,6 +23,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
maxCanvasDim = 8192 / 2; // 1/2 of the maximum canvas dimension for Chrome
@observable inkMidX: number = 0;
@observable inkMidY: number = 0;
+ private previousState?: StrokeMap;
private _currentStrokeId: string = "";
public static IntersectStrokeRect(stroke: StrokeData, selRect: { left: number, top: number, width: number, height: number }): boolean {
return stroke.pathData.reduce((inside: boolean, val) => inside ||
@@ -56,24 +58,29 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
@action
onPointerDown = (e: React.PointerEvent): void => {
- if (e.button != 0 || e.altKey || e.ctrlKey || InkingControl.Instance.selectedTool === InkTool.None) {
+ if (e.button !== 0 || e.altKey || e.ctrlKey || InkingControl.Instance.selectedTool === InkTool.None) {
return;
}
+
document.addEventListener("pointermove", this.onPointerMove, true);
document.addEventListener("pointerup", this.onPointerUp, true);
e.stopPropagation();
e.preventDefault();
- if (InkingControl.Instance.selectedTool != InkTool.Eraser) {
+ this.previousState = this.inkData;
+
+ if (InkingControl.Instance.selectedTool !== InkTool.Eraser) {
// start the new line, saves a uuid to represent the field of the stroke
this._currentStrokeId = Utils.GenerateGuid();
- this.inkData.set(this._currentStrokeId, {
+ const data = this.inkData;
+ data.set(this._currentStrokeId, {
pathData: [this.relativeCoordinatesForEvent(e.clientX, e.clientY)],
color: InkingControl.Instance.selectedColor,
width: InkingControl.Instance.selectedWidth,
tool: InkingControl.Instance.selectedTool,
page: this.props.Document.GetNumber(KeyStore.CurPage, -1)
});
+ this.inkData = data;
}
}
@@ -88,13 +95,23 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
}
e.stopPropagation();
e.preventDefault();
+
+ const batch = UndoManager.StartBatch("One ink stroke");
+ const oldState = this.previousState || new Map;
+ this.previousState = undefined;
+ const newState = this.inkData;
+ UndoManager.AddEvent({
+ undo: () => this.inkData = oldState,
+ redo: () => this.inkData = newState,
+ });
+ batch.end();
}
@action
onPointerMove = (e: PointerEvent): void => {
- e.stopPropagation()
+ e.stopPropagation();
e.preventDefault();
- if (InkingControl.Instance.selectedTool != InkTool.Eraser) {
+ if (InkingControl.Instance.selectedTool !== InkTool.Eraser) {
let data = this.inkData; // add points to new line as it is being drawn
let strokeData = data.get(this._currentStrokeId);
if (strokeData) {
@@ -110,6 +127,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
return { x, y };
}
+ @undoBatch
@action
removeLine = (id: string): void => {
let data = this.inkData;
@@ -119,34 +137,35 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
@computed
get drawnPaths() {
- let curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1)
+ let curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
let paths = Array.from(this.inkData).reduce((paths, [id, strokeData]) => {
- if (strokeData.page == -1 || strokeData.page == curPage)
+ if (strokeData.page === -1 || strokeData.page === curPage) {
paths.push(<InkingStroke key={id} id={id} line={strokeData.pathData}
offsetX={this.maxCanvasDim - this.inkMidX}
offsetY={this.maxCanvasDim - this.inkMidY}
color={strokeData.color} width={strokeData.width}
- tool={strokeData.tool} deleteCallback={this.removeLine} />)
+ tool={strokeData.tool} deleteCallback={this.removeLine} />);
+ }
return paths;
}, [] as JSX.Element[]);
return [<svg className={`inkingCanvas-paths-markers`} key="Markers"
style={{ left: `${this.inkMidX - this.maxCanvasDim}px`, top: `${this.inkMidY - this.maxCanvasDim}px` }} >
- {paths.filter(path => path.props.tool == InkTool.Highlighter)}
+ {paths.filter(path => path.props.tool === InkTool.Highlighter)}
</svg>,
<svg className={`inkingCanvas-paths-ink`} key="Pens"
- style={{ left: `-${this.inkMidX - this.maxCanvasDim}px`, top: `-${this.inkMidY - this.maxCanvasDim}px` }}>
- {paths.filter(path => path.props.tool != InkTool.Highlighter)}
+ style={{ left: `${this.inkMidX - this.maxCanvasDim}px`, top: `${this.inkMidY - this.maxCanvasDim}px` }}>
+ {paths.filter(path => path.props.tool !== InkTool.Highlighter)}
</svg>];
}
render() {
- let svgCanvasStyle = InkingControl.Instance.selectedTool != InkTool.None ? "canSelect" : "noSelect";
+ let svgCanvasStyle = InkingControl.Instance.selectedTool !== InkTool.None ? "canSelect" : "noSelect";
return (
<div className="inkingCanvas" >
<div className={`inkingCanvas-${svgCanvasStyle}`} onPointerDown={this.onPointerDown} />
{this.props.children()}
{this.drawnPaths}
</div >
- )
+ );
}
} \ No newline at end of file
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/InkingControl.tsx b/src/client/views/InkingControl.tsx
index c1519dff8..9a68f0671 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -1,10 +1,10 @@
import { observable, action, computed } from "mobx";
-import { CirclePicker, ColorResult } from 'react-color'
+import { CirclePicker, ColorResult } from 'react-color';
import React = require("react");
import { InkTool } from "../../fields/InkField";
import { observer } from "mobx-react";
-import "./InkingControl.scss"
+import "./InkingControl.scss";
import { library } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faPen, faHighlighter, faEraser, faBan } from '@fortawesome/free-solid-svg-icons';
@@ -21,11 +21,11 @@ export class InkingControl extends React.Component {
@observable private _selectedColor: string = "rgb(244, 67, 54)";
@observable private _selectedWidth: string = "25";
@observable private _open: boolean = false;
- @observable private _colorPickerDisplay: boolean = false;
+ @observable private _colorPickerDisplay = false;
constructor(props: Readonly<{}>) {
super(props);
- InkingControl.Instance = this
+ InkingControl.Instance = this;
}
@action
@@ -36,9 +36,9 @@ export class InkingControl extends React.Component {
@action
switchColor = (color: ColorResult): void => {
this._selectedColor = color.hex;
- if (SelectionManager.SelectedDocuments().length == 1) {
+ if (SelectionManager.SelectedDocuments().length === 1) {
var sdoc = SelectionManager.SelectedDocuments()[0];
- if (sdoc.props.ContainingCollectionView && sdoc.props.ContainingCollectionView) {
+ if (sdoc.props.ContainingCollectionView) {
sdoc.props.Document.SetDataOnPrototype(KeyStore.BackgroundColor, color.hex, TextField);
}
}
@@ -66,9 +66,9 @@ export class InkingControl extends React.Component {
selected = (tool: InkTool) => {
if (this._selectedTool === tool) {
- return { color: "#61aaa3" }
+ return { color: "#61aaa3" };
}
- return {}
+ return {};
}
@action
@@ -76,6 +76,7 @@ export class InkingControl extends React.Component {
this._open = !this._open;
}
+
@action
toggleColorPicker = () => {
this._colorPickerDisplay = !this._colorPickerDisplay;
@@ -110,6 +111,6 @@ export class InkingControl extends React.Component {
onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.switchWidth(e.target.value)} />
</li>
</ul >
- )
+ );
}
} \ No newline at end of file
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 615f8af7e..0f05da22c 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -41,7 +41,7 @@ export class InkingStroke extends React.Component<StrokeProps> {
fill: "none",
stroke: this._strokeColor,
strokeWidth: this._strokeWidth + "px",
- }
+ };
}
}
@@ -49,10 +49,10 @@ export class InkingStroke extends React.Component<StrokeProps> {
let pathStyle = this.createStyle();
let pathData = this.parseData(this.props.line);
- let pointerEvents: any = InkingControl.Instance.selectedTool == InkTool.Eraser ? "all" : "none";
+ let pointerEvents: any = InkingControl.Instance.selectedTool === InkTool.Eraser ? "all" : "none";
return (
<path d={pathData} style={{ ...pathStyle, pointerEvents: pointerEvents }} strokeLinejoin="round" strokeLinecap="round"
onPointerOver={this.deleteStroke} onPointerDown={this.deleteStroke} />
- )
+ );
}
} \ No newline at end of file
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index 698a9c617..4373534b2 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -1,12 +1,15 @@
-@import "global_variables";
+@import "globalCssVariables";
@import "nodeModuleOverrides";
html,
body {
width: 100%;
height: 100%;
- overflow: hidden;
+ overflow: auto;
font-family: $sans-serif;
margin: 0;
+ position:absolute;
+ top: 0;
+ left:0;
}
#dash-title {
@@ -154,15 +157,23 @@ button:hover {
cursor: pointer;
}
}
+#root {
+ overflow: visible;
+}
#main-div {
width:100%;
height:100%;
position:absolute;
+ top: 0;
+ left:0;
+ overflow: scroll;
}
#mainContent-div {
width:100%;
height:100%;
position:absolute;
+ top: 0;
+ left:0;
}
#add-options-content {
display: table;
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 2014f3304..4a12b0015 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -1,17 +1,32 @@
-import { action, configure, observable, runInAction, trace, computed, reaction } from 'mobx';
+import { IconName, library } from '@fortawesome/fontawesome-svg-core';
+import { faFilePdf, faFilm, faFont, faGlobeAsia, faImage, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faTree, faUndoAlt } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, configure, observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
import "normalize.css";
import * as React from 'react';
import * as ReactDOM from 'react-dom';
+import Measure from 'react-measure';
+import * as request from 'request';
import { Document } from '../../fields/Document';
+import { Field, FieldWaiting, Opt, FIELD_WAITING } from '../../fields/Field';
import { KeyStore } from '../../fields/KeyStore';
-import "./Main.scss";
+import { ListField } from '../../fields/ListField';
+import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
import { MessageStore } from '../../server/Message';
-import { Utils } from '../../Utils';
-import * as request from 'request'
-import * as rp from 'request-promise'
+import { RouteStore } from '../../server/RouteStore';
+import { ServerUtils } from '../../server/ServerUtil';
+import { emptyDocFunction, emptyFunction, returnTrue, Utils } from '../../Utils';
import { Documents } from '../documents/Documents';
+import { ColumnAttributeModel } from '../northstar/core/attribute/AttributeModel';
+import { AttributeTransformationModel } from '../northstar/core/attribute/AttributeTransformationModel';
+import { Gateway, NorthstarSettings } from '../northstar/manager/Gateway';
+import { AggregateFunction, Catalog } 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 } from '../util/DragManager';
import { Transform } from '../util/Transform';
import { UndoManager } from '../util/UndoManager';
import { PresentationView } from './PresentationView';
@@ -19,65 +34,30 @@ import { WorkspacesMenu } from '../../server/authentication/controllers/Workspac
import { CollectionDockingView } from './collections/CollectionDockingView';
import { ContextMenu } from './ContextMenu';
import { DocumentDecorations } from './DocumentDecorations';
-import { DocumentView } from './nodes/DocumentView';
-import "./Main.scss";
-import { observer } from 'mobx-react';
import { InkingControl } from './InkingControl';
-import { RouteStore } from '../../server/RouteStore';
-import { library, IconName } from '@fortawesome/fontawesome-svg-core';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faFont } from '@fortawesome/free-solid-svg-icons';
-import { faImage } from '@fortawesome/free-solid-svg-icons';
-import { faFilePdf } from '@fortawesome/free-solid-svg-icons';
-import { faObjectGroup } from '@fortawesome/free-solid-svg-icons';
-import { faTable } from '@fortawesome/free-solid-svg-icons';
-import { faGlobeAsia } from '@fortawesome/free-solid-svg-icons';
-import { faUndoAlt } from '@fortawesome/free-solid-svg-icons';
-import { faRedoAlt } from '@fortawesome/free-solid-svg-icons';
-import { faPenNib } from '@fortawesome/free-solid-svg-icons';
-import { faFilm } from '@fortawesome/free-solid-svg-icons';
-import { faMusic } from '@fortawesome/free-solid-svg-icons';
-import { faTree } from '@fortawesome/free-solid-svg-icons';
-import Measure from 'react-measure';
-import { DashUserModel } from '../../server/authentication/models/user_model';
-import { ServerUtils } from '../../server/ServerUtil';
-import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
-import { Field, Opt, FieldWaiting } from '../../fields/Field';
-import { ListField } from '../../fields/ListField';
-import { Gateway, Settings } from '../northstar/manager/Gateway';
-import { Catalog, Schema, Attribute, AttributeGroup, AggregateFunction } from '../northstar/model/idea/idea';
-import { ArrayUtil } from '../northstar/utils/ArrayUtil';
-import '../northstar/model/ModelExtensions'
-import '../northstar/utils/Extensions'
-import { HistogramOperation } from '../northstar/operations/HistogramOperation';
-import { AttributeTransformationModel } from '../northstar/core/attribute/AttributeTransformationModel';
-import { ColumnAttributeModel } from '../northstar/core/attribute/AttributeModel';
+import "./Main.scss";
+import { MainOverlayTextBox } from './MainOverlayTextBox';
+import { DocumentView } from './nodes/DocumentView';
+import { PreviewCursor } from './PreviewCursor';
+
@observer
export class Main extends React.Component {
- // dummy initializations keep the compiler happy
- @observable private mainfreeform?: Document;
+ public static Instance: Main;
+ @observable private _workspacesShown: boolean = false;
@observable public pwidth: number = 0;
@observable public pheight: number = 0;
- private _northstarSchemas: Document[] = [];
- @computed private get mainContainer(): Document | undefined {
- let doc = this.userDocument.GetT(KeyStore.ActiveWorkspace, Document);
- return doc == FieldWaiting ? undefined : doc;
+ @computed private get mainContainer(): Document | undefined | FIELD_WAITING {
+ return CurrentUserUtils.UserDocument.GetT(KeyStore.ActiveWorkspace, Document);
}
-
- private set mainContainer(doc: Document | undefined) {
+ private set mainContainer(doc: Document | undefined | FIELD_WAITING) {
if (doc) {
- this.userDocument.Set(KeyStore.ActiveWorkspace, doc);
+ doc.GetOrCreateAsync(KeyStore.PresentationView, Document, doc => { });
+ CurrentUserUtils.UserDocument.Set(KeyStore.ActiveWorkspace, doc);
}
}
- private get userDocument(): Document {
- return CurrentUserUtils.UserDocument;
- }
-
- public static Instance: Main;
-
constructor(props: Readonly<{}>) {
super(props);
Main.Instance = this;
@@ -85,10 +65,10 @@ export class Main extends React.Component {
configure({ enforceActions: "observed" });
if (window.location.pathname !== RouteStore.home) {
let pathname = window.location.pathname.split("/");
- if (pathname.length > 1 && pathname[pathname.length - 2] == 'doc') {
+ if (pathname.length > 1 && pathname[pathname.length - 2] === 'doc') {
CurrentUserUtils.MainDocId = pathname[pathname.length - 1];
}
- };
+ }
CurrentUserUtils.loadCurrentUser();
@@ -111,6 +91,10 @@ export class Main extends React.Component {
this.initializeNorthstar();
}
+ componentDidMount() { window.onpopstate = this.onHistory; }
+
+ componentWillUnmount() { window.onpopstate = null; }
+
onHistory = () => {
if (window.location.pathname !== RouteStore.home) {
let pathname = window.location.pathname.split("/");
@@ -123,18 +107,10 @@ export class Main extends React.Component {
}
}
- componentDidMount() {
- window.onpopstate = this.onHistory;
- }
-
- componentWillUnmount() {
- window.onpopstate = null;
- }
-
initEventListeners = () => {
// window.addEventListener("pointermove", (e) => this.reportLocation(e))
- window.addEventListener("drop", (e) => e.preventDefault(), false) // drop event handler
- window.addEventListener("dragover", (e) => e.preventDefault(), false) // drag event handler
+ window.addEventListener("drop", (e) => e.preventDefault(), false); // drop event handler
+ window.addEventListener("dragover", (e) => e.preventDefault(), false); // drag event handler
// click interactions for the context menu
document.addEventListener("pointerdown", action(function (e: PointerEvent) {
if (!ContextMenu.Instance.intersects(e.pageX, e.pageY)) {
@@ -146,28 +122,24 @@ export class Main extends React.Component {
initAuthenticationRouters = () => {
// Load the user's active workspace, or create a new one if initial session after signup
if (!CurrentUserUtils.MainDocId) {
- this.userDocument.GetTAsync(KeyStore.ActiveWorkspace, Document).then(doc => {
+ CurrentUserUtils.UserDocument.GetTAsync(KeyStore.ActiveWorkspace, Document).then(doc => {
if (doc) {
CurrentUserUtils.MainDocId = doc.Id;
this.openWorkspace(doc);
} else {
this.createNewWorkspace();
}
- })
+ });
} else {
- Server.GetField(CurrentUserUtils.MainDocId).then(field => {
- if (field instanceof Document) {
- this.openWorkspace(field)
- } else {
- this.createNewWorkspace(CurrentUserUtils.MainDocId);
- }
- })
+ Server.GetField(CurrentUserUtils.MainDocId).then(field =>
+ field instanceof Document ? this.openWorkspace(field) :
+ this.createNewWorkspace(CurrentUserUtils.MainDocId));
}
}
@action
createNewWorkspace = (id?: string): void => {
- this.userDocument.GetTAsync<ListField<Document>>(KeyStore.Workspaces, ListField).then(action((list: Opt<ListField<Document>>) => {
+ CurrentUserUtils.UserDocument.GetTAsync<ListField<Document>>(KeyStore.Workspaces, ListField).then(action((list: Opt<ListField<Document>>) => {
if (list) {
let freeformDoc = Documents.FreeformDocument([], { x: 0, y: 400, title: "mini collection" });
var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(freeformDoc)] }] };
@@ -177,7 +149,7 @@ export class Main extends React.Component {
// bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
setTimeout(() => {
this.openWorkspace(mainDoc);
- let pendingDocument = Documents.SchemaDocument([], { title: "New Mobile Uploads" })
+ let pendingDocument = Documents.SchemaDocument([], { title: "New Mobile Uploads" });
mainDoc.Set(KeyStore.OptionalRightCollection, pendingDocument);
}, 0);
}
@@ -188,77 +160,78 @@ export class Main extends React.Component {
openWorkspace = (doc: Document, fromHistory = false): void => {
this.mainContainer = doc;
fromHistory || window.history.pushState(null, doc.Title, "/doc/" + doc.Id);
- this.userDocument.GetTAsync(KeyStore.OptionalRightCollection, Document).then(col => {
+ CurrentUserUtils.UserDocument.GetTAsync(KeyStore.OptionalRightCollection, Document).then(col =>
// if there is a pending doc, and it has new data, show it (syip: we use a timeout to prevent collection docking view from being uninitialized)
- setTimeout(() => {
- if (col) {
- col.GetTAsync<ListField<Document>>(KeyStore.Data, ListField, (f: Opt<ListField<Document>>) => {
- if (f && f.Data.length > 0) {
- CollectionDockingView.Instance.AddRightSplit(col);
- }
- })
- }
- }, 100);
- });
+ setTimeout(() =>
+ col && col.GetTAsync<ListField<Document>>(KeyStore.Data, ListField, (f: Opt<ListField<Document>>) =>
+ f && f.Data.length > 0 && CollectionDockingView.Instance.AddRightSplit(col))
+ , 100)
+ );
}
- @observable
- workspacesShown: boolean = false;
-
- areWorkspacesShown = () => {
- return this.workspacesShown;
- }
- @action
- toggleWorkspaces = () => {
- this.workspacesShown = !this.workspacesShown;
+ @computed
+ get presentationView() {
+ if (this.mainContainer) {
+ let presentation = this.mainContainer.GetT(KeyStore.PresentationView, Document);
+ return presentation ? <PresentationView Document={presentation} key="presentation" /> : (null);
+ }
+ return (null);
}
- screenToLocalTransform = () => Transform.Identity
- pwidthFunc = () => this.pwidth;
- pheightFunc = () => this.pheight;
- focusDocument = (doc: Document) => { }
- noScaling = () => 1;
-
@computed
get mainContent() {
- if (!this.mainContainer)
- return (null);
- this.mainContainer.GetOrCreateAsync(KeyStore.PresentationView, Document, doc => { });
- let presentation = this.mainContainer.GetT(KeyStore.PresentationView, Document);
- let pcontent = presentation && presentation != FieldWaiting ? <PresentationView Document={presentation} /> : (null);
- return [
- pcontent,
- <DocumentView Document={this.mainContainer}
- AddDocument={undefined}
- RemoveDocument={undefined}
- ScreenToLocalTransform={this.screenToLocalTransform}
- ContentScaling={this.noScaling}
- PanelWidth={this.pwidthFunc}
- PanelHeight={this.pheightFunc}
- isTopMost={true}
- SelectOnLoad={false}
- focus={this.focusDocument}
- ContainingCollectionView={undefined} />]
+ let pwidthFunc = () => this.pwidth;
+ let pheightFunc = () => this.pheight;
+ let noScaling = () => 1;
+ let mainCont = this.mainContainer;
+ let pcontent = this.presentationView;
+ return <Measure onResize={action((r: any) => { this.pwidth = r.entry.width; this.pheight = r.entry.height; })}>
+ {({ measureRef }) =>
+ <div ref={measureRef} id="mainContent-div">
+ {!mainCont ? (null) :
+ <div style={{ width: "100%", height: "100%", position: "absolute", }}>
+ {pcontent}
+ <DocumentView
+ key="documentView"
+ Document={mainCont}
+ addDocument={undefined}
+ removeDocument={undefined}
+ opacity={1}
+ ScreenToLocalTransform={Transform.Identity}
+ ContentScaling={noScaling}
+ PanelWidth={pwidthFunc}
+ PanelHeight={pheightFunc}
+ isTopMost={true}
+ selectOnLoad={false}
+ focus={emptyDocFunction}
+ parentActive={returnTrue}
+ onActiveChanged={emptyFunction}
+ ContainingCollectionView={undefined} />
+ </div>
+ }
+ </div>
+ }
+ </Measure>;
}
/* for the expandable add nodes menu. Not included with the miscbuttons because once it expands it expands the whole div with it, making canvas interactions limited. */
@computed
get nodesMenu() {
let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg";
- let pdfurl = "http://www.adobe.com/support/products/enterprise/knowledgecenter/media/c4611_sample_explain.pdf"
+ let pdfurl = "http://www.adobe.com/support/products/enterprise/knowledgecenter/media/c27211_sample_explain.pdf";
let weburl = "https://cs.brown.edu/courses/cs166/";
let audiourl = "http://techslides.com/demos/samples/sample.mp3";
let videourl = "http://techslides.com/demos/sample-videos/small.mp4";
- let addTextNode = action(() => Documents.TextDocument({ width: 200, height: 200, title: "a text note" }))
+ let addTextNode = action(() => Documents.TextDocument({ width: 200, height: 200, title: "a text note" }));
let addColNode = action(() => Documents.FreeformDocument([], { width: 200, height: 200, title: "a freeform collection" }));
let addSchemaNode = action(() => Documents.SchemaDocument([], { width: 200, height: 200, title: "a schema collection" }));
- let addTreeNode = action(() => Documents.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas" }));
+ let addTreeNode = action(() => Documents.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas", copyDraggedItems: true }));
let addVideoNode = action(() => Documents.VideoDocument(videourl, { width: 200, height: 200, title: "video node" }));
let addPDFNode = action(() => Documents.PdfDocument(pdfurl, { width: 200, height: 200, title: "a schema collection" }));
let addImageNode = action(() => Documents.ImageDocument(imgurl, { width: 200, height: 200, title: "an image of a cat" }));
let addWebNode = action(() => Documents.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" }));
- let addAudioNode = action(() => Documents.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" }))
+ let addAudioNode = action(() => Documents.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" }));
let btns: [React.RefObject<HTMLDivElement>, IconName, string, () => Document][] = [
[React.createRef<HTMLDivElement>(), "font", "Add Textbox", addTextNode],
@@ -270,7 +243,7 @@ export class Main extends React.Component {
[React.createRef<HTMLDivElement>(), "object-group", "Add Collection", addColNode],
[React.createRef<HTMLDivElement>(), "tree", "Add Tree", addTreeNode],
[React.createRef<HTMLDivElement>(), "table", "Add Schema", addSchemaNode],
- ]
+ ];
return < div id="add-nodes-menu" >
<input type="checkbox" id="add-menu-toggle" />
@@ -280,13 +253,13 @@ 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>)}
</ul>
</div>
- </div >
+ </div >;
}
/* @TODO this should really be moved into a moveable toolbar component, but for now let's put it here to meet the deadline */
@@ -294,8 +267,9 @@ export class Main extends React.Component {
get miscButtons() {
let workspacesRef = React.createRef<HTMLDivElement>();
let logoutRef = React.createRef<HTMLDivElement>();
+ let toggleWorkspaces = () => runInAction(() => this._workspacesShown = !this._workspacesShown);
- let clearDatabase = action(() => Utils.Emit(Server.Socket, MessageStore.DeleteAll, {}))
+ let clearDatabase = action(() => Utils.Emit(Server.Socket, MessageStore.DeleteAll, {}));
return [
<button className="clear-db-button" key="clear-db" onClick={clearDatabase}>Clear Database</button>,
<div id="toolbar" key="toolbar">
@@ -304,83 +278,76 @@ export class Main extends React.Component {
<button className="toolbar-button round-button" title="Ink" onClick={() => InkingControl.Instance.toggleDisplay()}><FontAwesomeIcon icon="pen-nib" size="sm" /></button>
</div >,
<div className="main-buttonDiv" key="workspaces" style={{ top: '34px', left: '2px', position: 'absolute' }} ref={workspacesRef}>
- <button onClick={this.toggleWorkspaces}>Workspaces</button></div>,
+ <button onClick={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>
+ ];
+ }
+
+ @computed
+ get workspaceMenu() {
+ let areWorkspacesShown = () => this._workspacesShown;
+ let toggleWorkspaces = () => runInAction(() => this._workspacesShown = !this._workspacesShown);
+ let workspaces = CurrentUserUtils.UserDocument.GetT<ListField<Document>>(KeyStore.Workspaces, ListField);
+ return (!workspaces || workspaces === FieldWaiting || this.mainContainer === FieldWaiting) ? (null) :
+ <WorkspacesMenu active={this.mainContainer} open={this.openWorkspace}
+ new={this.createNewWorkspace} allWorkspaces={workspaces.Data}
+ isShown={areWorkspacesShown} toggle={toggleWorkspaces} />;
}
render() {
- let workspaceMenu: any = null;
- let workspaces = this.userDocument.GetT<ListField<Document>>(KeyStore.Workspaces, ListField);
- if (workspaces && workspaces !== FieldWaiting) {
- workspaceMenu = <WorkspacesMenu active={this.mainContainer} open={this.openWorkspace} new={this.createNewWorkspace} allWorkspaces={workspaces.Data}
- isShown={this.areWorkspacesShown} toggle={this.toggleWorkspaces} />
- }
return (
<div id="main-div">
<DocumentDecorations />
- <Measure onResize={(r: any) => runInAction(() => {
- this.pwidth = r.entry.width;
- this.pheight = r.entry.height;
- })}>
- {({ measureRef }) =>
- <div ref={measureRef} id="mainContent-div">
- {this.mainContent}
- </div>
- }
- </Measure>
+ {this.mainContent}
+ <PreviewCursor />
<ContextMenu />
{this.nodesMenu}
{this.miscButtons}
- {workspaceMenu}
+ {this.workspaceMenu}
<InkingControl />
+ <MainOverlayTextBox />
</div>
);
}
// --------------- Northstar hooks ------------- /
+ private _northstarSchemas: Document[] = [];
@action SetNorthstarCatalog(ctlog: Catalog) {
CurrentUserUtils.NorthstarDBCatalog = ctlog;
if (ctlog && ctlog.schemas) {
- this._northstarSchemas = ctlog.schemas.map(schema => {
- let schemaDoc = Documents.TreeDocument([], { width: 50, 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>) => {
+ ctlog.schemas.map(schema => {
+ let schemaDocuments: Document[] = [];
+ let attributesToBecomeDocs = CurrentUserUtils.GetAllNorthstarColumnAttributes(schema);
+ Promise.all(attributesToBecomeDocs.reduce((promises, attr) => {
+ promises.push(Server.GetField(attr.displayName! + ".alias").then(action((field: Opt<Field>) => {
if (field instanceof Document) {
schemaDocuments.push(field);
} else {
var atmod = new ColumnAttributeModel(attr);
- let histoOp = new HistogramOperation(schema!.displayName!,
+ 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 promises;
+ }, [] as Promise<void>[])).finally(() =>
+ this._northstarSchemas.push(Documents.TreeDocument(schemaDocuments, { width: 50, height: 100, title: schema.displayName! })));
+ });
}
}
async initializeNorthstar(): Promise<void> {
- let envPath = "/assets/env.json";
- const response = await fetch(envPath, {
- redirect: "follow",
- method: "GET",
- credentials: "include"
- });
- const env = await response.json();
- Settings.Instance.Update(env);
- let cat = Gateway.Instance.ClearCatalog();
- cat.then(async () => this.SetNorthstarCatalog(await Gateway.Instance.GetCatalog()));
+ const getEnvironment = await fetch("/assets/env.json", { redirect: "follow", method: "GET", credentials: "include" });
+ NorthstarSettings.Instance.UpdateEnvironment(await getEnvironment.json());
+ Gateway.Instance.ClearCatalog().then(async () => this.SetNorthstarCatalog(await Gateway.Instance.GetCatalog()));
}
}
-Documents.initProtos().then(() => {
- return CurrentUserUtils.loadCurrentUser()
-}).then(() => {
+(async () => {
+ await Documents.initProtos();
+ await CurrentUserUtils.loadCurrentUser();
ReactDOM.render(<Main />, document.getElementById('root'));
-});
+})();
diff --git a/src/client/views/MainOverlayTextBox.scss b/src/client/views/MainOverlayTextBox.scss
new file mode 100644
index 000000000..697d68c8c
--- /dev/null
+++ b/src/client/views/MainOverlayTextBox.scss
@@ -0,0 +1,19 @@
+@import "globalCssVariables";
+.mainOverlayTextBox-textInput {
+ 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;
+ top: 0;
+ left: 0;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
new file mode 100644
index 000000000..df1addbc7
--- /dev/null
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -0,0 +1,110 @@
+import { action, observable, trace } from 'mobx';
+import { observer } from 'mobx-react';
+import "normalize.css";
+import * as React from 'react';
+import { Document } from '../../fields/Document';
+import { Key } from '../../fields/Key';
+import { KeyStore } from '../../fields/KeyStore';
+import { emptyDocFunction, emptyFunction, returnTrue } from '../../Utils';
+import '../northstar/model/ModelExtensions';
+import '../northstar/utils/Extensions';
+import { DragManager } from '../util/DragManager';
+import { Transform } from '../util/Transform';
+import "./MainOverlayTextBox.scss";
+import { FormattedTextBox } from './nodes/FormattedTextBox';
+
+interface MainOverlayTextBoxProps {
+}
+
+@observer
+export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps> {
+ public static Instance: MainOverlayTextBox;
+ @observable public TextDoc?: Document = undefined;
+ public TextScroll: number = 0;
+ private _textRect: any;
+ private _textXf: Transform = Transform.Identity();
+ private _textFieldKey: Key = KeyStore.Data;
+ private _textColor: string | null = null;
+ private _textTargetDiv: HTMLDivElement | undefined;
+ private _textProxyDiv: React.RefObject<HTMLDivElement>;
+
+ constructor(props: MainOverlayTextBoxProps) {
+ super(props);
+ this._textProxyDiv = React.createRef();
+ MainOverlayTextBox.Instance = this;
+ }
+
+ @action
+ 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);
+ }
+
+ render() {
+ if (this.TextDoc) {
+ let x: number = this._textRect.x;
+ let y: number = this._textRect.y;
+ let w: number = this._textRect.width;
+ let h: number = this._textRect.height;
+ 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="mainOverlayTextBox-textInput" style={{ pointerEvents: "none", transform: `translate(${x}px, ${y}px) scale(${1 / s[0]},${1 / s[0]})`, width: "auto", height: "auto" }} >
+ <div className="mainOverlayTextBox-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} ContainingCollectionView={undefined} onActiveChanged={emptyFunction} active={returnTrue} ScreenToLocalTransform={() => this._textXf} focus={emptyDocFunction} />
+ </div>
+ </ div>;
+ }
+ else return (null);
+ }
+} \ No newline at end of file
diff --git a/src/client/views/PresentationView.tsx b/src/client/views/PresentationView.tsx
index e5e67572b..9c5297e7c 100644
--- a/src/client/views/PresentationView.tsx
+++ b/src/client/views/PresentationView.tsx
@@ -5,11 +5,12 @@ import { ListField } from "../../fields/ListField";
import React = require("react")
import { TextField } from "../../fields/TextField";
import { observable, action } from "mobx";
-import { Field } from "../../fields/Field";
+import { Field, FieldWaiting } from "../../fields/Field";
import "./PresentationView.scss"
import { NumberField } from "../../fields/NumberField";
import "./Main.tsx";
import { DocumentManager } from "../util/DocumentManager";
+import { Utils } from "../../Utils";
export interface PresViewProps {
Document: Document;
@@ -27,7 +28,7 @@ class PresentationViewItem extends React.Component<PresViewProps> {
openDoc = (doc: Document) => {
let docView = DocumentManager.Instance.getDocumentView(doc);
if (docView) {
- docView.focus();
+ docView.props.focus(docView.props.Document);
}
}
@@ -39,7 +40,7 @@ class PresentationViewItem extends React.Component<PresViewProps> {
const value = this.props.Document.GetData(KeyStore.Data, ListField, new Array<Document>())
let index = -1;
for (let i = 0; i < value.length; i++) {
- if (value[i].Id == doc.Id) {
+ if (value[i].Id === doc.Id) {
index = i;
break;
}
@@ -54,29 +55,29 @@ class PresentationViewItem extends React.Component<PresViewProps> {
* @param document The document to render.
*/
renderChild(document: Document) {
- let title = document.GetT<TextField>(KeyStore.Title, TextField);
+ let title = document.Title;
//to get currently selected presentation doc
let selected = this.props.Document.GetNumber(KeyStore.SelectedDoc, 0);
// if the title hasn't loaded, immediately return the div
- if (!title || title === "<Waiting>") {
+ if (!title) {
return <div className="presentationView-item" key={document.Id}></div>;
}
// finally, if it's a normal document, then render it as such.
else {
const children = this.props.Document.GetT<ListField<Document>>(KeyStore.Data, ListField);
- if (children && children !== "<Waiting>" && children.Data[selected] == document) {
+ if (children && children.Data[selected] === document) {
//this doc is selected
const styles = {
background: "gray"
}
- return <li className="presentationView-item" style={styles} key={document.Id}>
- <div className="presentationView-header" onClick={() => this.openDoc(document)}>{title.Data}</div>
+ return <li className="presentationView-item" style={styles} key={Utils.GenerateGuid()}>
+ <div className="presentationView-header" onClick={() => this.openDoc(document)}>{title}</div>
<div className="presentation-icon" onClick={() => this.RemoveDoc(document)}>X</div></li>;
} else {
- return <li className="presentationView-item" key={document.Id} >
- <div className="presentationView-header" onClick={() => this.openDoc(document)}>{title.Data}</div>
+ return <li className="presentationView-item" key={Utils.GenerateGuid()} >
+ <div className="presentationView-header" onClick={() => this.openDoc(document)}>{title}</div>
<div className="presentation-icon" onClick={() => this.RemoveDoc(document)}>X</div></li>;
}
@@ -86,7 +87,7 @@ class PresentationViewItem extends React.Component<PresViewProps> {
render() {
const children = this.props.Document.GetT<ListField<Document>>(KeyStore.Data, ListField);
- if (children && children !== "<Waiting>") {
+ if (children) {
return (<div>
{children.Data.map(value => this.renderChild(value))}
</div>)
@@ -108,13 +109,13 @@ export class PresentationView extends React.Component<PresViewProps> {
next = () => {
const current = this.props.Document.GetNumber(KeyStore.SelectedDoc, 0);
const allDocs = this.props.Document.GetT<ListField<Document>>(KeyStore.Data, ListField);
- if (allDocs && allDocs !== "<Waiting>" && current < allDocs.Data.length + 1) {
+ if (allDocs && current < allDocs.Data.length + 1) {
//can move forwards
this.props.Document.SetNumber(KeyStore.SelectedDoc, current + 1);
const doc = allDocs.Data[current + 1];
let docView = DocumentManager.Instance.getDocumentView(doc);
if (docView) {
- docView.focus();
+ docView.props.focus(docView.props.Document);
}
}
@@ -122,13 +123,13 @@ export class PresentationView extends React.Component<PresViewProps> {
back = () => {
const current = this.props.Document.GetNumber(KeyStore.SelectedDoc, 0);
const allDocs = this.props.Document.GetT<ListField<Document>>(KeyStore.Data, ListField);
- if (allDocs && allDocs !== "<Waiting>" && current - 1 >= 0) {
+ if (allDocs && current - 1 >= 0) {
//can move forwards
this.props.Document.SetNumber(KeyStore.SelectedDoc, current - 1);
const doc = allDocs.Data[current - 1];
let docView = DocumentManager.Instance.getDocumentView(doc);
if (docView) {
- docView.focus();
+ docView.props.focus(docView.props.Document);
}
}
@@ -160,16 +161,12 @@ export class PresentationView extends React.Component<PresViewProps> {
}
render() {
- let titleStr = "Title";
- let title = this.props.Document.GetT<TextField>(KeyStore.Title, TextField);
- if (title && title !== "<Waiting>") {
- titleStr = title.Data;
- }
+ let titleStr = this.props.Document.Title;
let width = this.props.Document.GetNumber(KeyStore.Width, 0);
//TODO: next and back should be icons
return (
- <div className="presentationView-cont" style={{ width: width }}>
+ <div className="presentationView-cont" style={{ width: width, overflow: "hidden" }}>
<div className="presentationView-heading">
<div className="presentationView-title">{titleStr}</div>
<div className='presentation-icon' onClick={this.closePresentation}>X</div></div>
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
new file mode 100644
index 000000000..bff56e8c3
--- /dev/null
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -0,0 +1,194 @@
+import { action, computed } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Document } from '../../../fields/Document';
+import { Field, FieldValue, FieldWaiting } from '../../../fields/Field';
+import { KeyStore } from '../../../fields/KeyStore';
+import { ListField } from '../../../fields/ListField';
+import { NumberField } from '../../../fields/NumberField';
+import { ContextMenu } from '../ContextMenu';
+import { FieldViewProps } from '../nodes/FieldView';
+
+export enum CollectionViewType {
+ Invalid,
+ Freeform,
+ Schema,
+ Docking,
+ Tree,
+}
+
+export interface CollectionRenderProps {
+ addDocument: (document: Document, allowDuplicates?: boolean) => boolean;
+ removeDocument: (document: Document) => boolean;
+ moveDocument: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
+ active: () => boolean;
+ onActiveChanged: (isActive: boolean) => void;
+}
+
+export interface CollectionViewProps extends FieldViewProps {
+ onContextMenu?: (e: React.MouseEvent) => void;
+ children: (type: CollectionViewType, props: CollectionRenderProps) => JSX.Element | JSX.Element[] | null;
+ className?: string;
+ contentRef?: React.Ref<HTMLDivElement>;
+}
+
+
+@observer
+export class CollectionBaseView extends React.Component<CollectionViewProps> {
+ get collectionViewType(): CollectionViewType | undefined {
+ let Document = this.props.Document;
+ let viewField = Document.GetT(KeyStore.ViewType, NumberField);
+ if (viewField === FieldWaiting) {
+ return undefined;
+ } else if (viewField) {
+ return viewField.Data;
+ } else {
+ return CollectionViewType.Invalid;
+ }
+ }
+
+ active = (): boolean => {
+ var isSelected = this.props.isSelected();
+ var topMost = this.props.isTopMost;
+ return isSelected || this._isChildActive || topMost;
+ }
+
+ //TODO should this be observable?
+ private _isChildActive = false;
+ onActiveChanged = (isActive: boolean) => {
+ this._isChildActive = isActive;
+ this.props.onActiveChanged(isActive);
+ }
+
+ createsCycle(documentToAdd: Document, containerDocument: Document): boolean {
+ let data = documentToAdd.GetList<Document>(KeyStore.Data, []);
+ for (const doc of data) {
+ if (this.createsCycle(doc, containerDocument)) {
+ return true;
+ }
+ }
+ let annots = documentToAdd.GetList<Document>(KeyStore.Annotations, []);
+ for (const annot of annots) {
+ if (this.createsCycle(annot, containerDocument)) {
+ return true;
+ }
+ }
+ for (let containerProto: FieldValue<Document> = containerDocument; containerProto && containerProto !== FieldWaiting; containerProto = containerProto.GetPrototype()) {
+ if (containerProto.Id === documentToAdd.Id) {
+ return true;
+ }
+ }
+ return false;
+ }
+ @computed get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's?
+
+ @action.bound
+ addDocument(doc: Document, allowDuplicates: boolean = false): boolean {
+ let props = this.props;
+ var curPage = props.Document.GetNumber(KeyStore.CurPage, -1);
+ doc.SetOnPrototype(KeyStore.Page, new NumberField(curPage));
+ if (this.isAnnotationOverlay) {
+ doc.SetOnPrototype(KeyStore.Zoom, new NumberField(this.props.Document.GetNumber(KeyStore.Scale, 1)));
+ }
+ if (curPage >= 0) {
+ doc.SetOnPrototype(KeyStore.AnnotationOn, props.Document);
+ }
+ if (props.Document.Get(props.fieldKey) instanceof Field) {
+ //TODO This won't create the field if it doesn't already exist
+ const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>());
+ if (!this.createsCycle(doc, props.Document)) {
+ if (!value.some(v => v.Id === doc.Id) || allowDuplicates) {
+ value.push(doc);
+ }
+ }
+ else {
+ return false;
+ }
+ } else {
+ let proto = props.Document.GetPrototype();
+ if (!proto || proto === FieldWaiting || !this.createsCycle(proto, doc)) {
+ const field = new ListField([doc]);
+ // const script = CompileScript(`
+ // if(added) {
+ // console.log("added " + field.Title + " " + doc.Title);
+ // } else {
+ // console.log("removed " + field.Title + " " + doc.Title);
+ // }
+ // `, {
+ // addReturn: false,
+ // params: {
+ // field: Document.name,
+ // added: "boolean"
+ // },
+ // capturedVariables: {
+ // doc: this.props.Document
+ // }
+ // });
+ // if (script.compiled) {
+ // field.addScript(new ScriptField(script));
+ // }
+ props.Document.SetOnPrototype(props.fieldKey, field);
+ }
+ else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @action.bound
+ removeDocument(doc: Document): boolean {
+ const props = this.props;
+ //TODO This won't create the field if it doesn't already exist
+ const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>());
+ let index = -1;
+ for (let i = 0; i < value.length; i++) {
+ if (value[i].Id === doc.Id) {
+ index = i;
+ break;
+ }
+ }
+ doc.GetTAsync(KeyStore.AnnotationOn, Document).then((annotationOn) => {
+ if (annotationOn === props.Document) {
+ doc.Set(KeyStore.AnnotationOn, undefined, true);
+ }
+ });
+
+ if (index !== -1) {
+ value.splice(index, 1);
+
+ // SelectionManager.DeselectAll()
+ ContextMenu.Instance.clearItems();
+ return true;
+ }
+ return false;
+ }
+
+ @action.bound
+ moveDocument(doc: Document, targetCollection: Document, addDocument: (doc: Document) => boolean): boolean {
+ if (this.props.Document === targetCollection) {
+ return true;
+ }
+ if (this.removeDocument(doc)) {
+ return addDocument(doc);
+ }
+ return false;
+ }
+
+ render() {
+ const props: CollectionRenderProps = {
+ addDocument: this.addDocument,
+ removeDocument: this.removeDocument,
+ moveDocument: this.moveDocument,
+ 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}>
+ {viewtype !== undefined ? this.props.children(viewtype, props) : (null)}
+ </div>
+ );
+ }
+
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index 2706c3272..0e7e0afa7 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -1,9 +1,30 @@
+@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 {
- position: relative;
+ width: 100%;
+ height: 100%;
+ border-style: solid;
+ border-width: $COLLECTION_BORDER_WIDTH;
+ position: absolute;
top: 0;
left: 0;
overflow: hidden;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 39e0dd989..4f7d4fc0d 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 } 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 "./CollectionView";
import React = require("react");
-import { SubCollectionViewProps } from "./CollectionViewBase";
+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> {
@@ -32,7 +32,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
documentId: document.Id,
//collectionDockingView: CollectionDockingView.Instance
}
- }
+ };
}
private _goldenLayout: any = null;
@@ -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
@@ -58,7 +58,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
let newItemStackConfig = {
type: 'stack',
content: [CollectionDockingView.makeDocumentConfig(document)]
- }
+ };
var docconfig = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout);
this._goldenLayout.root.contentItems[0].addChild(docconfig);
docconfig.callDownwards('_$init');
@@ -86,7 +86,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
let newItemStackConfig = {
type: 'stack',
content: [CollectionDockingView.makeDocumentConfig(document)]
- }
+ };
var newContentItem = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout);
@@ -101,12 +101,12 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
newRow.addChild(newContentItem, undefined, true);
newRow.addChild(collayout, 0, true);
- collayout.config["width"] = 50;
- newContentItem.config["width"] = 50;
+ collayout.config.width = 50;
+ newContentItem.config.width = 50;
}
if (minimize) {
- newContentItem.config["width"] = 10;
- newContentItem.config["height"] = 10;
+ newContentItem.config.width = 10;
+ newContentItem.config.height = 10;
}
newContentItem.callDownwards('_$init');
this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]);
@@ -124,8 +124,9 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
this._goldenLayout = new GoldenLayout(JSON.parse(config));
}
else {
- if (config == JSON.stringify(this._goldenLayout.toConfig()))
+ if (config === JSON.stringify(this._goldenLayout.toConfig())) {
return;
+ }
try {
this._goldenLayout.unbind('itemDropped', this.itemDropped);
this._goldenLayout.unbind('tabCreated', this.tabCreated);
@@ -154,7 +155,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
reaction(
() => this.props.Document.GetText(KeyStore.Data, ""),
() => {
- if (!this._goldenLayout || this._ignoreStateChange != JSON.stringify(this._goldenLayout.toConfig())) {
+ if (!this._goldenLayout || this._ignoreStateChange !== JSON.stringify(this._goldenLayout.toConfig())) {
setTimeout(() => this.setupGoldenLayout(), 1);
}
this._ignoreStateChange = "";
@@ -171,7 +172,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
} catch (e) {
}
- this._goldenLayout.destroy();
+ if (this._goldenLayout) this._goldenLayout.destroy();
this._goldenLayout = null;
window.removeEventListener('resize', this.onResize);
}
@@ -193,23 +194,36 @@ 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 as Document]),
- {
- handlers: {
- dragComplete: action(() => { }),
- },
- hideSource: false
- })
- }));
- }
- if (className == "lm_drag_handle" || className == "lm_close" || className == "lm_maximise" || className == "lm_minimise" || className == "lm_close_tab") {
+ 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;
}
if (this.props.active()) {
@@ -220,30 +234,51 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
@undoBatch
stateChanged = () => {
var json = JSON.stringify(this._goldenLayout.toConfig());
- this.props.Document.SetText(KeyStore.Data, json)
+ this.props.Document.SetText(KeyStore.Data, json);
}
itemDropped = () => {
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;
+ if (tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") {
+ 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();
});
}
@@ -268,19 +303,13 @@ 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} />
);
}
}
interface DockedFrameProps {
- documentId: FieldId,
+ documentId: FieldId;
//collectionDockingView: CollectionDockingView
}
@observer
@@ -296,35 +325,39 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
Server.GetField(this.props.documentId, action((f: Opt<Field>) => this._document = f as Document));
}
- private _nativeWidth = () => { return this._document!.GetNumber(KeyStore.NativeWidth, this._panelWidth); }
- private _nativeHeight = () => { return this._document!.GetNumber(KeyStore.NativeHeight, this._panelHeight); }
- private _contentScaling = () => { return this._panelWidth / (this._nativeWidth() ? this._nativeWidth() : this._panelWidth); }
+ private _nativeWidth = () => this._document!.GetNumber(KeyStore.NativeWidth, this._panelWidth);
+ private _nativeHeight = () => this._document!.GetNumber(KeyStore.NativeHeight, this._panelHeight);
+ private _contentScaling = () => this._panelWidth / (this._nativeWidth() ? this._nativeWidth() : this._panelWidth);
ScreenToLocalTransform = () => {
let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current!);
- return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(scale / this._contentScaling())
+ return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(scale / this._contentScaling());
}
render() {
- if (!this._document)
+ if (!this._document) {
return (null);
+ }
var content =
<div className="collectionDockingView-content" ref={this._mainCont}>
<DocumentView key={this._document.Id} Document={this._document}
- AddDocument={undefined}
- RemoveDocument={undefined}
+ addDocument={undefined}
+ removeDocument={undefined}
+ opacity={1}
ContentScaling={this._contentScaling}
PanelWidth={this._nativeWidth}
PanelHeight={this._nativeHeight}
ScreenToLocalTransform={this.ScreenToLocalTransform}
isTopMost={true}
- SelectOnLoad={false}
- focus={(doc: Document) => { }}
+ selectOnLoad={false}
+ parentActive={returnTrue}
+ onActiveChanged={emptyFunction}
+ focus={emptyDocFunction}
ContainingCollectionView={undefined} />
- </div>
+ </div>;
return <Measure onResize={action((r: any) => { this._panelWidth = r.entry.width; this._panelHeight = r.entry.height; })}>
{({ measureRef }) => <div ref={measureRef}> {content} </div>}
- </Measure>
+ </Measure>;
}
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionPDFView.scss b/src/client/views/collections/CollectionPDFView.scss
index 0144625c1..0eca3f1cd 100644
--- a/src/client/views/collections/CollectionPDFView.scss
+++ b/src/client/views/collections/CollectionPDFView.scss
@@ -9,6 +9,8 @@
width: 100%;
height: 100%;
position: absolute;
+ top: 0;
+ left:0;
}
.collectionPdfView-backward {
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index 4d2daf149..229bc4059 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -1,22 +1,20 @@
-import { action, computed, observable } from "mobx";
+import { action } from "mobx";
import { observer } from "mobx-react";
-import { Document } from "../../../fields/Document";
import { KeyStore } from "../../../fields/KeyStore";
import { ContextMenu } from "../ContextMenu";
-import { CollectionView, CollectionViewType } from "./CollectionView";
-import { CollectionViewProps } from "./CollectionViewBase";
-import "./CollectionPDFView.scss"
+import "./CollectionPDFView.scss";
import React = require("react");
-import { FieldId } from "../../../fields/Field";
+import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
+import { FieldView, FieldViewProps } from "../nodes/FieldView";
+import { CollectionRenderProps, CollectionBaseView, CollectionViewType } from "./CollectionBaseView";
+import { emptyFunction } from "../../../Utils";
@observer
-export class CollectionPDFView extends React.Component<CollectionViewProps> {
+export class CollectionPDFView extends React.Component<FieldViewProps> {
public static LayoutString(fieldKey: string = "DataKey") {
- return `<${CollectionPDFView.name} Document={Document}
- ScreenToLocalTransform={ScreenToLocalTransform} fieldKey={${fieldKey}} panelWidth={PanelWidth} panelHeight={PanelHeight} isSelected={isSelected} select={select} bindings={bindings}
- isTopMost={isTopMost} SelectOnLoad={selectOnLoad} BackgroundView={BackgroundView} focus={focus}/>`;
+ return FieldView.LayoutString(CollectionPDFView, fieldKey);
}
private get curPage() { return this.props.Document.GetNumber(KeyStore.CurPage, -1); }
@@ -25,35 +23,36 @@ export class CollectionPDFView extends React.Component<CollectionViewProps> {
@action onPageForward = () => this.curPage < this.numPages ? this.props.Document.SetNumber(KeyStore.CurPage, this.curPage + 1) : -1;
private get uIButtons() {
- let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().transformDirection(1, 1)[0]);
+ let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale);
return (
<div className="collectionPdfView-buttonTray" key="tray" style={{ transform: `scale(${scaling}, ${scaling})` }}>
<button className="collectionPdfView-backward" onClick={this.onPageBack}>{"<"}</button>
<button className="collectionPdfView-forward" onClick={this.onPageForward}>{">"}</button>
- </div>);
+ </div>
+ );
}
- // "inherited" CollectionView API starts here...
- @observable
- public SelectedDocs: FieldId[] = []
- public active: () => boolean = () => CollectionView.Active(this);
-
- addDocument = (doc: Document, allowDuplicates: boolean): boolean => { return CollectionView.AddDocument(this.props, doc, allowDuplicates); }
- removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); }
-
- specificContextMenu = (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: () => { } });
+ 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: emptyFunction });
}
}
- get collectionViewType(): CollectionViewType { return CollectionViewType.Freeform; }
- get subView(): any { return CollectionView.SubView(this); }
+ private subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => {
+ let props = { ...this.props, ...renderProps };
+ return (
+ <>
+ <CollectionFreeFormView {...props} CollectionView={this} />
+ {this.props.isSelected() ? this.uIButtons : (null)}
+ </>
+ );
+ }
render() {
- return (<div className="collectionPdfView-cont" onContextMenu={this.specificContextMenu}>
- {this.subView}
- {this.props.isSelected() ? this.uIButtons : (null)}
- </div>)
+ return (
+ <CollectionBaseView {...this.props} className="collectionPdfView-cont" onContextMenu={this.onContextMenu}>
+ {this.subView}
+ </CollectionBaseView>
+ );
}
} \ No newline at end of file
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 0ff6c3b40..2c99c9c67 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -1,30 +1,30 @@
-import React = require("react")
+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, COLLECTION_BORDER_WIDTH } from "./CollectionView";
-import { CollectionViewBase } from "./CollectionViewBase";
-import { TextField } from "../../../fields/TextField";
+import { CollectionSubView } from "./CollectionSubView";
// bcz: need to add drag and drop of rows and columns. This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657
@@ -39,7 +39,7 @@ class KeyToggle extends React.Component<{ keyId: string, checked: boolean, toggl
if (field instanceof Key) {
this.key = field;
}
- }))
+ }));
}
render() {
@@ -47,14 +47,14 @@ class KeyToggle extends React.Component<{ keyId: string, checked: boolean, toggl
return (<div key={this.key.Id}>
<input type="checkbox" checked={this.props.checked} onChange={() => this.key && this.props.toggle(this.key)} />
{this.key.Name}
- </div>)
+ </div>);
}
return (null);
}
}
@observer
-export class CollectionSchemaView extends CollectionViewBase {
+export class CollectionSchemaView extends CollectionSubView {
private _mainCont = React.createRef<HTMLDivElement>();
private _startSplitPercent = 0;
private DIVIDER_WIDTH = 4;
@@ -73,25 +73,27 @@ export class CollectionSchemaView extends CollectionViewBase {
renderCell = (rowProps: CellInfo) => {
let props: FieldViewProps = {
- doc: rowProps.value[0],
+ Document: rowProps.value[0],
fieldKey: rowProps.value[1],
- isSelected: () => false,
- select: () => { },
+ ContainingCollectionView: this.props.CollectionView,
+ isSelected: returnFalse,
+ select: emptyFunction,
isTopMost: false,
- bindings: {},
selectOnLoad: false,
- }
+ ScreenToLocalTransform: Transform.Identity,
+ focus: emptyDocFunction,
+ active: returnFalse,
+ onActiveChanged: emptyFunction,
+ };
let contents = (
<FieldView {...props} />
- )
+ );
let reference = React.createRef<HTMLDivElement>();
- let onItemDown = setupDrag(reference, () => props.doc, (containingCollection: CollectionView) => this.props.removeDocument(props.doc));
- let applyToDoc = (doc: Document, value: string) => {
- let script = CompileScript(value, { this: doc }, true);
- if (!script.compiled) {
- return false;
- }
- let field = script();
+ 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;
+ const field = res.result;
if (field instanceof Field) {
doc.Set(props.fieldKey, field);
return true;
@@ -103,33 +105,43 @@ export class CollectionSchemaView extends CollectionViewBase {
}
}
return false;
- }
+ };
return (
- <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} style={{ height: "56px" }} key={props.doc.Id} ref={reference}>
+ <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} style={{ height: "56px" }} key={props.Document.Id} ref={reference}>
<EditableView
display={"inline"}
contents={contents}
height={56}
GetValue={() => {
- let field = props.doc.Get(props.fieldKey);
+ let field = props.Document.Get(props.fieldKey);
if (field && field instanceof Field) {
return field.ToScriptString();
}
return field || "";
}}
SetValue={(value: string) => {
- return applyToDoc(props.doc, value);
+ let script = CompileScript(value, { addReturn: true, params: { this: Document.name } });
+ if (!script.compiled) {
+ return false;
+ }
+ return applyToDoc(props.Document, script.run);
}}
OnFillDown={(value: string) => {
+ let script = CompileScript(value, { addReturn: true, params: { this: Document.name } });
+ if (!script.compiled) {
+ return;
+ }
+ const run = script.run;
+ //TODO This should be able to be refactored to compile the script once
this.props.Document.GetTAsync<ListField<Document>>(this.props.fieldKey, ListField).then((val) => {
if (val) {
- val.Data.forEach(doc => applyToDoc(doc, value));
+ val.Data.forEach(doc => applyToDoc(doc, run));
}
- })
+ });
}}>
</EditableView>
</div>
- )
+ );
}
private getTrProps: ComponentPropsGetterR = (state, rowInfo) => {
@@ -143,12 +155,12 @@ export class CollectionSchemaView extends CollectionViewBase {
that._selectedIndex = rowInfo.index;
if (handleOriginal) {
- handleOriginal()
+ handleOriginal();
}
}),
style: {
- background: rowInfo.index == this._selectedIndex ? "lightGray" : "white",
- //color: rowInfo.index == this._selectedIndex ? "white" : "black"
+ background: rowInfo.index === this._selectedIndex ? "lightGray" : "white",
+ //color: rowInfo.index === this._selectedIndex ? "white" : "black"
}
};
}
@@ -169,22 +181,22 @@ export class CollectionSchemaView extends CollectionViewBase {
this.columns.splice(index, 1);
}
- })
+ });
}
//toggles preview side-panel of schema
@action
toggleExpander = (event: React.ChangeEvent<HTMLInputElement>) => {
this._startSplitPercent = this.splitPercentage;
- if (this._startSplitPercent == this.splitPercentage) {
- this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage == 0 ? 33 : 0);
+ if (this._startSplitPercent === this.splitPercentage) {
+ this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage === 0 ? 33 : 0);
}
}
@computed
get findAllDocumentKeys(): { [id: string]: boolean } {
const docs = this.props.Document.GetList<Document>(this.props.fieldKey, []);
- let keys: { [id: string]: boolean } = {}
+ let keys: { [id: string]: boolean } = {};
if (this._optionsActivated > -1) {
// bcz: ugh. this is untracked since otherwise a large collection of documents will blast the server for all their fields.
// then as each document's fields come back, we update the documents _proxies. Each time we do this, the whole schema will be
@@ -193,7 +205,7 @@ export class CollectionSchemaView extends CollectionViewBase {
// is displayed (unlikely) it won't show up until something else changes.
untracked(() => docs.map(doc => doc.GetAllPrototypes().map(proto => proto._proxies.forEach((val: any, key: string) => keys[key] = false))));
}
- this.columns.forEach(key => keys[key.Id] = true)
+ this.columns.forEach(key => keys[key.Id] = true);
return keys;
}
@@ -206,8 +218,8 @@ export class CollectionSchemaView extends CollectionViewBase {
onDividerUp = (e: PointerEvent): void => {
document.removeEventListener("pointermove", this.onDividerMove);
document.removeEventListener('pointerup', this.onDividerUp);
- if (this._startSplitPercent == this.splitPercentage) {
- this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage == 0 ? 33 : 0);
+ if (this._startSplitPercent === this.splitPercentage) {
+ this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage === 0 ? 33 : 0);
}
}
onDividerDown = (e: React.PointerEvent) => {
@@ -232,20 +244,16 @@ export class CollectionSchemaView extends CollectionViewBase {
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 => {
- return this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling);
- }
- getPreviewTransform = (): Transform => {
- return 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();
}
}
@@ -264,8 +272,9 @@ export class CollectionSchemaView extends CollectionViewBase {
this.newKeyName = e.currentTarget.value;
}
onWheel = (e: React.WheelEvent): void => {
- if (this.props.active())
+ if (this.props.active()) {
e.stopPropagation();
+ }
}
@observable _optionsActivated: number = 0;
@@ -291,29 +300,32 @@ export class CollectionSchemaView extends CollectionViewBase {
let doc: any = selected ? selected.Get(new Key(this.previewScript)) : undefined;
// let doc = CompileScript(this.previewScript, { this: selected }, true)();
- let content = this._selectedIndex == -1 || !selected ? (null) : (
+ let content = this._selectedIndex === -1 || !selected ? (null) : (
<Measure onResize={this.setScaling}>
{({ measureRef }) =>
<div className="collectionSchemaView-content" ref={measureRef}>
- {doc instanceof Document ? <DocumentView Document={doc}
- AddDocument={this.props.addDocument} RemoveDocument={this.props.removeDocument}
- isTopMost={false}
- SelectOnLoad={false}
- ScreenToLocalTransform={this.getPreviewTransform}
- ContentScaling={this.getContentScaling}
- PanelWidth={this.getPanelWidth}
- PanelHeight={this.getPanelHeight}
- ContainingCollectionView={this.props.CollectionView}
- focus={this.focusDocument}
- /> : null}
+ {doc instanceof Document ?
+ <DocumentView Document={doc}
+ addDocument={this.props.addDocument} removeDocument={this.props.removeDocument}
+ isTopMost={false}
+ opacity={1}
+ selectOnLoad={false}
+ ScreenToLocalTransform={this.getPreviewTransform}
+ ContentScaling={this.getContentScaling}
+ PanelWidth={this.getPanelWidth}
+ PanelHeight={this.getPanelHeight}
+ ContainingCollectionView={this.props.CollectionView}
+ focus={emptyDocFunction}
+ parentActive={this.props.active}
+ onActiveChanged={this.props.onActiveChanged} /> : null}
<input value={this.previewScript} onChange={this.onPreviewScriptChange}
style={{ position: 'absolute', bottom: '0px' }} />
</div>
}
</Measure>
- )
- let dividerDragger = this.splitPercentage == 0 ? (null) :
- <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} style={{ width: `${this.DIVIDER_WIDTH}px` }} />
+ );
+ let dividerDragger = this.splitPercentage === 0 ? (null) :
+ <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} style={{ width: `${this.DIVIDER_WIDTH}px` }} />;
//options button and menu
let optionsMenu = !this.props.active() ? (null) : (<Flyout
@@ -322,12 +334,11 @@ export class CollectionSchemaView extends CollectionViewBase {
<div id="schema-options-header"><h5><b>Options</b></h5></div>
<div id="options-flyout-div">
<h6 className="schema-options-subHeader">Preview Window</h6>
- <div id="preview-schema-checkbox-div"><input type="checkbox" key={"Show Preview"} checked={this.splitPercentage != 0} onChange={this.toggleExpander} /> Show Preview </div>
+ <div id="preview-schema-checkbox-div"><input type="checkbox" key={"Show Preview"} checked={this.splitPercentage !== 0} onChange={this.toggleExpander} /> Show Preview </div>
<h6 className="schema-options-subHeader" >Displayed Columns</h6>
<ul id="schema-col-checklist" >
- {Array.from(Object.keys(allKeys)).map(item => {
- return (<KeyToggle checked={allKeys[item]} key={item} keyId={item} toggle={this.toggleKey} />)
- })}
+ {Array.from(Object.keys(allKeys)).map(item =>
+ (<KeyToggle checked={allKeys[item]} key={item} keyId={item} toggle={this.toggleKey} />))}
</ul>
<input value={this.newKeyName} onChange={this.newKeyChange} />
<button onClick={this.addColumn}><FontAwesomeIcon style={{ color: "white" }} icon="plus" size="lg" /></button>
@@ -338,7 +349,7 @@ export class CollectionSchemaView extends CollectionViewBase {
</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 }) =>
@@ -369,6 +380,6 @@ export class CollectionSchemaView extends CollectionViewBase {
{optionsMenu}
</div>
</div >
- )
+ );
}
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionSubView.tsx
index 316d20c9d..d3d69b1af 100644
--- a/src/client/views/collections/CollectionViewBase.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -4,43 +4,35 @@ import { ListField } from "../../../fields/ListField";
import React = require("react");
import { KeyStore } from "../../../fields/KeyStore";
import { FieldWaiting, Opt } from "../../../fields/Field";
-import { undoBatch } from "../../util/UndoManager";
+import { undoBatch, UndoManager } from "../../util/UndoManager";
import { DragManager } from "../../util/DragManager";
import { Documents, DocumentOptions } from "../../documents/Documents";
-import { Key } from "../../../fields/Key";
-import { Transform } from "../../util/Transform";
-import { CollectionView } from "./CollectionView";
import { RouteStore } from "../../../server/RouteStore";
import { TupleField } from "../../../fields/TupleField";
import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
import { NumberField } from "../../../fields/NumberField";
-import request = require("request");
import { ServerUtils } from "../../../server/ServerUtil";
import { Server } from "../../Server";
+import { FieldViewProps } from "../nodes/FieldView";
+import * as rp from 'request-promise';
+import { emptyFunction } from "../../../Utils";
+import { CollectionView } from "./CollectionView";
+import { CollectionPDFView } from "./CollectionPDFView";
+import { CollectionVideoView } from "./CollectionVideoView";
-export interface CollectionViewProps {
- fieldKey: Key;
- Document: Document;
- ScreenToLocalTransform: () => Transform;
- isSelected: () => boolean;
- isTopMost: boolean;
- select: (ctrlPressed: boolean) => void;
- bindings: any;
- panelWidth: () => number;
- panelHeight: () => number;
- focus: (doc: Document) => void;
+export interface CollectionViewProps extends FieldViewProps {
+ addDocument: (document: Document, allowDuplicates?: boolean) => boolean;
+ removeDocument: (document: Document) => boolean;
+ moveDocument: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
}
export interface SubCollectionViewProps extends CollectionViewProps {
- active: () => boolean;
- addDocument: (doc: Document, allowDuplicates: boolean) => boolean;
- removeDocument: (doc: Document) => boolean;
- CollectionView: CollectionView;
+ CollectionView: CollectionView | CollectionPDFView | CollectionVideoView;
}
export type CursorEntry = TupleField<[string, string], [number, number]>;
-export class CollectionViewBase extends React.Component<SubCollectionViewProps> {
+export class CollectionSubView extends React.Component<SubCollectionViewProps> {
private dropDisposer?: DragManager.DragDropDisposer;
protected createDropTarget = (ele: HTMLDivElement) => {
if (this.dropDisposer) {
@@ -71,8 +63,8 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
let entry = new TupleField<[string, string], [number, number]>([textInfo, position]);
cursors.push(entry);
}
- }))
- })
+ }));
+ });
}
}
@@ -80,13 +72,28 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
@action
protected drop(e: Event, de: DragManager.DropEvent): boolean {
if (de.data instanceof DragManager.DocumentDragData) {
- if (de.data.aliasOnDrop) {
+ if (de.data.aliasOnDrop || de.data.copyOnDrop) {
[KeyStore.Width, KeyStore.Height, KeyStore.CurPage].map(key =>
- de.data.draggedDocuments.GetTAsync(key, NumberField, (f: Opt<NumberField>) => f ? de.data.droppedDocument.SetNumber(key, f.Data) : null));
+ de.data.draggedDocuments.map((draggedDocument: Document, i: number) =>
+ draggedDocument.GetTAsync(key, NumberField, (f: Opt<NumberField>) => f ? de.data.droppedDocuments[i].SetNumber(key, f.Data) : null)));
}
- let added = de.data.droppedDocuments.reduce((added, d) => this.props.addDocument(d, false), true);
- if (added && de.data.removeDocument && !de.data.aliasOnDrop) {
- de.data.removeDocument(this.props.CollectionView);
+ let added = false;
+ if (de.data.aliasOnDrop || de.data.copyOnDrop) {
+ 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) => {
+ let moved = move(d, this.props.Document, this.props.addDocument);
+ return moved || added;
+ }, false);
+ } else {
+ added = de.data.droppedDocuments.reduce((added: boolean, d) => {
+ let moved = this.props.addDocument(d);
+ return moved || added;
+ }, false);
}
e.stopPropagation();
return added;
@@ -94,8 +101,8 @@ export class CollectionViewBase 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;
}
@@ -107,6 +114,11 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
}
if (type.indexOf("pdf") !== -1) {
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')) {
@@ -121,7 +133,7 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
alias.SetNumber(KeyStore.Height, options.height || options.width || 300);
this.props.addDocument(alias, false);
}
- })
+ });
return undefined;
}
ctor = Documents.WebDocument;
@@ -130,20 +142,19 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
return ctor ? ctor(path, options) : undefined;
}
+ @undoBatch
@action
protected onDrop(e: React.DragEvent, options: DocumentOptions): void {
- let that = this;
-
let html = e.dataTransfer.getData("text/html");
let text = e.dataTransfer.getData("text/plain");
if (text && text.startsWith("<div")) {
return;
}
- e.stopPropagation()
- e.preventDefault()
+ e.stopPropagation();
+ e.preventDefault();
- if (html && html.indexOf("<img") != 0 && !html.startsWith("<a")) {
+ if (html && html.indexOf("<img") !== 0 && !html.startsWith("<a")) {
console.log("not good");
let htmlDoc = Documents.HtmlDocument(html, { ...options, width: 300, height: 300 });
htmlDoc.SetText(KeyStore.DocumentText, text);
@@ -151,58 +162,65 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
return;
}
+ let batch = UndoManager.StartBatch("collection view drop");
+ let promises: Promise<void>[] = [];
+ // tslint:disable-next-line:prefer-for-of
for (let i = 0; i < e.dataTransfer.items.length; i++) {
const upload = window.location.origin + RouteStore.upload;
let item = e.dataTransfer.items[i];
- if (item.kind === "string" && item.type.indexOf("uri") != -1) {
- e.dataTransfer.items[i].getAsString(action((s: string) => {
- let document: Document;
- request.head(ServerUtils.prepend(RouteStore.corsProxy + "/" + s), (err, res, body) => {
- let type = res.headers["content-type"];
+ if (item.kind === "string" && item.type.indexOf("uri") !== -1) {
+ let str: string;
+ 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, s, { ...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));
}
});
- // this.props.addDocument(Documents.WebDocument(s, { ...options, width: 300, height: 300 }), false)
- }))
+ promises.push(prom);
}
- let type = item.type
- if (item.kind == "file") {
+ let type = item.type;
+ if (item.kind === "file") {
let file = item.getAsFile();
- let formData = new FormData()
+ let formData = new FormData();
if (file) {
- formData.append('file', file)
+ formData.append('file', file);
}
+ let dropFileName = file ? file.name : "-empty-";
- fetch(upload, {
+ let prom = fetch(upload, {
method: 'POST',
body: formData
- }).then((res: Response) => {
- return res.json()
- }).then(json => {
- json.map((file: any) => {
- let path = window.location.origin + file
- runInAction(() => {
- let doc = this.getDocumentFromType(type, path, { ...options, nativeWidth: 300, width: 300 })
+ }).then(async (res: Response) => {
+ (await res.json()).map(action((file: any) => {
+ let path = window.location.origin + file;
+ let docPromise = this.getDocumentFromType(type, path, { ...options, nativeWidth: 600, width: 300, title: dropFileName });
- let docs = that.props.Document.GetT(KeyStore.Data, ListField);
- if (docs != FieldWaiting) {
+ docPromise.then(action((doc?: Document) => {
+ let docs = this.props.Document.GetT(KeyStore.Data, ListField);
+ if (docs !== FieldWaiting) {
if (!docs) {
docs = new ListField<Document>();
- that.props.Document.Set(KeyStore.Data, docs)
+ this.props.Document.Set(KeyStore.Data, docs);
}
if (doc) {
docs.Data.push(doc);
}
}
- })
- })
- })
+ }));
+ }));
+ });
+ promises.push(prom);
}
}
+
+ if (promises.length) {
+ 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 5a14aa54d..973eead97 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -1,64 +1,64 @@
-@import "../global_variables";
-#body {
+@import "../globalCssVariables";
+
+.collectionTreeView-dropTarget {
+ border-width: $COLLECTION_BORDER_WIDTH;
+ border-color: transparent;
+ border-style: solid;
+ border-radius: $border-radius;
+ box-sizing: border-box;
+ height: 100%;
padding: 20px;
padding-left: 10px;
padding-right: 0px;
background: $light-color-secondary;
font-size: 13px;
overflow: scroll;
-}
-ul {
- list-style: none;
- padding-left: 20px;
-}
+ ul {
+ list-style: none;
+ padding-left: 20px;
+ }
-li {
- margin: 5px 0;
-}
+ li {
+ margin: 5px 0;
+ }
-.collection-child {
- margin-top: 10px;
- margin-bottom: 10px;
-}
+ .collection-child {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ }
-.no-indent {
- padding-left: 0;
-}
+ .no-indent {
+ padding-left: 0;
+ }
-.bullet {
- width: 1.5em;
- display: inline-block;
- color: $intermediate-color;
-}
+ .bullet {
+ width: 1.5em;
+ display: inline-block;
+ color: $intermediate-color;
+ }
-.coll-title {
- font-size: 24px;
- margin-bottom: 20px;
-}
+ .coll-title {
+ font-size: 24px;
+ margin-bottom: 20px;
+ }
-.collectionTreeView-dropTarget {
- border: 0px solid transparent;
- border-radius: $border-radius;
- box-sizing: border-box;
- height: 100%;
-}
+ .docContainer {
+ display: inline-table;
+ }
-.docContainer {
- display: inline-table;
-}
+ .docContainer:hover {
+ .delete-button {
+ display: inline;
+ width: auto;
+ }
+ }
-.docContainer:hover {
.delete-button {
+ color: $intermediate-color;
+ float: right;
+ margin-left: 15px;
+ margin-top: 3px;
display: inline;
- width: auto;
}
-}
-
-.delete-button {
- color: $intermediate-color;
- float: right;
- margin-left: 15px;
- margin-top: 3px;
- display: inline;
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 70790af18..51a02fc25 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -7,17 +7,21 @@ import { Document } from "../../../fields/Document";
import { FieldWaiting } from "../../../fields/Field";
import { KeyStore } from "../../../fields/KeyStore";
import { ListField } from "../../../fields/ListField";
-import { setupDrag } from "../../util/DragManager";
+import { SetupDrag, DragManager } from "../../util/DragManager";
import { EditableView } from "../EditableView";
import "./CollectionTreeView.scss";
-import { CollectionView, COLLECTION_BORDER_WIDTH } from "./CollectionView";
-import { CollectionViewBase } from "./CollectionViewBase";
-import React = require("react")
+import { CollectionView } from "./CollectionView";
+import * as globalCssVariables from "../../views/globalCssVariables.scss";
+import { CollectionSubView } from "./CollectionSubView";
+import React = require("react");
+import { props } from 'bluebird';
export interface TreeViewProps {
document: Document;
deleteDoc: (doc: Document) => void;
+ moveDocument: DragManager.MoveFunction;
+ copyOnDrag: boolean;
}
export enum BulletType {
@@ -48,6 +52,16 @@ class TreeView extends React.Component<TreeViewProps> {
}
}
+ @action
+ move: DragManager.MoveFunction = (document, target, addDoc) => {
+ if (this.props.document === target) {
+ return true;
+ }
+ //TODO This should check if it was removed
+ this.remove(document);
+ return addDoc(document);
+ }
+
renderBullet(type: BulletType) {
let onClicked = action(() => this._collapsed = !this._collapsed);
let bullet: IconProp | undefined = undefined;
@@ -55,7 +69,7 @@ class TreeView extends React.Component<TreeViewProps> {
case BulletType.Collapsed: bullet = "caret-right"; break;
case BulletType.Collapsible: bullet = "caret-down"; break;
}
- return <div className="bullet" onClick={onClicked}>{bullet ? <FontAwesomeIcon icon={bullet} /> : ""} </div>
+ return <div className="bullet" onClick={onClicked}>{bullet ? <FontAwesomeIcon icon={bullet} /> : ""} </div>;
}
/**
@@ -63,7 +77,7 @@ class TreeView extends React.Component<TreeViewProps> {
*/
renderTitle() {
let reference = React.createRef<HTMLDivElement>();
- let onItemDown = setupDrag(reference, () => this.props.document, (containingCollection: CollectionView) => this.props.deleteDoc(this.props.document));
+ let onItemDown = SetupDrag(reference, () => this.props.document, this.props.moveDocument, this.props.copyOnDrag);
let editableView = (titleString: string) =>
(<EditableView
display={"inline"}
@@ -79,20 +93,19 @@ class TreeView extends React.Component<TreeViewProps> {
<div className="docContainer" ref={reference} onPointerDown={onItemDown}>
{editableView(this.props.document.Title)}
<div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div>
- </div >)
+ </div >);
}
render() {
let bulletType = BulletType.List;
let childElements: JSX.Element | undefined = undefined;
-
var children = this.props.document.GetT<ListField<Document>>(KeyStore.Data, ListField);
if (children && children !== FieldWaiting) { // add children for a collection
if (!this._collapsed) {
bulletType = BulletType.Collapsible;
childElements = <ul>
- {children.Data.map(value => <TreeView key={value.Id} document={value} deleteDoc={this.remove} />)}
- </ul>
+ {children.Data.map(value => <TreeView key={value.Id} document={value} deleteDoc={this.remove} moveDocument={this.move} copyOnDrag={this.props.copyOnDrag} />)}
+ </ul >;
}
else bulletType = BulletType.Collapsed;
}
@@ -102,12 +115,12 @@ class TreeView extends React.Component<TreeViewProps> {
{this.renderTitle()}
{childElements ? childElements : (null)}
</li>
- </div>
+ </div>;
}
}
@observer
-export class CollectionTreeView extends CollectionViewBase {
+export class CollectionTreeView extends CollectionSubView {
@action
remove = (document: Document) => {
@@ -118,14 +131,15 @@ export class CollectionTreeView extends CollectionViewBase {
}
render() {
- var children = this.props.Document.GetT<ListField<Document>>(KeyStore.Data, ListField);
+ let children = this.props.Document.GetT<ListField<Document>>(KeyStore.Data, ListField);
+ let copyOnDrag = this.props.Document.GetBoolean(KeyStore.CopyDraggedItems, false);
let childrenElement = !children || children === FieldWaiting ? (null) :
(children.Data.map(value =>
- <TreeView document={value} key={value.Id} deleteDoc={this.remove} />)
- )
+ <TreeView document={value} key={value.Id} deleteDoc={this.remove} moveDocument={this.props.moveDocument} copyOnDrag={copyOnDrag} />)
+ );
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.scss b/src/client/views/collections/CollectionVideoView.scss
index cbb981b13..ed56ad268 100644
--- a/src/client/views/collections/CollectionVideoView.scss
+++ b/src/client/views/collections/CollectionVideoView.scss
@@ -3,6 +3,8 @@
width: 100%;
height: 100%;
position: absolute;
+ top: 0;
+ left:0;
}
.collectionVideoView-time{
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index 470a853e3..29fb342c6 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -1,17 +1,17 @@
-import { action, computed, observable, trace } from "mobx";
+import { action, observable, trace } from "mobx";
import { observer } from "mobx-react";
-import { Document } from "../../../fields/Document";
import { KeyStore } from "../../../fields/KeyStore";
import { ContextMenu } from "../ContextMenu";
-import { CollectionView, CollectionViewType } from "./CollectionView";
-import { CollectionViewProps } from "./CollectionViewBase";
+import { CollectionViewType, CollectionBaseView, CollectionRenderProps } from "./CollectionBaseView";
import React = require("react");
-import { FieldId } from "../../../fields/Field";
-import "./CollectionVideoView.scss"
+import "./CollectionVideoView.scss";
+import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
+import { FieldView, FieldViewProps } from "../nodes/FieldView";
+import { emptyFunction } from "../../../Utils";
@observer
-export class CollectionVideoView extends React.Component<CollectionViewProps> {
+export class CollectionVideoView extends React.Component<FieldViewProps> {
private _intervalTimer: any = undefined;
private _player: HTMLVideoElement | undefined = undefined;
@@ -19,12 +19,10 @@ export class CollectionVideoView extends React.Component<CollectionViewProps> {
@observable _isPlaying: boolean = false;
public static LayoutString(fieldKey: string = "DataKey") {
- return `<${CollectionVideoView.name} Document={Document}
- ScreenToLocalTransform={ScreenToLocalTransform} fieldKey={${fieldKey}} panelWidth={PanelWidth} panelHeight={PanelHeight} isSelected={isSelected} select={select} bindings={bindings}
- isTopMost={isTopMost} SelectOnLoad={selectOnLoad} BackgroundView={BackgroundView} focus={focus}/>`;
+ return FieldView.LayoutString(CollectionVideoView, fieldKey);
}
private get uIButtons() {
- let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().transformDirection(1, 1)[0]);
+ let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale);
return ([
<div className="collectionVideoView-time" key="time" onPointerDown={this.onResetDown} style={{ transform: `scale(${scaling}, ${scaling})` }}>
<span>{"" + Math.round(this._currentTimecode)}</span>
@@ -42,7 +40,7 @@ export class CollectionVideoView extends React.Component<CollectionViewProps> {
@action
mainCont = (ele: HTMLDivElement | null) => {
if (ele) {
- this._player = ele!.getElementsByTagName("video")[0];
+ this._player = ele.getElementsByTagName("video")[0];
if (this.props.Document.GetNumber(KeyStore.CurPage, -1) >= 0) {
this._currentTimecode = this.props.Document.GetNumber(KeyStore.CurPage, -1);
}
@@ -60,7 +58,7 @@ export class CollectionVideoView extends React.Component<CollectionViewProps> {
@action
updateTimecode = () => {
if (this._player) {
- if ((this._player as any).AHackBecauseSomethingResetsTheVideoToZero != -1) {
+ if ((this._player as any).AHackBecauseSomethingResetsTheVideoToZero !== -1) {
this._player.currentTime = (this._player as any).AHackBecauseSomethingResetsTheVideoToZero;
(this._player as any).AHackBecauseSomethingResetsTheVideoToZero = -1;
} else {
@@ -101,30 +99,27 @@ export class CollectionVideoView extends React.Component<CollectionViewProps> {
}
- // "inherited" CollectionView API starts here...
-
- @observable
- public SelectedDocs: FieldId[] = []
- public active: () => boolean = () => CollectionView.Active(this);
-
- addDocument = (doc: Document, allowDuplicates: boolean): boolean => { return CollectionView.AddDocument(this.props, doc, allowDuplicates); }
- removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); }
-
- specificContextMenu = (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: () => { } });
+ 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: emptyFunction });
}
}
- get collectionViewType(): CollectionViewType { return CollectionViewType.Freeform; }
- get subView(): any { return CollectionView.SubView(this); }
-
+ private subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => {
+ let props = { ...this.props, ...renderProps };
+ return (
+ <>
+ <CollectionFreeFormView {...props} CollectionView={this} />
+ {this.props.isSelected() ? this.uIButtons : (null)}
+ </>
+ );
+ }
render() {
trace();
- return (<div className="collectionVideoView-cont" ref={this.mainCont} onContextMenu={this.specificContextMenu}>
- {this.subView}
- {this.props.isSelected() ? this.uIButtons : (null)}
- </div>)
+ return (
+ <CollectionBaseView {...this.props} className="collectionVideoView-cont" contentRef={this.mainCont} onContextMenu={this.onContextMenu}>
+ {this.subView}
+ </CollectionBaseView>);
}
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 014aa1d8f..1f69a69e8 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,159 +1,46 @@
-import { action, computed, observable } from "mobx";
-import { observer } from "mobx-react";
-import { Document } from "../../../fields/Document";
-import { ListField } from "../../../fields/ListField";
-import { SelectionManager } from "../../util/SelectionManager";
-import { ContextMenu } from "../ContextMenu";
-import React = require("react");
-import { KeyStore } from "../../../fields/KeyStore";
-import { NumberField } from "../../../fields/NumberField";
-import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
-import { CollectionDockingView } from "./CollectionDockingView";
-import { CollectionSchemaView } from "./CollectionSchemaView";
-import { CollectionViewProps } from "./CollectionViewBase";
-import { CollectionTreeView } from "./CollectionTreeView";
-import { Field, FieldId, FieldWaiting } from "../../../fields/Field";
-import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
-
-export enum CollectionViewType {
- Invalid,
- Freeform,
- Schema,
- Docking,
- Tree
-}
-
-export const COLLECTION_BORDER_WIDTH = 1;
+import * as React from 'react';
+import { FieldViewProps, FieldView } from '../nodes/FieldView';
+import { CollectionBaseView, CollectionViewType, CollectionRenderProps } from './CollectionBaseView';
+import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+import { CollectionSchemaView } from './CollectionSchemaView';
+import { CollectionDockingView } from './CollectionDockingView';
+import { CollectionTreeView } from './CollectionTreeView';
+import { ContextMenu } from '../ContextMenu';
+import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
+import { KeyStore } from '../../../fields/KeyStore';
+import { observer } from 'mobx-react';
+import { undoBatch } from '../../util/UndoManager';
@observer
-export class CollectionView extends React.Component<CollectionViewProps> {
-
- public static LayoutString(fieldKey: string = "DataKey") {
- return `<${CollectionView.name} Document={Document}
- ScreenToLocalTransform={ScreenToLocalTransform} fieldKey={${fieldKey}} panelWidth={PanelWidth} panelHeight={PanelHeight} isSelected={isSelected} select={select} bindings={bindings}
- isTopMost={isTopMost} SelectOnLoad={selectOnLoad} BackgroundView={BackgroundView} focus={focus}/>`;
- }
-
- @observable
- public SelectedDocs: FieldId[] = [];
- public active: () => boolean = () => CollectionView.Active(this);
- addDocument = (doc: Document, allowDuplicates: boolean): boolean => { return CollectionView.AddDocument(this.props, doc, allowDuplicates); }
- removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); }
- get subView() { return CollectionView.SubView(this); }
-
- public static Active(self: CollectionView): boolean {
- var isSelected = self.props.isSelected();
- var childSelected = SelectionManager.SelectedDocuments().some(view => view.props.ContainingCollectionView == self);
- var topMost = self.props.isTopMost;
- return isSelected || childSelected || topMost;
- }
-
- static createsCycle(documentToAdd: Document, containerDocument: Document): boolean {
- let data = documentToAdd.GetList<Document>(KeyStore.Data, []);
- for (let i = 0; i < data.length; i++) {
- if (CollectionView.createsCycle(data[i], containerDocument))
- return true;
- }
- let annots = documentToAdd.GetList<Document>(KeyStore.Annotations, []);
- for (let i = 0; i < annots.length; i++) {
- if (CollectionView.createsCycle(annots[i], containerDocument))
- return true;
- }
- for (let containerProto: any = containerDocument; containerProto && containerProto != FieldWaiting; containerProto = containerProto.GetPrototype()) {
- if (containerProto.Id == documentToAdd.Id)
- return true;
- }
- return false;
- }
-
- @action
- public static AddDocument(props: CollectionViewProps, doc: Document, allowDuplicates: boolean): boolean {
- var curPage = props.Document.GetNumber(KeyStore.CurPage, -1);
- doc.SetOnPrototype(KeyStore.Page, new NumberField(curPage));
- if (curPage >= 0) {
- doc.SetOnPrototype(KeyStore.AnnotationOn, props.Document);
- }
- if (props.Document.Get(props.fieldKey) instanceof Field) {
- //TODO This won't create the field if it doesn't already exist
- const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>())
- if (!CollectionView.createsCycle(doc, props.Document)) {
- if (!value.some(v => v.Id == doc.Id) || allowDuplicates)
- value.push(doc);
- }
- else
- return false;
- } else {
- let proto = props.Document.GetPrototype();
- if (!proto || proto == FieldWaiting || !CollectionView.createsCycle(proto, doc)) {
- props.Document.SetOnPrototype(props.fieldKey, new ListField([doc]));
- }
- else
- return false;
- }
- return true;
- }
-
- @action
- public static RemoveDocument(props: CollectionViewProps, doc: Document): boolean {
- //TODO This won't create the field if it doesn't already exist
- const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>())
- let index = -1;
- for (let i = 0; i < value.length; i++) {
- if (value[i].Id == doc.Id) {
- index = i;
- break;
- }
- }
- doc.GetTAsync(KeyStore.AnnotationOn, Document).then((annotationOn) => {
- if (annotationOn == props.Document) {
- doc.Set(KeyStore.AnnotationOn, undefined, true);
- }
- })
-
- if (index !== -1) {
- value.splice(index, 1)
-
- //SelectionManager.DeselectAll()
- ContextMenu.Instance.clearItems()
- return true;
- }
- return false
- }
-
- get collectionViewType(): CollectionViewType {
- let Document = this.props.Document;
- let viewField = Document.GetT(KeyStore.ViewType, NumberField);
- if (viewField === FieldWaiting) {
- return CollectionViewType.Invalid;
- } else if (viewField) {
- return viewField.Data;
- } else {
- return CollectionViewType.Freeform;
- }
- }
-
- specificContextMenu = (e: React.MouseEvent): void => {
- if (!e.isPropagationStopped() && this.props.Document.Id != CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
- ContextMenu.Instance.addItem({ description: "Freeform", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Freeform) })
- ContextMenu.Instance.addItem({ description: "Schema", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Schema) })
- ContextMenu.Instance.addItem({ description: "Treeview", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Tree) })
+export class CollectionView extends React.Component<FieldViewProps> {
+ public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(CollectionView, fieldStr); }
+
+ private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
+ let props = { ...this.props, ...renderProps };
+ switch (type) {
+ case CollectionViewType.Schema: return (<CollectionSchemaView {...props} CollectionView={this} />);
+ case CollectionViewType.Docking: return (<CollectionDockingView {...props} CollectionView={this} />);
+ case CollectionViewType.Tree: return (<CollectionTreeView {...props} CollectionView={this} />);
+ case CollectionViewType.Freeform:
+ default:
+ return (<CollectionFreeFormView {...props} CollectionView={this} />);
}
+ return (null);
}
- public static SubView(self: CollectionView) {
- let subProps = { ...self.props, addDocument: self.addDocument, removeDocument: self.removeDocument, active: self.active, CollectionView: self }
- switch (self.collectionViewType) {
- case CollectionViewType.Freeform: return (<CollectionFreeFormView {...subProps} />)
- case CollectionViewType.Schema: return (<CollectionSchemaView {...subProps} />)
- case CollectionViewType.Docking: return (<CollectionDockingView {...subProps} />)
- case CollectionViewType.Tree: return (<CollectionTreeView {...subProps} />)
+ onContextMenu = (e: React.MouseEvent): void => {
+ if (!e.isPropagationStopped() && this.props.Document.Id !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
+ ContextMenu.Instance.addItem({ description: "Freeform", event: undoBatch(() => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Freeform)) });
+ ContextMenu.Instance.addItem({ description: "Schema", event: undoBatch(() => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Schema)) });
+ ContextMenu.Instance.addItem({ description: "Treeview", event: undoBatch(() => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Tree)) });
}
- return (null);
}
render() {
- return (<div className="collectionView-cont" onContextMenu={this.specificContextMenu}>
- {this.subView}
- </div>)
+ return (
+ <CollectionBaseView {...this.props} onContextMenu={this.onContextMenu}>
+ {this.SubView}
+ </CollectionBaseView>
+ );
}
-}
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index e84f0c5ad..081b3eb6c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -23,15 +23,15 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
let l = this.props.LinkDocs;
let a = this.props.A;
let b = this.props.B;
- let x1 = a.GetNumber(KeyStore.X, 0) + a.GetNumber(KeyStore.Width, 0) / 2;
- let y1 = a.GetNumber(KeyStore.Y, 0) + a.GetNumber(KeyStore.Height, 0) / 2;
- let x2 = b.GetNumber(KeyStore.X, 0) + b.GetNumber(KeyStore.Width, 0) / 2;
- let y2 = b.GetNumber(KeyStore.Y, 0) + b.GetNumber(KeyStore.Height, 0) / 2;
+ let x1 = a.GetNumber(KeyStore.X, 0) + (a.GetBoolean(KeyStore.Minimized, false) ? 5 : a.GetNumber(KeyStore.Width, 0) / 2);
+ let y1 = a.GetNumber(KeyStore.Y, 0) + (a.GetBoolean(KeyStore.Minimized, false) ? 5 : a.GetNumber(KeyStore.Height, 0) / 2);
+ let x2 = b.GetNumber(KeyStore.X, 0) + (b.GetBoolean(KeyStore.Minimized, false) ? 5 : b.GetNumber(KeyStore.Width, 0) / 2);
+ let y2 = b.GetNumber(KeyStore.Y, 0) + (b.GetBoolean(KeyStore.Minimized, false) ? 5 : b.GetNumber(KeyStore.Height, 0) / 2);
return (
<line key={Utils.GenerateGuid()} className="collectionfreeformlinkview-linkLine" onPointerDown={this.onPointerDown}
style={{ strokeWidth: `${l.length * 5}` }}
x1={`${x1}`} y1={`${y1}`}
x2={`${x2}`} y2={`${y2}`} />
- )
+ );
}
} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
index 4341c82f7..30e158603 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
@@ -1,6 +1,8 @@
.collectionfreeformlinksview-svgCanvas{
transform: translate(-10000px,-10000px);
position: absolute;
+ top: 0;
+ left: 0;
width: 20000px;
height: 20000px;
pointer-events: none;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index eb20b3100..2f684a54e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -1,72 +1,80 @@
-import { computed, reaction, runInAction, trace } from "mobx";
+import { computed, IReactionDisposer, reaction } from "mobx";
import { observer } from "mobx-react";
import { Document } from "../../../../fields/Document";
-import { FieldWaiting } from "../../../../fields/Field";
import { KeyStore } from "../../../../fields/KeyStore";
import { ListField } from "../../../../fields/ListField";
import { Utils } from "../../../../Utils";
import { DocumentManager } from "../../../util/DocumentManager";
import { DocumentView } from "../../nodes/DocumentView";
-import { CollectionViewProps } from "../CollectionViewBase";
+import { CollectionViewProps } from "../CollectionSubView";
import "./CollectionFreeFormLinksView.scss";
import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView";
import React = require("react");
-import v5 = require("uuid/v5");
@observer
export class CollectionFreeFormLinksView extends React.Component<CollectionViewProps> {
+ _brushReactionDisposer?: IReactionDisposer;
componentDidMount() {
- reaction(() => {
- return DocumentManager.Instance.getAllDocumentViews(this.props.Document).map(dv => dv.props.Document.GetNumber(KeyStore.X, 0))
- }, () => {
- let views = DocumentManager.Instance.getAllDocumentViews(this.props.Document);
- for (let i = 0; i < views.length; i++) {
- for (let j = i + 1; j < views.length; j++) {
- let srcDoc = views[j].props.Document;
- let dstDoc = views[i].props.Document;
- let x1 = srcDoc.GetNumber(KeyStore.X, 0);
- let x1w = srcDoc.GetNumber(KeyStore.Width, -1);
- let x2 = dstDoc.GetNumber(KeyStore.X, 0);
- let x2w = dstDoc.GetNumber(KeyStore.Width, -1);
- if (x1w < 0 || x2w < 0)
- continue;
- dstDoc.GetTAsync(KeyStore.Prototype, Document).then((protoDest) =>
- srcDoc.GetTAsync(KeyStore.Prototype, Document).then((protoSrc) => runInAction(() => {
- let dstTarg = (protoDest ? protoDest : dstDoc);
- let srcTarg = (protoSrc ? protoSrc : srcDoc);
- let findBrush = (field: ListField<Document>) => field.Data.findIndex(brush => {
- let bdocs = brush.GetList(KeyStore.BrushingDocs, [] as Document[]);
- return (bdocs.length == 0 || (bdocs[0] == dstTarg && bdocs[1] == srcTarg) || (bdocs[0] == srcTarg && bdocs[1] == dstTarg))
- });
- let brushAction = (field: ListField<Document>) => {
- let found = findBrush(field);
- if (found != -1)
- field.Data.splice(found, 1);
- };
- if (Math.abs(x1 + x1w - x2) < 20 || Math.abs(x2 + x2w - x1) < 20) {
- let linkDoc: Document = new Document();
- linkDoc.SetText(KeyStore.Title, "Histogram Brush");
- linkDoc.SetText(KeyStore.LinkDescription, "Brush between " + srcTarg.Title + " and " + dstTarg.Title);
- linkDoc.SetData(KeyStore.BrushingDocs, [dstTarg, srcTarg], ListField);
-
- brushAction = brushAction = (field: ListField<Document>) => (findBrush(field) == -1) && field.Data.push(linkDoc);
+ this._brushReactionDisposer = reaction(() => this.props.Document.GetList<Document>(this.props.fieldKey, []).map(doc => doc.GetNumber(KeyStore.X, 0)),
+ () => {
+ 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];
+ 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);
+ let x2w = dstDoc.GetNumber(KeyStore.Width, -1);
+ if (x1w < 0 || x2w < 0 || i === j) {
+ continue;
+ }
+ let dstTarg = dstDoc;
+ let srcTarg = srcDoc;
+ let findBrush = (field: ListField<Document>) => field.Data.findIndex(brush => {
+ let bdocs = brush ? brush.GetList(KeyStore.BrushingDocs, [] as Document[]) : [];
+ return (bdocs.length && ((bdocs[0] === dstTarg && bdocs[1] === srcTarg)) ? true : false);
+ });
+ let brushAction = (field: ListField<Document>) => {
+ let found = findBrush(field);
+ if (found !== -1) {
+ console.log("REMOVE BRUSH " + srcTarg.Title + " " + dstTarg.Title);
+ field.Data.splice(found, 1);
}
- dstTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, brushAction);
- srcTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, brushAction);
+ };
+ if (Math.abs(x1 + x1w - x2) < 20) {
+ let linkDoc: Document = new Document();
+ linkDoc.SetText(KeyStore.Title, "Histogram Brush");
+ linkDoc.SetText(KeyStore.LinkDescription, "Brush between " + srcTarg.Title + " and " + dstTarg.Title);
+ linkDoc.SetData(KeyStore.BrushingDocs, [dstTarg, srcTarg], ListField);
+
+ brushAction = (field: ListField<Document>) => {
+ if (findBrush(field) === -1) {
+ console.log("ADD BRUSH " + srcTarg.Title + " " + dstTarg.Title);
+ (findBrush(field) === -1) && field.Data.push(linkDoc);
+ }
+ };
}
- )))
+ dstTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, brushAction);
+ srcTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, brushAction);
+
+ }
}
- }
- })
+ });
+ }
+ componentWillUnmount() {
+ if (this._brushReactionDisposer) {
+ this._brushReactionDisposer();
+ }
}
documentAnchors(view: DocumentView) {
let equalViews = [view];
let containerDoc = view.props.Document.GetT(KeyStore.AnnotationOn, Document);
- if (containerDoc && containerDoc != FieldWaiting && containerDoc instanceof Document) {
- equalViews = DocumentManager.Instance.getDocumentViews(containerDoc.GetPrototype() as Document)
+ if (containerDoc && containerDoc instanceof Document) {
+ equalViews = DocumentManager.Instance.getDocumentViews(containerDoc.GetPrototype()!);
}
- return equalViews.filter(sv => sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document == this.props.Document);
+ return equalViews.filter(sv => sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document === this.props.Document);
}
@computed
@@ -76,19 +84,18 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
let targetViews = this.documentAnchors(connection.b);
let possiblePairs: { a: Document, b: Document, }[] = [];
srcViews.map(sv => targetViews.map(tv => possiblePairs.push({ a: sv.props.Document, b: tv.props.Document })));
- possiblePairs.map(possiblePair => {
- if (!drawnPairs.reduce((found, drawnPair) => {
- let match = (possiblePair.a == drawnPair.a && possiblePair.b == drawnPair.b);
- if (match) {
- if (!drawnPair.l.reduce((found, link) => found || link.Id == connection.l.Id, false))
- drawnPair.l.push(connection.l);
+ possiblePairs.map(possiblePair =>
+ drawnPairs.reduce((found, drawnPair) => {
+ let match = (possiblePair.a === drawnPair.a && possiblePair.b === drawnPair.b);
+ if (match && !drawnPair.l.reduce((found, link) => found || link.Id === connection.l.Id, false)) {
+ drawnPair.l.push(connection.l);
}
return match || found;
- }, false)) {
- drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] as Document[] });
- }
- })
- return drawnPairs
+ }, false)
+ ||
+ drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] })
+ );
+ return drawnPairs;
}, [] as { a: Document, b: Document, l: Document[] }[]);
return connections.map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />);
}
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 19382e66f..751ea8190 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -1,21 +1,8 @@
-import { action, computed, observable } from "mobx";
+import { computed } from "mobx";
import { observer } from "mobx-react";
-import { Document } from "../../../../fields/Document";
-import { FieldWaiting } from "../../../../fields/Field";
import { KeyStore } from "../../../../fields/KeyStore";
-import { TextField } from "../../../../fields/TextField";
-import { DragManager } from "../../../util/DragManager";
-import { Transform } from "../../../util/Transform";
-import { undoBatch } from "../../../util/UndoManager";
-import { InkingCanvas } from "../../InkingCanvas";
-import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
-import { DocumentContentsView } from "../../nodes/DocumentContentsView";
-import { DocumentViewProps } from "../../nodes/DocumentView";
-import { COLLECTION_BORDER_WIDTH } from "../CollectionView";
-import { CollectionViewBase, CollectionViewProps, CursorEntry } from "../CollectionViewBase";
-import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";
+import { CollectionViewProps, CursorEntry } from "../CollectionSubView";
import "./CollectionFreeFormView.scss";
-import { MarqueeView } from "./MarqueeView";
import React = require("react");
import v5 = require("uuid/v5");
import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
@@ -70,43 +57,23 @@ export class CollectionFreeFormRemoteCursors extends React.Component<CollectionV
let id = entry.Data[0][0];
let email = entry.Data[0][1];
let point = entry.Data[1];
- this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22")
+ 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
- ref={(el) => { if (el) this.crosshairs = el }}
+ <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>
);
}
- })
+ });
}
render() {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 81d21d89a..26c794e91 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -1,7 +1,13 @@
-@import "../../global_variables";
-
+@import "../../globalCssVariables";
+.collectionfreeformview-measure {
+ position: inherit;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ }
.collectionfreeformview {
- position: absolute;
+ position: inherit;
top: 0;
left: 0;
width: 100%;
@@ -10,7 +16,7 @@
}
.collectionfreeformview-container {
.collectionfreeformview > .jsx-parser {
- position: absolute;
+ position: inherit;
height: 100%;
width: 100%;
}
@@ -22,11 +28,13 @@
// 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: relative;
+ position: absolute;
overflow: hidden;
top: 0;
left: 0;
@@ -35,18 +43,20 @@
}
.collectionfreeformview-overlay {
.collectionfreeformview > .jsx-parser {
- position: absolute;
+ position: inherit;
height: 100%;
}
.formattedTextBox-cont {
background: $light-color-secondary;
}
-
+
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:relative;
+ 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 c5178f69d..c3ab80f8d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,28 +1,33 @@
-import { action, computed, observable, trace } from "mobx";
+import { action, computed, observable, runInAction, untracked } 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 { 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 "../CollectionView";
-import { CollectionViewBase } from "../CollectionViewBase";
+import { CollectionSubView, SubCollectionViewProps } 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 { MainOverlayTextBox } from "../../MainOverlayTextBox";
@observer
-export class CollectionFreeFormView extends CollectionViewBase {
+export class CollectionFreeFormView extends CollectionSubView {
public _canvasRef = React.createRef<HTMLDivElement>();
private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type)
@@ -33,21 +38,23 @@ export class CollectionFreeFormView extends CollectionViewBase {
}
public addDocument = (newBox: Document, allowDuplicates: boolean) => {
- let added = this.props.addDocument(newBox, false);
- this.bringToFront(newBox);
- return added;
+ if (this.isAnnotationOverlay) {
+ newBox.SetNumber(KeyStore.Zoom, this.props.Document.GetNumber(KeyStore.Scale, 1));
+ }
+ return this.props.addDocument(this.bringToFront(newBox), false);
}
public selectDocuments = (docs: Document[]) => {
- this.props.CollectionView.SelectedDocs.length = 0;
- docs.map(d => this.props.CollectionView.SelectedDocs.push(d.Id));
+ SelectionManager.DeselectAll;
+ docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).filter(dv => dv).map(dv =>
+ SelectionManager.SelectDoc(dv!, true));
}
public getActiveDocuments = () => {
var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
return this.props.Document.GetList(this.props.fieldKey, [] as Document[]).reduce((active, doc) => {
var page = doc.GetNumber(KeyStore.Page, -1);
- if (page == curPage || page == -1) {
+ if (page === curPage || page === -1) {
active.push(doc);
}
return active;
@@ -58,46 +65,44 @@ export class CollectionFreeFormView extends CollectionViewBase {
@observable public DownY: number = 0;
@observable private _lastX: number = 0;
@observable private _lastY: number = 0;
+ @observable private _pwidth: number = 0;
+ @observable private _pheight: number = 0;
- @computed get panX(): number { return this.props.Document.GetNumber(KeyStore.PanX, 0) }
- @computed get panY(): number { return this.props.Document.GetNumber(KeyStore.PanY, 0) }
+ @computed get panX(): number { return this.props.Document.GetNumber(KeyStore.PanX, 0); }
+ @computed get panY(): number { return this.props.Document.GetNumber(KeyStore.PanY, 0); }
@computed get scale(): number { return this.props.Document.GetNumber(KeyStore.Scale, 1); }
@computed get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's?
@computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
@computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
@computed get zoomScaling() { return this.props.Document.GetNumber(KeyStore.Scale, 1); }
- @computed get centeringShiftX() { return !this.props.Document.GetNumber(KeyStore.NativeWidth, 0) ? this.props.panelWidth() / 2 : 0; } // shift so pan position is at center of window for non-overlay collections
- @computed get centeringShiftY() { return !this.props.Document.GetNumber(KeyStore.NativeHeight, 0) ? this.props.panelHeight() / 2 : 0; }// shift so pan position is at center of window for non-overlay collections
+ @computed get centeringShiftX() { return !this.props.Document.GetNumber(KeyStore.NativeWidth, 0) ? this._pwidth / 2 : 0; } // shift so pan position is at center of window for non-overlay collections
+ @computed get centeringShiftY() { return !this.props.Document.GetNumber(KeyStore.NativeHeight, 0) ? this._pheight / 2 : 0; }// shift so pan position is at center of window for non-overlay collections
@undoBatch
@action
drop = (e: Event, de: DragManager.DropEvent) => {
- if (super.drop(e, de)) {
- if (de.data instanceof DragManager.DocumentDragData) {
- let screenX = de.x - (de.data.xOffset as number || 0);
- let screenY = de.y - (de.data.yOffset as number || 0);
- const [x, y] = this.getTransform().transformPoint(screenX, screenY);
- let dragDoc = de.data.draggedDocuments[0];
- let dragX = dragDoc.GetNumber(KeyStore.X, 0);
- let dragY = dragDoc.GetNumber(KeyStore.Y, 0);
- de.data.draggedDocuments.map(d => {
- let docX = d.GetNumber(KeyStore.X, 0);
- let docY = d.GetNumber(KeyStore.Y, 0);
- d.SetNumber(KeyStore.X, x + (docX - dragX));
- d.SetNumber(KeyStore.Y, y + (docY - dragY));
+ if (super.drop(e, de) && de.data instanceof DragManager.DocumentDragData) {
+ const [x, y] = this.getTransform().transformPoint(de.x - de.data.xOffset, de.y - de.data.yOffset);
+ if (de.data.droppedDocuments.length) {
+ let dropX = de.data.droppedDocuments[0].GetNumber(KeyStore.X, 0);
+ let dropY = de.data.droppedDocuments[0].GetNumber(KeyStore.Y, 0);
+ de.data.droppedDocuments.map(d => {
+ d.SetNumber(KeyStore.X, x + (d.GetNumber(KeyStore.X, 0) - dropX));
+ d.SetNumber(KeyStore.Y, y + (d.GetNumber(KeyStore.Y, 0) - dropY));
if (!d.GetNumber(KeyStore.Width, 0)) {
d.SetNumber(KeyStore.Width, 300);
+ }
+ if (!d.GetNumber(KeyStore.Height, 0)) {
d.SetNumber(KeyStore.Height, 300);
}
this.bringToFront(d);
- })
+ });
}
return true;
}
return false;
}
-
@action
cleanupInteractions = () => {
document.removeEventListener("pointermove", this.onPointerMove);
@@ -106,15 +111,13 @@ export class CollectionFreeFormView extends CollectionViewBase {
@action
onPointerDown = (e: React.PointerEvent): void => {
- if (((e.button === 2 && (!this.isAnnotationOverlay || this.zoomScaling != 1)) || e.button == 0) && this.props.active()) {
+ if (((e.button === 2 && (!this.isAnnotationOverlay || this.zoomScaling !== 1)) || e.button === 0) && this.props.active()) {
document.removeEventListener("pointermove", this.onPointerMove);
document.addEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
document.addEventListener("pointerup", this.onPointerUp);
this._lastX = this.DownX = e.pageX;
this._lastY = this.DownY = e.pageY;
- if (this.props.isSelected())
- e.stopPropagation();
}
}
@@ -128,10 +131,31 @@ export class CollectionFreeFormView extends CollectionViewBase {
@action
onPointerMove = (e: PointerEvent): void => {
if (!e.cancelBubble && this.props.active()) {
- if ((!this.isAnnotationOverlay || this.zoomScaling != 1) && !e.shiftKey) {
+ if ((!this.isAnnotationOverlay || this.zoomScaling !== 1) && !e.shiftKey) {
let x = this.props.Document.GetNumber(KeyStore.PanX, 0);
let y = this.props.Document.GetNumber(KeyStore.PanY, 0);
+ let docs = this.props.Document.GetList(this.props.fieldKey, [] as Document[]);
let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
+ if (!this.isAnnotationOverlay) {
+ let minx = docs.length ? docs[0].GetNumber(KeyStore.X, 0) : 0;
+ let maxx = docs.length ? docs[0].GetNumber(KeyStore.Width, 0) + minx : minx;
+ let miny = docs.length ? docs[0].GetNumber(KeyStore.Y, 0) : 0;
+ let maxy = docs.length ? docs[0].GetNumber(KeyStore.Height, 0) + miny : miny;
+ let ranges = docs.filter(doc => doc).reduce((range, doc) => {
+ let x = doc.GetNumber(KeyStore.X, 0);
+ let xe = x + doc.GetNumber(KeyStore.Width, 0);
+ let y = doc.GetNumber(KeyStore.Y, 0);
+ let ye = y + doc.GetNumber(KeyStore.Height, 0);
+ return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]],
+ [range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]];
+ }, [[minx, maxx], [miny, maxy]]);
+ let panelwidth = this._pwidth / this.scale / 2;
+ let panelheight = this._pheight / this.scale / 2;
+ if (x - dx < ranges[0][0] - panelwidth) x = ranges[0][1] + panelwidth + dx;
+ if (x - dx > ranges[0][1] + panelwidth) x = ranges[0][0] - panelwidth + dx;
+ if (y - dy < ranges[1][0] - panelheight) y = ranges[1][1] + panelheight + dy;
+ if (y - dy > ranges[1][1] + panelheight) y = ranges[1][0] - panelheight + dy;
+ }
this.SetPan(x - dx, y - dy);
this._lastX = e.pageX;
this._lastY = e.pageY;
@@ -143,7 +167,9 @@ export class CollectionFreeFormView extends CollectionViewBase {
@action
onPointerWheel = (e: React.WheelEvent): void => {
- this.props.select(false);
+ // if (!this.props.active()) {
+ // return;
+ // }
e.stopPropagation();
let coefficient = 1000;
@@ -157,26 +183,30 @@ export class CollectionFreeFormView extends CollectionViewBase {
e.stopPropagation();
e.preventDefault();
} else {
- // if (modes[e.deltaMode] == 'pixels') coefficient = 50;
- // else if (modes[e.deltaMode] == 'lines') coefficient = 1000; // This should correspond to line-height??
+ // if (modes[e.deltaMode] === 'pixels') coefficient = 50;
+ // else if (modes[e.deltaMode] === 'lines') coefficient = 1000; // This should correspond to line-height??
let transform = this.getTransform();
let deltaScale = (1 - (e.deltaY / coefficient));
- if (deltaScale * this.zoomScaling < 1 && this.isAnnotationOverlay)
+ if (deltaScale * this.zoomScaling < 1 && this.isAnnotationOverlay) {
deltaScale = 1 / this.zoomScaling;
+ }
let [x, y] = transform.transformPoint(e.clientX, e.clientY);
- let localTransform = this.getLocalTransform()
- localTransform = localTransform.inverse().scaleAbout(deltaScale, x, y)
+ let localTransform = this.getLocalTransform();
+ localTransform = localTransform.inverse().scaleAbout(deltaScale, x, y);
// console.log(localTransform)
this.props.Document.SetNumber(KeyStore.Scale, localTransform.Scale);
this.SetPan(-localTransform.TranslateX / localTransform.Scale, -localTransform.TranslateY / localTransform.Scale);
+ e.stopPropagation();
+ e.preventDefault();
}
}
@action
private SetPan(panX: number, panY: number) {
+ MainOverlayTextBox.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));
@@ -206,9 +236,9 @@ export class CollectionFreeFormView extends CollectionViewBase {
return -1;
}
return doc1.GetNumber(KeyStore.ZIndex, 0) - doc2.GetNumber(KeyStore.ZIndex, 0);
- }).map((doc, index) => {
- doc.SetNumber(KeyStore.ZIndex, index + 1)
- });
+ }).map((doc, index) =>
+ doc.SetNumber(KeyStore.ZIndex, index + 1));
+ return doc;
}
@computed get backgroundLayout(): string | undefined {
@@ -228,85 +258,105 @@ export class CollectionFreeFormView extends CollectionViewBase {
let x = doc.GetNumber(KeyStore.X, 0) + doc.GetNumber(KeyStore.Width, 0) / 2;
let y = doc.GetNumber(KeyStore.Y, 0) + doc.GetNumber(KeyStore.Height, 0) / 2;
this.SetPan(x, y);
- this.props.focus(this.props.Document);
}
- getDocumentViewProps(document: Document): DocumentViewProps {
+ getDocumentViewProps(document: Document, opacity: number): DocumentViewProps {
return {
Document: document,
- AddDocument: this.props.addDocument,
- RemoveDocument: this.props.removeDocument,
+ opacity: opacity,
+ addDocument: this.props.addDocument,
+ removeDocument: this.props.removeDocument,
+ moveDocument: this.props.moveDocument,
ScreenToLocalTransform: this.getTransform,
isTopMost: false,
- SelectOnLoad: document.Id == this._selectOnLoaded,
+ selectOnLoad: document.Id === this._selectOnLoaded,
PanelWidth: document.Width,
PanelHeight: document.Height,
ContentScaling: this.noScaling,
ContainingCollectionView: this.props.CollectionView,
- focus: this.focusDocument
- }
+ focus: this.focusDocument,
+ parentActive: this.props.active,
+ onActiveChanged: this.props.active,
+ };
}
@computed
get views() {
var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
- return this.props.Document.GetList(this.props.fieldKey, [] as Document[]).filter(doc => doc).reduce((prev, doc) => {
+ let docviews = this.props.Document.GetList(this.props.fieldKey, [] as Document[]).filter(doc => doc).reduce((prev, doc) => {
var page = doc.GetNumber(KeyStore.Page, -1);
- if (page == curPage || page == -1)
- prev.push(<CollectionFreeFormDocumentView key={doc.Id} {...this.getDocumentViewProps(doc)} />);
+ var zoom = doc.GetNumber(KeyStore.Zoom, 1);
+ var dv = DocumentManager.Instance.getDocumentView(doc);
+ let opacity = this.isAnnotationOverlay && (!dv || !SelectionManager.IsSelected(dv)) ? 1 - Math.abs(zoom - this.scale) : 1;
+ if ((page === curPage || page === -1)) {
+ prev.push(<CollectionFreeFormDocumentView key={doc.Id} {...this.getDocumentViewProps(doc, opacity)} />);
+ }
return prev;
- }, [] as JSX.Element[])
+ }, [] as JSX.Element[]);
+
+ setTimeout(() => { // bcz: surely there must be a better way ....
+ this._selectOnLoaded = "";
+ }, 600);
+
+ return docviews;
}
@computed
get backgroundView() {
return !this.backgroundLayout ? (null) :
- (<DocumentContentsView {...this.getDocumentViewProps(this.props.Document)}
- layoutKey={KeyStore.BackgroundLayout} isTopMost={this.props.isTopMost} isSelected={() => false} select={() => { }} />);
+ (<DocumentContentsView {...this.getDocumentViewProps(this.props.Document, 1)}
+ 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={() => { }} />);
+ (<DocumentContentsView {...this.getDocumentViewProps(this.props.Document, 1)}
+ 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)
- getLocalTransform = (): Transform => Transform.Identity.scale(1 / this.scale).translate(this.panX, this.panY);
+ @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 (
- <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}>
- <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments}
- addDocument={this.addDocument} removeDocument={this.props.removeDocument}
- 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} />
+ <Measure onResize={(r: any) => runInAction(() => { this._pwidth = r.entry.width; this._pheight = r.entry.height; })}>
+ {({ measureRef }) => (
+ <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} ref={this.createDropTarget}>
+ <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments}
+ addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox}
+ getContainerTransform={this.getContainerTransform} getTransform={this.getTransform}>
+ <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>
- {this.overlayView}
- </PreviewCursor>
- </MarqueeView>
- </div>
+ </div>)}
+ </Measure>
);
}
} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
index 1ee3b244b..ae0a9fd48 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
@@ -1,6 +1,8 @@
.marqueeView {
- position: absolute;
+ position: inherit;
+ top:0;
+ left:0;
width:100%;
height:100%;
}
@@ -11,4 +13,14 @@
border-width: 1px;
border-color: black;
pointer-events: none;
+ .marquee-legend {
+ bottom:-18px;
+ left:0;
+ position: absolute;
+ font-size: 9;
+ white-space:nowrap;
+ }
+ .marquee-legend::after {
+ content: "Press: C (collection), or Delete"
+ }
} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index e2239c8be..dbab790d4 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,12 +34,13 @@ 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
cleanupInteractions = (all: boolean = false) => {
if (all) {
- document.removeEventListener("pointermove", this.onPointerMove, true)
+ document.removeEventListener("pointermove", this.onPointerMove, true);
document.removeEventListener("pointerup", this.onPointerUp, true);
} else {
this._used = true;
@@ -47,12 +50,32 @@ 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()) {
+ 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;
- document.addEventListener("pointermove", this.onPointerMove, true)
+ 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,7 +86,11 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
this._lastX = e.pageX;
this._lastY = e.pageY;
if (!e.cancelBubble) {
- if (!this._used && e.buttons == 1 && !e.altKey && !e.metaKey &&
+ 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 },
@@ -94,20 +126,21 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
let top = this._downY < this._lastY ? this._downY : this._lastY;
let topLeft = this.props.getTransform().transformPoint(left, top);
let size = this.props.getTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
- return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) }
+ 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") {
+ if (e.key === "Backspace" || e.key === "Delete") {
this.marqueeSelect().map(d => this.props.removeDocument(d));
let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField);
- if (ink && ink != FieldWaiting) {
+ if (ink && ink !== FieldWaiting) {
this.marqueeInkDelete(ink.Data);
}
this.cleanupInteractions();
}
- if (e.key == "c") {
+ if (e.key === "c") {
let bounds = this.Bounds;
let selected = this.marqueeSelect().map(d => {
this.props.removeDocument(d);
@@ -118,7 +151,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
return d;
});
let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField);
- let inkData = ink && ink != FieldWaiting ? ink.Data : undefined;
+ let inkData = ink && ink !== FieldWaiting ? ink.Data : undefined;
//setTimeout(() => {
let newCollection = Documents.FreeformDocument(selected, {
x: bounds.left,
@@ -135,6 +168,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
this.marqueeInkDelete(inkData);
// }, 100);
this.cleanupInteractions();
+ SelectionManager.DeselectAll();
}
}
@action
@@ -146,7 +180,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
if (InkingCanvas.IntersectStrokeRect(value, this.Bounds)) {
idata.set(key,
{
- pathData: value.pathData.map(val => { return { x: val.x + centerShiftX, y: val.y + centerShiftY } }),
+ pathData: value.pathData.map(val => ({ x: val.x + centerShiftX, y: val.y + centerShiftY })),
color: value.color,
width: value.width,
tool: value.tool,
@@ -175,13 +209,15 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
let selRect = this.Bounds;
let selection: Document[] = [];
this.props.activeDocuments().map(doc => {
+ var z = doc.GetNumber(KeyStore.Zoom, 1);
var x = doc.GetNumber(KeyStore.X, 0);
var y = doc.GetNumber(KeyStore.Y, 0);
- var w = doc.GetNumber(KeyStore.Width, 0);
- var h = doc.GetNumber(KeyStore.Height, 0);
- if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect))
- selection.push(doc)
- })
+ var w = doc.GetNumber(KeyStore.Width, 0) / z;
+ var h = doc.GetNumber(KeyStore.Height, 0) / z;
+ if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) {
+ selection.push(doc);
+ }
+ });
return selection;
}
@@ -189,7 +225,9 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
get marqueeDiv() {
let p = this.props.getContainerTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY);
let v = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
- return <div className="marquee" style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}` }} />
+ return <div className="marquee" style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}` }} >
+ <span className="marquee-legend" />
+ </div>;
}
render() {
diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.scss b/src/client/views/collections/collectionFreeForm/PreviewCursor.scss
deleted file mode 100644
index 21210be2b..000000000
--- a/src/client/views/collections/collectionFreeForm/PreviewCursor.scss
+++ /dev/null
@@ -1,23 +0,0 @@
-
-.previewCursor {
- color: black;
- position: absolute;
- transform-origin: left top;
- pointer-events: none;
-}
-.previewCursorView {
- 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 93c98f7b0..000000000
--- a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx
+++ /dev/null
@@ -1,119 +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/Annotation.tsx b/src/client/views/nodes/Annotation.tsx
index a2c7be1a8..3e4ed6bf1 100644
--- a/src/client/views/nodes/Annotation.tsx
+++ b/src/client/views/nodes/Annotation.tsx
@@ -1,16 +1,16 @@
import "./ImageBox.scss";
-import React = require("react")
-import { observer } from "mobx-react"
+import React = require("react");
+import { observer } from "mobx-react";
import { observable, action } from 'mobx';
-import 'react-pdf/dist/Page/AnnotationLayer.css'
+import 'react-pdf/dist/Page/AnnotationLayer.css';
-interface IProps{
+interface IProps {
Span: HTMLSpanElement;
- X: number;
- Y: number;
- Highlights: any[];
- Annotations: any[];
- CurrAnno: any[];
+ X: number;
+ Y: number;
+ Highlights: any[];
+ Annotations: any[];
+ CurrAnno: any[];
}
@@ -23,95 +23,95 @@ interface IProps{
*/
@observer
export class Annotation extends React.Component<IProps> {
-
+
/**
* changes color of the span (highlighted section)
*/
- onColorChange = (e:React.PointerEvent) => {
- if (e.currentTarget.innerHTML == "r"){
- this.props.Span.style.backgroundColor = "rgba(255,0,0, 0.3)"
- } else if (e.currentTarget.innerHTML == "b"){
- this.props.Span.style.backgroundColor = "rgba(0,255, 255, 0.3)"
- } else if (e.currentTarget.innerHTML == "y"){
- this.props.Span.style.backgroundColor = "rgba(255,255,0, 0.3)"
- } else if (e.currentTarget.innerHTML == "g"){
- this.props.Span.style.backgroundColor = "rgba(76, 175, 80, 0.3)"
+ onColorChange = (e: React.PointerEvent) => {
+ if (e.currentTarget.innerHTML === "r") {
+ this.props.Span.style.backgroundColor = "rgba(255,0,0, 0.3)";
+ } else if (e.currentTarget.innerHTML === "b") {
+ this.props.Span.style.backgroundColor = "rgba(0,255, 255, 0.3)";
+ } else if (e.currentTarget.innerHTML === "y") {
+ this.props.Span.style.backgroundColor = "rgba(255,255,0, 0.3)";
+ } else if (e.currentTarget.innerHTML === "g") {
+ this.props.Span.style.backgroundColor = "rgba(76, 175, 80, 0.3)";
}
-
+
}
/**
* removes the highlighted span. Supposed to remove Annotation too, but I don't know how to unmount this
*/
@action
- onRemove = (e:any) => {
- let index:number = -1;
+ onRemove = (e: any) => {
+ let index: number = -1;
//finding the highlight in the highlight array
this.props.Highlights.forEach((e) => {
- for (let i = 0; i < e.spans.length; i++){
- if (e.spans[i] == this.props.Span){
- index = this.props.Highlights.indexOf(e);
- this.props.Highlights.splice(index, 1);
+ for (const span of e.spans) {
+ if (span === this.props.Span) {
+ index = this.props.Highlights.indexOf(e);
+ this.props.Highlights.splice(index, 1);
}
}
- })
+ });
//removing from CurrAnno and Annotation array
- this.props.Annotations.splice(index, 1);
- this.props.CurrAnno.pop()
-
+ this.props.Annotations.splice(index, 1);
+ this.props.CurrAnno.pop();
+
//removing span from div
- if(this.props.Span.parentElement){
- let nodesArray = this.props.Span.parentElement.childNodes;
+ if (this.props.Span.parentElement) {
+ let nodesArray = this.props.Span.parentElement.childNodes;
nodesArray.forEach((e) => {
- if (e == this.props.Span){
- if (this.props.Span.parentElement){
+ if (e === this.props.Span) {
+ if (this.props.Span.parentElement) {
this.props.Highlights.forEach((item) => {
- if (item == e){
- item.remove();
+ if (item === e) {
+ item.remove();
}
- })
- e.remove();
+ });
+ e.remove();
}
}
- })
+ });
}
-
-
+
+
}
render() {
return (
- <div
- style = {{
- position: "absolute",
- top: "20px",
- left: "0px",
- zIndex: 1,
- transform: `translate(${this.props.X}px, ${this.props.Y}px)`,
-
- }}>
- <div style = {{width:"200px", height:"50px", backgroundColor: "orange"}}>
+ <div
+ style={{
+ position: "absolute",
+ top: "20px",
+ left: "0px",
+ zIndex: 1,
+ transform: `translate(${this.props.X}px, ${this.props.Y}px)`,
+
+ }}>
+ <div style={{ width: "200px", height: "50px", backgroundColor: "orange" }}>
<button
- style = {{borderRadius: "25px", width:"25%", height:"100%"}}
- onClick = {this.onRemove}
+ style={{ borderRadius: "25px", width: "25%", height: "100%" }}
+ onClick={this.onRemove}
>x</button>
- <div style = {{width:"75%", height: "100%" , display:"inline-block"}}>
- <button onPointerDown = {this.onColorChange} style = {{backgroundColor:"red", borderRadius:"50%", color: "transparent"}}>r</button>
- <button onPointerDown = {this.onColorChange} style = {{backgroundColor:"blue", borderRadius:"50%", color: "transparent"}}>b</button>
- <button onPointerDown = {this.onColorChange} style = {{backgroundColor:"yellow", borderRadius:"50%", color:"transparent"}}>y</button>
- <button onPointerDown = {this.onColorChange} style = {{backgroundColor:"green", borderRadius:"50%", color:"transparent"}}>g</button>
+ <div style={{ width: "75%", height: "100%", display: "inline-block" }}>
+ <button onPointerDown={this.onColorChange} style={{ backgroundColor: "red", borderRadius: "50%", color: "transparent" }}>r</button>
+ <button onPointerDown={this.onColorChange} style={{ backgroundColor: "blue", borderRadius: "50%", color: "transparent" }}>b</button>
+ <button onPointerDown={this.onColorChange} style={{ backgroundColor: "yellow", borderRadius: "50%", color: "transparent" }}>y</button>
+ <button onPointerDown={this.onColorChange} style={{ backgroundColor: "green", borderRadius: "50%", color: "transparent" }}>g</button>
</div>
-
+
</div>
- <div style = {{width:"200px", height:"200"}}>
- <textarea style = {{width: "100%", height: "100%"}}
- defaultValue = "Enter Text Here..."
-
+ <div style={{ width: "200px", height: "200" }}>
+ <textarea style={{ width: "100%", height: "100%" }}
+ defaultValue="Enter Text Here..."
+
></textarea>
</div>
</div>
-
+
);
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 6daf15f5f..1493ff25b 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -1,18 +1,18 @@
-import React = require("react")
+import React = require("react");
import { FieldViewProps, FieldView } from './FieldView';
import { FieldWaiting } from '../../../fields/Field';
-import { observer } from "mobx-react"
+import { observer } from "mobx-react";
import { ContextMenu } from "../../views/ContextMenu";
import { observable, action } from 'mobx';
import { KeyStore } from '../../../fields/KeyStore';
import { AudioField } from "../../../fields/AudioField";
-import "./AudioBox.scss"
+import "./AudioBox.scss";
import { NumberField } from "../../../fields/NumberField";
@observer
export class AudioBox extends React.Component<FieldViewProps> {
- public static LayoutString() { return FieldView.LayoutString(AudioBox) }
+ public static LayoutString() { return FieldView.LayoutString(AudioBox); }
constructor(props: FieldViewProps) {
super(props);
@@ -28,8 +28,8 @@ export class AudioBox extends React.Component<FieldViewProps> {
render() {
- let field = this.props.doc.Get(this.props.fieldKey)
- let path = field == FieldWaiting ? "http://techslides.com/demos/samples/sample.mp3" :
+ let field = this.props.Document.Get(this.props.fieldKey);
+ let path = field === FieldWaiting ? "http://techslides.com/demos/samples/sample.mp3" :
field instanceof AudioField ? field.Data.href : "http://techslides.com/demos/samples/sample.mp3";
return (
@@ -39,6 +39,6 @@ export class AudioBox extends React.Component<FieldViewProps> {
Not supported.
</audio>
</div>
- )
+ );
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index d52b662bd..0aa152209 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -6,7 +6,6 @@ import { Transform } from "../../util/Transform";
import { DocumentView, DocumentViewProps } from "./DocumentView";
import "./DocumentView.scss";
import React = require("react");
-import { thisExpression } from "babel-types";
@observer
@@ -16,18 +15,13 @@ export class CollectionFreeFormDocumentView extends React.Component<DocumentView
constructor(props: DocumentViewProps) {
super(props);
}
- get screenRect(): ClientRect | DOMRect {
- if (this._mainCont.current) {
- return this._mainCont.current.getBoundingClientRect();
- }
- return new DOMRect();
- }
@computed
get transform(): string {
- return `scale(${this.props.ContentScaling()}, ${this.props.ContentScaling()}) translate(${this.props.Document.GetNumber(KeyStore.X, 0)}px, ${this.props.Document.GetNumber(KeyStore.Y, 0)}px)`;
+ return `scale(${this.props.ContentScaling()}, ${this.props.ContentScaling()}) translate(${this.props.Document.GetNumber(KeyStore.X, 0)}px, ${this.props.Document.GetNumber(KeyStore.Y, 0)}px) scale(${this.zoom}, ${this.zoom}) `;
}
+ @computed get zoom(): number { return 1 / this.props.Document.GetNumber(KeyStore.Zoom, 1); }
@computed get zIndex(): number { return this.props.Document.GetNumber(KeyStore.ZIndex, 0); }
@computed get width(): number { return this.props.Document.Width(); }
@computed get height(): number { return this.props.Document.Height(); }
@@ -35,43 +29,46 @@ export class CollectionFreeFormDocumentView extends React.Component<DocumentView
@computed get nativeHeight(): number { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
set width(w: number) {
- this.props.Document.SetData(KeyStore.Width, w, NumberField)
+ this.props.Document.SetData(KeyStore.Width, w, NumberField);
if (this.nativeWidth && this.nativeHeight) {
- this.props.Document.SetNumber(KeyStore.Height, this.nativeHeight / this.nativeWidth * w)
+ this.props.Document.SetNumber(KeyStore.Height, this.nativeHeight / this.nativeWidth * w);
}
}
set height(h: number) {
this.props.Document.SetData(KeyStore.Height, h, NumberField);
if (this.nativeWidth && this.nativeHeight) {
- this.props.Document.SetNumber(KeyStore.Width, this.nativeWidth / this.nativeHeight * h)
+ this.props.Document.SetNumber(KeyStore.Width, this.nativeWidth / this.nativeHeight * h);
}
}
set zIndex(h: number) {
- this.props.Document.SetData(KeyStore.ZIndex, h, NumberField)
+ this.props.Document.SetData(KeyStore.ZIndex, h, NumberField);
}
- contentScaling = () => {
- return this.nativeWidth > 0 ? this.width / this.nativeWidth : 1;
- }
+ contentScaling = () => this.nativeWidth > 0 ? this.width / this.nativeWidth : 1;
- getTransform = (): Transform => {
- return this.props.ScreenToLocalTransform().
- translate(-this.props.Document.GetNumber(KeyStore.X, 0), -this.props.Document.GetNumber(KeyStore.Y, 0)).scale(1 / this.contentScaling());
- }
+ getTransform = (): Transform =>
+ this.props.ScreenToLocalTransform()
+ .translate(-this.props.Document.GetNumber(KeyStore.X, 0), -this.props.Document.GetNumber(KeyStore.Y, 0))
+ .scale(1 / this.contentScaling()).scale(1 / this.zoom)
@computed
get docView() {
return <DocumentView {...this.props}
ContentScaling={this.contentScaling}
ScreenToLocalTransform={this.getTransform}
- />
+ PanelWidth={this.panelWidth}
+ PanelHeight={this.panelHeight}
+ />;
}
+ panelWidth = () => this.props.Document.GetBoolean(KeyStore.Minimized, false) ? 10 : this.props.PanelWidth();
+ panelHeight = () => this.props.Document.GetBoolean(KeyStore.Minimized, false) ? 10 : this.props.PanelHeight();
render() {
return (
<div className="collectionFreeFormDocumentView-container" ref={this._mainCont} style={{
+ opacity: this.props.opacity,
transformOrigin: "left top",
transform: this.transform,
pointerEvents: "all",
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 77551649c..88900c4b1 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,6 +1,6 @@
import { computed } from "mobx";
import { observer } from "mobx-react";
-import { FieldWaiting } from "../../../fields/Field";
+import { FieldWaiting, Field } from "../../../fields/Field";
import { Key } from "../../../fields/Key";
import { KeyStore } from "../../../fields/KeyStore";
import { ListField } from "../../../fields/ListField";
@@ -11,7 +11,7 @@ import { CollectionSchemaView } from "../collections/CollectionSchemaView";
import { CollectionVideoView } from "../collections/CollectionVideoView";
import { CollectionView } from "../collections/CollectionView";
import { AudioBox } from "./AudioBox";
-import { DocumentViewProps, JsxBindings } from "./DocumentView";
+import { DocumentViewProps } from "./DocumentView";
import "./DocumentView.scss";
import { FormattedTextBox } from "./FormattedTextBox";
import { ImageBox } from "./ImageBox";
@@ -21,8 +21,16 @@ import { VideoBox } from "./VideoBox";
import { WebBox } from "./WebBox";
import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox";
import React = require("react");
+import { Document } from "../../../fields/Document";
+import { FieldViewProps } from "./FieldView";
+import { Without } from "../../../Utils";
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
+type BindingProps = Without<FieldViewProps, 'fieldKey'>;
+export interface JsxBindings {
+ props: BindingProps;
+ [keyName: string]: BindingProps | Field;
+}
@observer
export class DocumentContentsView extends React.Component<DocumentViewProps & {
@@ -36,13 +44,41 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
CreateBindings(): JsxBindings {
- let bindings: JsxBindings = { ...this.props, };
+ let {
+ Document,
+ isSelected,
+ select,
+ isTopMost,
+ selectOnLoad,
+ ScreenToLocalTransform,
+ ContainingCollectionView,
+ addDocument,
+ removeDocument,
+ onActiveChanged,
+ parentActive: active,
+ } = this.props;
+ let bindings: JsxBindings = {
+ props: {
+ Document,
+ isSelected,
+ select,
+ isTopMost,
+ selectOnLoad,
+ ScreenToLocalTransform,
+ ContainingCollectionView,
+ active,
+ onActiveChanged,
+ addDocument,
+ removeDocument,
+ focus,
+ }
+ };
for (const key of this.layoutKeys) {
bindings[key.Name + "Key"] = key; // this maps string values of the form <keyname>Key to an actual key Kestore.keyname e.g, "DataKey" => KeyStore.Data
}
for (const key of this.layoutFields) {
let field = this.props.Document.Get(key);
- bindings[key.Name] = field && field != FieldWaiting ? field.GetValue() : field;
+ bindings[key.Name] = field && field !== FieldWaiting ? field.GetValue() : field;
}
return bindings;
}
@@ -57,7 +93,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
bindings={this.CreateBindings()}
jsx={this.layout}
showWarnings={true}
- onError={(test: any) => { console.log(test) }}
- />
+ onError={(test: any) => { console.log(test); }}
+ />;
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 85a115f1c..a946ac1a8 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -1,23 +1,43 @@
-@import "../global_variables";
+@import "../globalCssVariables";
+
.documentView-node {
- position: absolute;
+ position: inherit;
+ top: 0;
+ left:0;
background: $light-color; //overflow: hidden;
+
&.minimized {
width: 30px;
height: 30px;
}
+
.top {
background: #232323;
height: 20px;
cursor: pointer;
}
+
.content {
padding: 20px 20px;
height: auto;
box-sizing: border-box;
}
+
.scroll-box {
overflow-y: scroll;
height: calc(100% - 20px);
}
}
+
+.minimized-box {
+ height: 10px;
+ width: 10px;
+ border-radius: 2px;
+ background: $dark-color
+}
+
+.minimized-box:hover {
+ background: $main-accent;
+ transform: scale(1.15);
+ cursor: pointer;
+} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 78b810398..07a0c148e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,43 +1,51 @@
-import { action, computed, IReactionDisposer, reaction, runInAction } from "mobx";
+import { action, computed, runInAction } from "mobx";
import { observer } from "mobx-react";
+import { BooleanField } from "../../../fields/BooleanField";
import { Document } from "../../../fields/Document";
-import { Field, Opt } from "../../../fields/Field";
+import { Field, FieldWaiting, Opt } from "../../../fields/Field";
import { Key } from "../../../fields/Key";
import { KeyStore } from "../../../fields/KeyStore";
import { ListField } from "../../../fields/ListField";
import { TextField } from "../../../fields/TextField";
import { ServerUtils } from "../../../server/ServerUtil";
-import { Utils } from "../../../Utils";
+import { emptyFunction, Utils } from "../../../Utils";
import { Documents } from "../../documents/Documents";
import { DocumentManager } from "../../util/DocumentManager";
import { DragManager } from "../../util/DragManager";
import { SelectionManager } from "../../util/SelectionManager";
import { Transform } from "../../util/Transform";
+import { undoBatch, UndoManager } from "../../util/UndoManager";
import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { CollectionView, CollectionViewType } from "../collections/CollectionView";
+import { CollectionPDFView } from "../collections/CollectionPDFView";
+import { CollectionVideoView } from "../collections/CollectionVideoView";
+import { CollectionView } from "../collections/CollectionView";
import { ContextMenu } from "../ContextMenu";
-import { PresentationView } from "../PresentationView";
import { DocumentContentsView } from "./DocumentContentsView";
import "./DocumentView.scss";
import React = require("react");
+import { PresentationView } from "../PresentationView";
export interface DocumentViewProps {
- ContainingCollectionView: Opt<CollectionView>;
- Document: Document;
- AddDocument?: (doc: Document, allowDuplicates: boolean) => boolean;
- RemoveDocument?: (doc: Document) => boolean;
- ScreenToLocalTransform: () => Transform;
- isTopMost: boolean;
- ContentScaling: () => number;
- PanelWidth: () => number;
- PanelHeight: () => number;
- focus: (doc: Document) => void;
- SelectOnLoad: boolean;
+ ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
+ Document: Document;
+ opacity: number;
+ addDocument?: (doc: Document, allowDuplicates?: boolean) => boolean;
+ removeDocument?: (doc: Document) => boolean;
+ moveDocument?: (doc: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
+ ScreenToLocalTransform: () => Transform;
+ isTopMost: boolean;
+ ContentScaling: () => number;
+ PanelWidth: () => number;
+ PanelHeight: () => number;
+ focus: (doc: Document) => void;
+ selectOnLoad: boolean;
+ parentActive: () => boolean;
+ onActiveChanged: (isActive: boolean) => void;
}
export interface JsxArgs extends DocumentViewProps {
- Keys: { [name: string]: Key }
- Fields: { [name: string]: Field }
+ Keys: { [name: string]: Key };
+ Fields: { [name: string]: Field };
}
/*
@@ -56,291 +64,389 @@ Example usage of this function:
}
*/
export function FakeJsxArgs(keys: string[], fields: string[] = []): JsxArgs {
- let Keys: { [name: string]: any } = {}
- let Fields: { [name: string]: any } = {}
- for (const key of keys) {
- let fn = () => { }
- Object.defineProperty(fn, "name", { value: key + "Key" })
- Keys[key] = fn;
- }
- for (const field of fields) {
- let fn = () => { }
- Object.defineProperty(fn, "name", { value: field })
- Fields[field] = fn;
- }
- let args: JsxArgs = {
- Document: function Document() { },
- DocumentView: function DocumentView() { },
- Keys,
- Fields
- } as any;
- return args;
-}
-
-export interface JsxBindings {
- Document: Document;
- isSelected: () => boolean;
- select: (isCtrlPressed: boolean) => void;
- isTopMost: boolean;
- SelectOnLoad: boolean;
- [prop: string]: any;
+ let Keys: { [name: string]: any } = {};
+ let Fields: { [name: string]: any } = {};
+ for (const key of keys) {
+ let fn = emptyFunction;
+ Object.defineProperty(fn, "name", { value: key + "Key" });
+ Keys[key] = fn;
+ }
+ for (const field of fields) {
+ let fn = emptyFunction;
+ Object.defineProperty(fn, "name", { value: field });
+ Fields[field] = fn;
+ }
+ let args: JsxArgs = {
+ Document: function Document() { },
+ DocumentView: function DocumentView() { },
+ Keys,
+ Fields
+ } as any;
+ return args;
}
-
-
@observer
export class DocumentView extends React.Component<DocumentViewProps> {
- private _mainCont = React.createRef<HTMLDivElement>();
- private _downX: number = 0;
- private _downY: number = 0;
- private _reactionDisposer: Opt<IReactionDisposer>;
- @computed get active(): boolean { return SelectionManager.IsSelected(this) || !this.props.ContainingCollectionView || this.props.ContainingCollectionView.active(); }
- @computed get topMost(): boolean { return !this.props.ContainingCollectionView || this.props.ContainingCollectionView.collectionViewType == CollectionViewType.Docking; }
- @computed get layout(): string { return this.props.Document.GetText(KeyStore.Layout, "<p>Error loading layout data</p>"); }
- @computed get layoutKeys(): Key[] { return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array<Key>()); }
- @computed get layoutFields(): Key[] { return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array<Key>()); }
- screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect();
- onPointerDown = (e: React.PointerEvent): void => {
- this._downX = e.clientX;
- this._downY = e.clientY;
- if (e.shiftKey && e.buttons === 2) {
- if (this.props.isTopMost) {
- this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey);
- }
- else CollectionDockingView.Instance.StartOtherDrag([this.props.Document], e);
- e.stopPropagation();
- } else {
- if (this.active && !e.isDefaultPrevented()) {
- e.stopPropagation();
- document.removeEventListener("pointermove", this.onPointerMove)
- document.addEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp)
- document.addEventListener("pointerup", this.onPointerUp);
- }
+ private _mainCont = React.createRef<HTMLDivElement>();
+ public get ContentRef() {
+ return this._mainCont;
+ }
+ private _downX: number = 0;
+ private _downY: number = 0;
+ @computed get active(): boolean { return SelectionManager.IsSelected(this) || this.props.parentActive(); }
+ @computed get topMost(): boolean { return this.props.isTopMost; }
+ @computed get layout(): string { return this.props.Document.GetText(KeyStore.Layout, "<p>Error loading layout data</p>"); }
+ @computed get layoutKeys(): Key[] { return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array<Key>()); }
+ @computed get layoutFields(): Key[] { return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array<Key>()); }
+ screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect();
+ onPointerDown = (e: React.PointerEvent): void => {
+ this._downX = e.clientX;
+ this._downY = e.clientY;
+ if (e.shiftKey && e.buttons === 2) {
+ if (this.props.isTopMost) {
+ this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey);
+ } else {
+ CollectionDockingView.Instance.StartOtherDrag([this.props.Document], e);
+ }
+ e.stopPropagation();
+ } else {
+ if (this.active) {
+ e.stopPropagation();
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointerup", this.onPointerUp);
+ }
+ }
}
- }
- private dropDisposer?: DragManager.DragDropDisposer;
- focus = () => {
- this.props.focus(this.props.Document);
- }
+ private dropDisposer?: DragManager.DragDropDisposer;
- componentDidMount() {
- if (this._mainCont.current) {
- this.dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } });
+ componentDidMount() {
+ if (this._mainCont.current) {
+ this.dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, {
+ handlers: { drop: this.drop.bind(this) }
+ });
+ }
+ runInAction(() => DocumentManager.Instance.DocumentViews.push(this));
}
- runInAction(() => DocumentManager.Instance.DocumentViews.push(this))
- this._reactionDisposer = reaction(
- () => this.props.ContainingCollectionView && this.props.ContainingCollectionView.SelectedDocs.slice(),
- () => {
- if (this.props.ContainingCollectionView && this.props.ContainingCollectionView.SelectedDocs.indexOf(this.props.Document.Id) != -1)
- SelectionManager.SelectDoc(this, true);
- });
- }
- componentDidUpdate() {
- if (this.dropDisposer) {
- this.dropDisposer();
- }
- if (this._mainCont.current) {
- this.dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } });
+ componentDidUpdate() {
+ if (this.dropDisposer) {
+ this.dropDisposer();
+ }
+ if (this._mainCont.current) {
+ this.dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, {
+ handlers: { drop: this.drop.bind(this) }
+ });
+ }
}
- }
- componentWillUnmount() {
- if (this.dropDisposer) {
- this.dropDisposer();
- }
- runInAction(() => DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1))
- if (this._reactionDisposer) {
- this._reactionDisposer();
+ componentWillUnmount() {
+ if (this.dropDisposer) {
+ this.dropDisposer();
+ }
+ runInAction(() => DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1));
}
- }
- startDragging(x: number, y: number, dropAliasOfDraggedDoc: boolean) {
- if (this._mainCont.current) {
- const [left, top] = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
- let dragData = new DragManager.DocumentDragData([this.props.Document]);
- dragData.aliasOnDrop = dropAliasOfDraggedDoc;
- dragData.xOffset = x - left;
- dragData.yOffset = y - top;
- dragData.removeDocument = (dropCollectionView: CollectionView) => {
- if (this.props.RemoveDocument && this.props.ContainingCollectionView !== dropCollectionView) {
- this.props.RemoveDocument(this.props.Document);
+ startDragging(x: number, y: number, dropAliasOfDraggedDoc: boolean) {
+ if (this._mainCont.current) {
+ const [left, top] = this.props
+ .ScreenToLocalTransform()
+ .inverse()
+ .transformPoint(0, 0);
+ let dragData = new DragManager.DocumentDragData([this.props.Document]);
+ dragData.aliasOnDrop = dropAliasOfDraggedDoc;
+ dragData.xOffset = x - left;
+ dragData.yOffset = y - top;
+ dragData.moveDocument = this.props.moveDocument;
+ DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, {
+ handlers: {
+ dragComplete: action(emptyFunction)
+ },
+ hideSource: !dropAliasOfDraggedDoc
+ });
}
- }
- DragManager.StartDocumentDrag([this._mainCont.current], dragData, {
- handlers: {
- dragComplete: action(() => { }),
- },
- hideSource: !dropAliasOfDraggedDoc
- })
}
- }
- onPointerMove = (e: PointerEvent): void => {
- if (e.cancelBubble) {
- return;
+ onPointerMove = (e: PointerEvent): void => {
+ if (e.cancelBubble) {
+ return;
+ }
+ if (
+ Math.abs(this._downX - e.clientX) > 3 ||
+ Math.abs(this._downY - e.clientY) > 3
+ ) {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ if (!this.topMost || e.buttons === 2 || e.altKey) {
+ this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey);
+ }
+ }
+ e.stopPropagation();
+ e.preventDefault();
}
- if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
- document.removeEventListener("pointermove", this.onPointerMove)
- document.removeEventListener("pointerup", this.onPointerUp);
- if (!this.topMost || e.buttons == 2 || e.altKey) {
- this.startDragging(e.x, e.y, e.ctrlKey || e.altKey);
- }
+ onPointerUp = (e: PointerEvent): void => {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ 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
+ ) {
+ SelectionManager.SelectDoc(this, e.ctrlKey);
+ }
}
- e.stopPropagation();
- e.preventDefault();
- }
- onPointerUp = (e: PointerEvent): void => {
- document.removeEventListener("pointermove", this.onPointerMove)
- document.removeEventListener("pointerup", this.onPointerUp)
- e.stopPropagation();
- if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientY - this._downY) < 4) {
- SelectionManager.SelectDoc(this, e.ctrlKey);
+ stopPropogation = (e: React.SyntheticEvent) => {
+ e.stopPropagation();
}
- }
- stopPropogation = (e: React.SyntheticEvent) => {
- e.stopPropagation();
- }
- deleteClicked = (): void => {
- if (this.props.RemoveDocument) {
- this.props.RemoveDocument(this.props.Document);
+ deleteClicked = (): void => {
+ if (this.props.removeDocument) {
+ this.props.removeDocument(this.props.Document);
+ }
+ }
+
+ fieldsClicked = (e: React.MouseEvent): void => {
+ if (this.props.addDocument) {
+ this.props.addDocument(Documents.KVPDocument(this.props.Document, { width: 300, height: 300 }), false);
+ }
+ }
+ fullScreenClicked = (e: React.MouseEvent): void => {
+ CollectionDockingView.Instance.OpenFullScreen(this.props.Document);
+ ContextMenu.Instance.clearItems();
+ ContextMenu.Instance.addItem({
+ description: "Close Full Screen",
+ event: this.closeFullScreenClicked
+ });
+ ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
}
- }
- fieldsClicked = (e: React.MouseEvent): void => {
- if (this.props.AddDocument) {
- this.props.AddDocument(Documents.KVPDocument(this.props.Document, { width: 300, height: 300 }), false);
+ closeFullScreenClicked = (e: React.MouseEvent): void => {
+ CollectionDockingView.Instance.CloseFullScreen();
+ ContextMenu.Instance.clearItems();
+ ContextMenu.Instance.addItem({
+ description: "Full Screen",
+ event: this.fullScreenClicked
+ });
+ ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
}
- }
- fullScreenClicked = (e: React.MouseEvent): void => {
- CollectionDockingView.Instance.OpenFullScreen(this.props.Document);
- ContextMenu.Instance.clearItems();
- ContextMenu.Instance.addItem({ description: "Close Full Screen", event: this.closeFullScreenClicked });
- ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
- }
- closeFullScreenClicked = (e: React.MouseEvent): void => {
- CollectionDockingView.Instance.CloseFullScreen();
- ContextMenu.Instance.clearItems();
- ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked })
- ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
- }
+ @action
+ public minimize = (): void => {
+ this.props.Document.SetData(
+ KeyStore.Minimized,
+ true as boolean,
+ BooleanField
+ );
+ SelectionManager.DeselectAll();
+ }
- @action
- drop = (e: Event, de: DragManager.DropEvent) => {
- if (de.data instanceof DragManager.LinkDragData) {
- let sourceDoc: Document = de.data.linkSourceDocumentView.props.Document;
- let destDoc: Document = this.props.Document;
- if (this.props.isTopMost) {
- return;
- }
- let linkDoc: Document = new Document();
+ @undoBatch
+ @action
+ drop = (e: Event, de: DragManager.DropEvent) => {
+ if (de.data instanceof DragManager.LinkDragData) {
+ let sourceDoc: Document = de.data.linkSourceDocument;
+ let destDoc: Document = this.props.Document;
+ let linkDoc: Document = new Document();
- destDoc.GetTAsync(KeyStore.Prototype, Document).then((protoDest) =>
- sourceDoc.GetTAsync(KeyStore.Prototype, Document).then((protoSrc) => runInAction(() => {
- linkDoc.Set(KeyStore.Title, new TextField("New Link"));
- linkDoc.Set(KeyStore.LinkDescription, new TextField(""));
- linkDoc.Set(KeyStore.LinkTags, new TextField("Default"));
+ 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"));
- let dstTarg = (protoDest ? protoDest : destDoc);
- let srcTarg = (protoSrc ? protoSrc : sourceDoc);
- linkDoc.Set(KeyStore.LinkedToDocs, dstTarg);
- linkDoc.Set(KeyStore.LinkedFromDocs, srcTarg);
- dstTarg.GetOrCreateAsync(KeyStore.LinkedFromDocs, ListField, field => { (field as ListField<Document>).Data.push(linkDoc) })
- srcTarg.GetOrCreateAsync(KeyStore.LinkedToDocs, ListField, field => { (field as ListField<Document>).Data.push(linkDoc) })
- }))
- )
- e.stopPropagation();
+ let dstTarg = protoDest ? protoDest : destDoc;
+ let srcTarg = protoSrc ? protoSrc : sourceDoc;
+ linkDoc.Set(KeyStore.LinkedToDocs, dstTarg);
+ linkDoc.Set(KeyStore.LinkedFromDocs, srcTarg);
+ const prom1 = new Promise(resolve => dstTarg.GetOrCreateAsync(
+ KeyStore.LinkedFromDocs,
+ ListField,
+ field => {
+ (field as ListField<Document>).Data.push(linkDoc);
+ resolve();
+ }
+ ));
+ 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());
+ })
+ )
+ );
+ e.stopPropagation();
+ }
}
- }
- onDrop = (e: React.DragEvent) => {
- if (e.isDefaultPrevented()) {
- return;
+ onDrop = (e: React.DragEvent) => {
+ if (e.isDefaultPrevented()) {
+ return;
+ }
+ let text = e.dataTransfer.getData("text/plain");
+ if (text && text.startsWith("<div")) {
+ let oldLayout = this.props.Document.GetText(KeyStore.Layout, "");
+ let layout = text.replace("{layout}", oldLayout);
+ this.props.Document.SetText(KeyStore.Layout, layout);
+ e.stopPropagation();
+ e.preventDefault();
+ }
}
- let text = e.dataTransfer.getData("text/plain");
- if (text && text.startsWith("<div")) {
- let oldLayout = this.props.Document.GetText(KeyStore.Layout, "");
- let layout = text.replace("{layout}", oldLayout);
- this.props.Document.SetText(KeyStore.Layout, layout);
- e.stopPropagation();
- e.preventDefault();
+
+ @action
+ onContextMenu = (e: React.MouseEvent): void => {
+ e.stopPropagation();
+ let moved =
+ Math.abs(this._downX - e.clientX) > 3 ||
+ Math.abs(this._downY - e.clientY) > 3;
+ if (moved || e.isDefaultPrevented()) {
+ e.preventDefault();
+ return;
+ }
+ e.preventDefault();
+
+ if (!this.isMinimized()) {
+ ContextMenu.Instance.addItem({
+ description: "Minimize",
+ event: this.minimize
+ });
+ }
+ ContextMenu.Instance.addItem({
+ description: "Full Screen",
+ event: this.fullScreenClicked
+ });
+ ContextMenu.Instance.addItem({
+ description: "Fields",
+ event: this.fieldsClicked
+ });
+ ContextMenu.Instance.addItem({
+ description: "Center",
+ event: () => this.props.focus(this.props.Document)
+ });
+ ContextMenu.Instance.addItem({
+ description: "Open Right",
+ event: () =>
+ CollectionDockingView.Instance.AddRightSplit(this.props.Document)
+ });
+ ContextMenu.Instance.addItem({
+ description: "Copy URL",
+ event: () => {
+ Utils.CopyText(ServerUtils.prepend("/doc/" + this.props.Document.Id));
+ }
+ });
+ ContextMenu.Instance.addItem({
+ description: "Copy ID",
+ event: () => {
+ Utils.CopyText(this.props.Document.Id);
+ }
+ });
+ //ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) })
+ ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
+ if (!this.topMost) {
+ // DocumentViews should stop propagation of this event
+ e.stopPropagation();
+ }
+ ContextMenu.Instance.addItem({
+ description: "Pin to Presentation",
+ event: () => PresentationView.Instance.PinDoc(this.props.Document)
+ });
+
+ ContextMenu.Instance.addItem({
+ description: "Delete",
+ event: this.deleteClicked
+ });
+ ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
+ SelectionManager.SelectDoc(this, e.ctrlKey);
}
- }
- @action
- onContextMenu = (e: React.MouseEvent): void => {
- e.stopPropagation();
- let moved = Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3;
- if (moved || e.isDefaultPrevented()) {
- e.preventDefault()
- return;
+ isMinimized = () => {
+ let field = this.props.Document.GetT(KeyStore.Minimized, BooleanField);
+ if (field && field !== FieldWaiting) {
+ return field.Data;
+ }
}
- e.preventDefault()
- ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked })
- ContextMenu.Instance.addItem({ description: "Fields", event: this.fieldsClicked })
- ContextMenu.Instance.addItem({ description: "Center", event: () => this.props.focus(this.props.Document) })
- ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document) })
- ContextMenu.Instance.addItem({
- description: "Copy URL",
- event: () => {
- Utils.CopyText(ServerUtils.prepend("/doc/" + this.props.Document.Id));
- }
- });
- ContextMenu.Instance.addItem({
- description: "Copy ID",
- event: () => {
- Utils.CopyText(this.props.Document.Id);
- }
- });
- //ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) })
- ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
- if (!this.topMost) {
- // DocumentViews should stop propagation of this event
- e.stopPropagation();
+ @action
+ expand = () => {
+ this.props.Document.SetData(
+ KeyStore.Minimized,
+ false as boolean,
+ BooleanField
+ );
}
- ContextMenu.Instance.addItem({ description: "Pin to Presentation", event: () => PresentationView.Instance.PinDoc(this.props.Document) })
- ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked })
- ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
- SelectionManager.SelectDoc(this, e.ctrlKey);
- }
+ isSelected = () => SelectionManager.IsSelected(this);
+ select = (ctrlPressed: boolean) => {
+ SelectionManager.SelectDoc(this, ctrlPressed);
+ }
- isSelected = () => {
- return SelectionManager.IsSelected(this);
- }
+ @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}
+ />);
+ }
- select = (ctrlPressed: boolean) => {
- SelectionManager.SelectDoc(this, ctrlPressed)
- }
+ render() {
+ if (!this.props.Document) {
+ return null;
+ }
- render() {
- if (!this.props.Document) {
- return (null);
+ var scaling = this.props.ContentScaling();
+ var nativeWidth = this.nativeWidth;
+ var nativeHeight = this.nativeHeight;
+
+ if (this.isMinimized()) {
+ return (
+ <div
+ className="minimized-box"
+ ref={this._mainCont}
+ style={{
+ transformOrigin: "left top",
+ transform: `scale(${scaling} , ${scaling})`
+ }}
+ onClick={this.expand}
+ onDrop={this.onDrop}
+ onPointerDown={this.onPointerDown}
+ />
+ );
+ } else {
+ var backgroundcolor = this.props.Document.GetText(
+ KeyStore.BackgroundColor,
+ ""
+ );
+ return (
+ <div
+ className="documentView-node"
+ ref={this._mainCont}
+ style={{
+ background: backgroundcolor,
+ width: nativeWidth > 0 ? nativeWidth.toString() + "px" : "100%",
+ height: nativeHeight > 0 ? nativeHeight.toString() + "px" : "100%",
+ transformOrigin: "left top",
+ transform: `scale(${scaling} , ${scaling})`
+ }}
+ onDrop={this.onDrop}
+ onContextMenu={this.onContextMenu}
+ onPointerDown={this.onPointerDown}
+ >
+ {this.contents}
+ </div>
+ );
+ }
}
- var scaling = this.props.ContentScaling();
- var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
- var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0);
- var backgroundcolor = this.props.Document.GetText(KeyStore.BackgroundColor, "");
- return (
- <div className="documentView-node" ref={this._mainCont}
- style={{
- background: backgroundcolor,
- width: nativeWidth > 0 ? nativeWidth.toString() + "px" : "100%",
- height: nativeHeight > 0 ? nativeHeight.toString() + "px" : "100%",
- transformOrigin: "left top",
- transform: `scale(${scaling} , ${scaling})`
- }}
- onDrop={this.onDrop}
- onContextMenu={this.onContextMenu}
- onPointerDown={this.onPointerDown} >
- <DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} layoutKey={KeyStore.Layout} />
- </div >
- )
- }
} \ No newline at end of file
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 4e83ec7b9..562de4827 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,13 +1,13 @@
-import React = require("react")
+import React = require("react");
import { observer } from "mobx-react";
import { computed } from "mobx";
-import { Field, FieldWaiting, FieldValue } from "../../../fields/Field";
+import { Field, FieldWaiting, FieldValue, Opt } from "../../../fields/Field";
import { Document } from "../../../fields/Document";
import { TextField } from "../../../fields/TextField";
import { NumberField } from "../../../fields/NumberField";
import { RichTextField } from "../../../fields/RichTextField";
import { ImageField } from "../../../fields/ImageField";
-import { VideoField } from "../../../fields/VideoField"
+import { VideoField } from "../../../fields/VideoField";
import { Key } from "../../../fields/Key";
import { FormattedTextBox } from "./FormattedTextBox";
import { ImageBox } from "./ImageBox";
@@ -19,6 +19,10 @@ import { ListField } from "../../../fields/ListField";
import { DocumentContentsView } from "./DocumentContentsView";
import { Transform } from "../../util/Transform";
import { KeyStore } from "../../../fields/KeyStore";
+import { returnFalse, emptyDocFunction } from "../../../Utils";
+import { CollectionView } from "../collections/CollectionView";
+import { CollectionPDFView } from "../collections/CollectionPDFView";
+import { CollectionVideoView } from "../collections/CollectionVideoView";
//
@@ -28,80 +32,91 @@ import { KeyStore } from "../../../fields/KeyStore";
//
export interface FieldViewProps {
fieldKey: Key;
- doc: Document;
+ ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
+ Document: Document;
isSelected: () => boolean;
- select: () => void;
+ select: (isCtrlPressed: boolean) => void;
isTopMost: boolean;
selectOnLoad: boolean;
- bindings: any;
+ addDocument?: (document: Document, allowDuplicates?: boolean) => boolean;
+ removeDocument?: (document: Document) => boolean;
+ moveDocument?: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
+ ScreenToLocalTransform: () => Transform;
+ active: () => boolean;
+ onActiveChanged: (isActive: boolean) => void;
+ focus: (doc: Document) => void;
}
@observer
export class FieldView extends React.Component<FieldViewProps> {
public static LayoutString(fieldType: { name: string }, fieldStr: string = "DataKey") {
- return `<${fieldType.name} doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={${fieldStr}} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost} />`;
+ return `<${fieldType.name} {...props} fieldKey={${fieldStr}} />`;
}
@computed
get field(): FieldValue<Field> {
- const { doc, fieldKey } = this.props;
+ const { Document: doc, fieldKey } = this.props;
return doc.Get(fieldKey);
}
render() {
const field = this.field;
if (!field) {
- return <p>{'<null>'}</p>
+ return <p>{'<null>'}</p>;
}
if (field instanceof TextField) {
- return <p>{field.Data}</p>
+ return <p>{field.Data}</p>;
}
else if (field instanceof RichTextField) {
- return <FormattedTextBox {...this.props} />
+ return <FormattedTextBox {...this.props} />;
}
else if (field instanceof ImageField) {
- return <ImageBox {...this.props} />
+ return <ImageBox {...this.props} />;
}
else if (field instanceof VideoField) {
- return <VideoBox {...this.props} />
+ return <VideoBox {...this.props} />;
}
else if (field instanceof AudioField) {
- return <AudioBox {...this.props} />
+ return <AudioBox {...this.props} />;
}
else if (field instanceof Document) {
- return (<DocumentContentsView Document={field}
- AddDocument={undefined}
- RemoveDocument={undefined}
- ScreenToLocalTransform={() => Transform.Identity}
- ContentScaling={() => 1}
- PanelWidth={() => 100}
- PanelHeight={() => 100}
- isTopMost={true}
- SelectOnLoad={false}
- focus={() => { }}
- isSelected={() => false}
- select={() => false}
- layoutKey={KeyStore.Layout}
- ContainingCollectionView={undefined} />)
+ return (
+ <DocumentContentsView Document={field}
+ addDocument={undefined}
+ removeDocument={undefined}
+ opacity={1}
+ ScreenToLocalTransform={Transform.Identity}
+ ContentScaling={() => 1}
+ PanelWidth={() => 100}
+ PanelHeight={() => 100}
+ isTopMost={true} //TODO Why is this top most?
+ selectOnLoad={false}
+ focus={emptyDocFunction}
+ isSelected={returnFalse}
+ select={returnFalse}
+ layoutKey={KeyStore.Layout}
+ ContainingCollectionView={this.props.ContainingCollectionView}
+ parentActive={this.props.active}
+ onActiveChanged={this.props.onActiveChanged} />
+ );
}
else if (field instanceof ListField) {
return (<div>
- {(field as ListField<Field>).Data.map(f => {
- return f instanceof Document ? f.Title : f.GetValue().toString();
- }).join(", ")}
- </div>)
+ {(field as ListField<Field>).Data.map(f => f instanceof Document ? f.Title : f.GetValue().toString()).join(", ")}
+ </div>);
}
// bcz: this belongs here, but it doesn't render well so taking it out for now
// else if (field instanceof HtmlField) {
// return <WebBox {...this.props} />
// }
else if (field instanceof NumberField) {
- return <p>{field.Data}</p>
+ return <p>{field.Data}</p>;
}
- else if (field != FieldWaiting) {
- return <p>{JSON.stringify(field.GetValue())}</p>
+ else if (field !== FieldWaiting) {
+ return <p>{JSON.stringify(field.GetValue())}</p>;
+ }
+ else {
+ return <p> {"Waiting for server..."} </p>;
}
- 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 512ad7d70..87c1bcb1e 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -2,29 +2,28 @@ import { action, IReactionDisposer, reaction } from "mobx";
import { baseKeymap } from "prosemirror-commands";
import { history, redo, undo } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
-import { schema } from "../../util/RichTextSchema";
-import { EditorState, Transaction, } from "prosemirror-state";
+import { EditorState, Plugin, Transaction } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
-import { Opt, FieldWaiting } from "../../../fields/Field";
-import "./FormattedTextBox.scss";
-import React = require("react")
+import { FieldWaiting, Opt } from "../../../fields/Field";
import { RichTextField } from "../../../fields/RichTextField";
-import { FieldViewProps, FieldView } from "./FieldView";
-import { Plugin } from 'prosemirror-state'
-import { Decoration, DecorationSet } from 'prosemirror-view'
-import { TooltipTextMenu } from "../../util/TooltipTextMenu"
-import { ContextMenu } from "../../views/ContextMenu";
import { inpRules } from "../../util/RichTextRules";
+import { schema } from "../../util/RichTextSchema";
+import { TooltipTextMenu } from "../../util/TooltipTextMenu";
+import { ContextMenu } from "../../views/ContextMenu";
+import { Main } from "../Main";
+import { FieldView, FieldViewProps } from "./FieldView";
+import "./FormattedTextBox.scss";
+import React = require("react");
+import { TextField } from "../../../fields/TextField";
+import { KeyStore } from "../../../fields/KeyStore";
+import { MainOverlayTextBox } from "../MainOverlayTextBox";
const { buildMenuItems } = require("prosemirror-example-setup");
const { menuBar } = require("prosemirror-menu");
-
-
-
// FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document
//
// HTML Markup: <FormattedTextBox Doc={Document's ID} FieldKey={Key's name + "Key"}
-//
+//
// In Code, the node's HTML is specified in the document's parameterized structure as:
// document.SetField(KeyStore.Layout, "<FormattedTextBox doc={doc} fieldKey={<KEYNAME>Key} />");
// and the node's binding to the specified document KEYNAME as:
@@ -33,16 +32,24 @@ const { menuBar } = require("prosemirror-menu");
// 'fieldKey' property to the Key stored in LayoutKeys
// and 'doc' property to the document that is being rendered
//
-// When rendered() by React, this extracts the TextController from the Document stored at the
-// specified Key and assigns it to an HTML input node. When changes are made to this node,
+// When rendered() by React, this extracts the TextController from the Document stored at the
+// 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> {
- public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(FormattedTextBox, fieldStr) }
+export interface FormattedTextBoxOverlay {
+ isOverlay?: boolean;
+}
+
+export class FormattedTextBox extends React.Component<(FieldViewProps & FormattedTextBoxOverlay)> {
+ public static LayoutString(fieldStr: string = "DataKey") {
+ return FieldView.LayoutString(FormattedTextBox, fieldStr);
+ }
private _ref: React.RefObject<HTMLDivElement>;
private _editorView: Opt<EditorView>;
private _reactionDisposer: Opt<IReactionDisposer>;
+ private _inputReactionDisposer: Opt<IReactionDisposer>;
+ private _proxyReactionDisposer: Opt<IReactionDisposer>;
constructor(props: FieldViewProps) {
super(props);
@@ -51,31 +58,75 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
this.onChange = this.onChange.bind(this);
}
+ _applyingChange: boolean = false;
+
dispatchTransaction = (tx: Transaction) => {
if (this._editorView) {
const state = this._editorView.state.apply(tx);
this._editorView.updateState(state);
- const { doc, fieldKey } = this.props;
- doc.SetDataOnPrototype(fieldKey, JSON.stringify(state.toJSON()), RichTextField);
+ this._applyingChange = true;
+ this.props.Document.SetDataOnPrototype(
+ this.props.fieldKey,
+ JSON.stringify(state.toJSON()),
+ RichTextField
+ );
+ this.props.Document.SetDataOnPrototype(KeyStore.DocumentText, state.doc.textBetween(0, state.doc.content.size, "\n\n"), TextField);
+ this._applyingChange = false;
// doc.SetData(fieldKey, JSON.stringify(state.toJSON()), RichTextField);
}
}
componentDidMount() {
- let state: EditorState;
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),
+ ]
};
- let field = this.props.doc.GetT(this.props.fieldKey, RichTextField);
- if (field && field != FieldWaiting && field.Data) {
+ if (this.props.isOverlay) {
+ this._inputReactionDisposer = reaction(() => MainOverlayTextBox.Instance.TextDoc && MainOverlayTextBox.Instance.TextDoc.Id,
+ () => {
+ if (this._editorView) {
+ this._editorView.destroy();
+ }
+
+ this.setupEditor(config);
+ }
+ );
+ } else {
+ this._proxyReactionDisposer = reaction(() => this.props.isSelected(),
+ () => this.props.isSelected() && MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform()));
+ }
+
+ this._reactionDisposer = reaction(
+ () => {
+ const field = this.props.Document ? this.props.Document.GetT(this.props.fieldKey, RichTextField) : undefined;
+ return field && field !== FieldWaiting ? field.Data : undefined;
+ },
+ field => {
+ if (field && this._editorView && !this._applyingChange) {
+ this._editorView.updateState(
+ EditorState.fromJSON(config, JSON.parse(field))
+ );
+ }
+ }
+ );
+ this.setupEditor(config);
+ }
+
+ private setupEditor(config: any) {
+ let state: EditorState;
+ 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 {
state = EditorState.create(config);
@@ -87,16 +138,8 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
});
}
- this._reactionDisposer = reaction(() => {
- const field = this.props.doc.GetT(this.props.fieldKey, RichTextField);
- return field && field != FieldWaiting ? field.Data : undefined;
- }, (field) => {
- if (field && this._editorView) {
- this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field)));
- }
- })
if (this.props.selectOnLoad) {
- this.props.select();
+ this.props.select(false);
this._editorView!.focus();
}
}
@@ -108,6 +151,12 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
if (this._reactionDisposer) {
this._reactionDisposer();
}
+ if (this._inputReactionDisposer) {
+ this._inputReactionDisposer();
+ }
+ if (this._proxyReactionDisposer) {
+ this._proxyReactionDisposer();
+ }
}
shouldComponentUpdate() {
@@ -116,22 +165,43 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
@action
onChange(e: React.ChangeEvent<HTMLInputElement>) {
- const { fieldKey, doc } = this.props;
- doc.SetOnPrototype(fieldKey, new RichTextField(e.target.value))
+ const { fieldKey, Document } = this.props;
+ Document.SetOnPrototype(fieldKey, new RichTextField(e.target.value));
// doc.SetData(fieldKey, e.target.value, RichTextField);
}
onPointerDown = (e: React.PointerEvent): void => {
+ 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();
}
}
- //REPLACE THIS WITH CAPABILITIES SPECIFIC TO THIS TYPE OF NODE
- textCapability = (e: React.MouseEvent): void => {
+ onFocused = (e: React.FocusEvent): void => {
+ if (!this.props.isOverlay) {
+ MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform());
+ } else {
+ if (this._ref.current) {
+ this._ref.current.scrollTop = MainOverlayTextBox.Instance.TextScroll;
+ }
+ }
}
+ //REPLACE THIS WITH CAPABILITIES SPECIFIC TO THIS TYPE OF NODE
+ textCapability = (e: React.MouseEvent): void => { };
+
specificContextMenu = (e: React.MouseEvent): void => {
- ContextMenu.Instance.addItem({ description: "Text Capability", event: this.textCapability });
+ ContextMenu.Instance.addItem({
+ description: "Text Capability",
+ event: this.textCapability
+ });
+
// ContextMenu.Instance.addItem({
// description: "Submenu",
// items: [
@@ -144,19 +214,21 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
// ]
// })
// e.stopPropagation()
-
}
onPointerWheel = (e: React.WheelEvent): void => {
- e.stopPropagation();
+ if (this.props.isSelected()) {
+ e.stopPropagation();
+ }
}
tooltipMenuPlugin() {
+ let myprops = this.props;
return new Plugin({
view(_editorView) {
- return new TooltipTextMenu(_editorView)
+ return new TooltipTextMenu(_editorView, myprops);
}
- })
+ });
}
onKeyPress(e: React.KeyboardEvent) {
e.stopPropagation();
@@ -165,13 +237,20 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
// (e.nativeEvent as any).DASHFormattedTextBoxHandled = true;
}
render() {
- return (<div className="formattedTextBox-cont"
- onKeyDown={this.onKeyPress}
- onKeyPress={this.onKeyPress}
- onPointerDown={this.onPointerDown}
- onContextMenu={this.specificContextMenu}
- // tfs: do we need this event handler
- onWheel={this.onPointerWheel}
- ref={this._ref} />)
- }
-} \ No newline at end of file
+ return (
+ <div
+ style={{ overflowY: this.props.isSelected() || this.props.isOverlay ? "scroll" : "hidden" }}
+ className={`formattedTextBox-cont`}
+ onKeyDown={this.onKeyPress}
+ onKeyPress={this.onKeyPress}
+ onFocus={this.onFocused}
+ onPointerUp={this.onPointerUp}
+ onPointerDown={this.onPointerDown}
+ onContextMenu={this.specificContextMenu}
+ // tfs: do we need this event handler
+ onWheel={this.onPointerWheel}
+ ref={this._ref}
+ />
+ );
+ }
+}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 60d1f7214..6b0a3a799 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -9,13 +9,13 @@ import { KeyStore } from '../../../fields/KeyStore';
import { ContextMenu } from "../../views/ContextMenu";
import { FieldView, FieldViewProps } from './FieldView';
import "./ImageBox.scss";
-import React = require("react")
+import React = require("react");
import { Utils } from '../../../Utils';
@observer
export class ImageBox extends React.Component<FieldViewProps> {
- public static LayoutString() { return FieldView.LayoutString(ImageBox) }
+ public static LayoutString() { return FieldView.LayoutString(ImageBox); }
private _ref: React.RefObject<HTMLDivElement>;
private _imgRef: React.RefObject<HTMLImageElement>;
private _downX: number = 0;
@@ -39,7 +39,7 @@ export class ImageBox extends React.Component<FieldViewProps> {
onLoad = (target: any) => {
var h = this._imgRef.current!.naturalHeight;
var w = this._imgRef.current!.naturalWidth;
- this.props.doc.SetNumber(KeyStore.NativeHeight, this.props.doc.GetNumber(KeyStore.NativeWidth, 0) * h / w)
+ this.props.Document.SetNumber(KeyStore.NativeHeight, this.props.Document.GetNumber(KeyStore.NativeWidth, 0) * h / w);
}
componentDidMount() {
@@ -86,31 +86,31 @@ export class ImageBox extends React.Component<FieldViewProps> {
onMoveNextRequest={action(() =>
this._photoIndex = (this._photoIndex + 1) % images.length
)}
- />)
+ />);
}
}
specificContextMenu = (e: React.MouseEvent): void => {
- let field = this.props.doc.GetT(this.props.fieldKey, ImageField);
+ let field = this.props.Document.GetT(this.props.fieldKey, ImageField);
if (field && field !== FieldWaiting) {
let url = field.Data.href;
ContextMenu.Instance.addItem({
description: "Copy path", event: () => {
- Utils.CopyText(url)
+ Utils.CopyText(url);
}
});
}
}
render() {
- let field = this.props.doc.Get(this.props.fieldKey);
- let path = field == FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
+ let field = this.props.Document.Get(this.props.fieldKey);
+ let path = field === FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
field instanceof ImageField ? field.Data.href : "http://www.cs.brown.edu/~bcz/face.gif";
- let nativeWidth = this.props.doc.GetNumber(KeyStore.NativeWidth, 1);
+ let nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 1);
return (
<div className="imageBox-cont" onPointerDown={this.onPointerDown} ref={this._ref} onContextMenu={this.specificContextMenu}>
<img src={path} width={nativeWidth} alt="Image not found" ref={this._imgRef} onLoad={this.onLoad} />
{this.lightbox(path)}
- </div>)
+ </div>);
}
} \ No newline at end of file
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 283c1f732..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";
+import React = require("react");
@observer
export class KeyValueBox extends React.Component<FieldViewProps> {
+ private _mainCont = React.createRef<HTMLDivElement>();
- public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(KeyValueBox, fieldStr) }
+ 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) {
@@ -32,19 +34,21 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
@action
onEnterKey = (e: React.KeyboardEvent): void => {
- if (e.key == 'Enter') {
+ if (e.key === 'Enter') {
if (this._keyInput && this._valueInput) {
- let doc = this.props.doc.GetT(KeyStore.Data, Document);
- if (!doc || doc == FieldWaiting) {
- return
+ let doc = this.props.Document.GetT(KeyStore.Data, Document);
+ if (!doc || doc === FieldWaiting) {
+ return;
}
let realDoc = doc;
- let script = CompileScript(this._valueInput, undefined, true);
+ let script = CompileScript(this._valueInput, { addReturn: true });
if (!script.compiled) {
return;
}
- let field = script();
+ let res = script.run();
+ if (!res.success) return;
+ const field = res.result;
if (field instanceof Field) {
realDoc.Set(new Key(this._keyInput), field);
} else {
@@ -53,8 +57,8 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
realDoc.Set(new Key(this._keyInput), dataField);
}
}
- this._keyInput = ""
- this._valueInput = ""
+ this._keyInput = "";
+ this._valueInput = "";
}
}
}
@@ -69,9 +73,9 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
}
createTable = () => {
- let doc = this.props.doc.GetT(KeyStore.Data, Document);
- if (!doc || doc == FieldWaiting) {
- return <tr><td>Loading...</td></tr>
+ let doc = this.props.Document.GetT(KeyStore.Data, Document);
+ if (!doc || doc === FieldWaiting) {
+ return <tr><td>Loading...</td></tr>;
}
let realDoc = doc;
@@ -82,13 +86,13 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
if (!(key in ids)) {
ids[key] = key;
}
- })
+ });
}
let rows: JSX.Element[] = [];
let i = 0;
for (let key in ids) {
- rows.push(<KeyValuePair doc={realDoc} rowStyle={"keyValueBox-" + (i++ % 2 ? "oddRow" : "evenRow")} fieldId={key} key={key} />)
+ rows.push(<KeyValuePair doc={realDoc} keyWidth={100 - this.splitPercentage} rowStyle={"keyValueBox-" + (i++ % 2 ? "oddRow" : "evenRow")} fieldId={key} key={key} />);
}
return rows;
}
@@ -103,27 +107,53 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
this._valueInput = e.currentTarget.value;
}
- newKeyValue = () => {
- return (
- <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>
+ newKeyValue = () =>
+ (
+ <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>
- </div>)
+ {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..01701e02c 100644
--- a/src/client/views/nodes/KeyValuePair.scss
+++ b/src/client/views/nodes/KeyValuePair.scss
@@ -1,12 +1,28 @@
-@import "../global_variables";
+@import "../globalCssVariables";
-.container{
- display: flex;
- flex-direction: row;
- flex-wrap: nowrap;
- justify-content: space-between;
-}
-.delete{
- color: red;
+.keyValuePair-td-key {
+ display:inline-block;
+ .keyValuePair-td-key-container{
+ width:100%;
+ height:100%;
+ display: flex;
+ flex-direction: row;
+ flex-wrap: nowrap;
+ justify-content: space-between;
+ .keyValuePair-td-key-delete{
+ position: relative;
+ background-color: transparent;
+ color:red;
+ }
+ .keyValuePair-keyField {
+ width:100%;
+ text-align: center;
+ position: relative;
+ overflow: auto;
+ }
+ }
+}
+.keyValuePair-td-value {
+ display:inline-block;
} \ No newline at end of file
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 7ed5ee272..5d69f23b2 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -1,16 +1,18 @@
+import { action, 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 "./KeyValueBox.scss";
-import "./KeyValuePair.scss";
-import React = require("react")
-import { FieldViewProps, FieldView } from './FieldView';
-import { Opt, Field } from '../../../fields/Field';
-import { observer } from "mobx-react"
-import { observable, action } from 'mobx';
import { Document } from '../../../fields/Document';
+import { Field, Opt } from '../../../fields/Field';
import { Key } from '../../../fields/Key';
-import { Server } from "../../Server"
-import { EditableView } from "../EditableView";
+import { emptyDocFunction, emptyFunction, returnFalse } from '../../../Utils';
+import { Server } from "../../Server";
import { CompileScript, ToField } from "../../util/Scripting";
+import { Transform } from '../../util/Transform';
+import { EditableView } from "../EditableView";
+import { FieldView, FieldViewProps } from './FieldView';
+import "./KeyValueBox.scss";
+import "./KeyValuePair.scss";
+import React = require("react");
// Represents one row in a key value plane
@@ -18,82 +20,82 @@ export interface KeyValuePairProps {
rowStyle: string;
fieldId: string;
doc: Document;
+ keyWidth: number;
}
@observer
export class KeyValuePair extends React.Component<KeyValuePairProps> {
- @observable
- private key: Opt<Key>
+ @observable private key: Opt<Key>;
constructor(props: KeyValuePairProps) {
super(props);
Server.GetField(this.props.fieldId,
- action((field: Opt<Field>) => {
- if (field) {
- this.key = field as Key;
- }
- }));
+ action((field: Opt<Field>) => field instanceof Key && (this.key = field)));
}
render() {
if (!this.key) {
- return <tr><td>error</td><td></td></tr>
-
+ return <tr><td>error</td><td /></tr>;
}
let props: FieldViewProps = {
- doc: this.props.doc,
+ Document: this.props.doc,
+ ContainingCollectionView: undefined,
fieldKey: this.key,
- isSelected: () => false,
- select: () => { },
+ isSelected: returnFalse,
+ select: emptyFunction,
isTopMost: false,
- bindings: {},
selectOnLoad: false,
- }
- let contents = (
- <FieldView {...props} />
- );
+ active: returnFalse,
+ onActiveChanged: emptyFunction,
+ ScreenToLocalTransform: Transform.Identity,
+ focus: emptyDocFunction,
+ };
+ let contents = <FieldView {...props} />;
return (
<tr className={this.props.rowStyle}>
- {/* <button>X</button> */}
- <td>
- <div className="container">
- <div>{this.key.Name}</div>
- <button className="delete" onClick={() => {
- let field = props.doc.Get(props.fieldKey);
- if (field && field instanceof Field) {
- props.doc.Set(props.fieldKey, undefined);
- }
- }}>X</button>
+ <td className="keyValuePair-td-key" style={{ width: `${this.props.keyWidth}%` }}>
+ <div className="keyValuePair-td-key-container">
+ <button className="keyValuePair-td-key-delete" onClick={() => {
+ let field = props.Document.Get(props.fieldKey);
+ 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.doc.Get(props.fieldKey);
- if (field && field instanceof Field) {
- return field.ToScriptString();
- }
- return field || "";
- }}
- SetValue={(value: string) => {
- let script = CompileScript(value, undefined, 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 field = script();
- if (field instanceof Field) {
- props.doc.Set(props.fieldKey, field);
- return true;
- } else {
- let dataField = ToField(field);
- if (dataField) {
- props.doc.Set(props.fieldKey, dataField);
+ return 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>
- )
+ );
}
} \ No newline at end of file
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 e81f8fec7..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);
@@ -30,7 +22,7 @@ interface Props {
linkName: String;
pairedDoc: Document;
type: String;
- showEditor: () => void
+ showEditor: () => void;
}
@observer
@@ -49,15 +41,16 @@ export class LinkBox extends React.Component<Props> {
} else if (contextDoc instanceof Document) {
this.props.pairedDoc.GetTAsync(KeyStore.Page, NumberField).then((pfield: any) => {
contextDoc.GetTAsync(KeyStore.CurPage, NumberField).then((cfield: any) => {
- if (pfield != cfield)
+ if (pfield !== cfield) {
contextDoc.SetNumber(KeyStore.CurPage, pfield.Data);
+ }
let contextView = DocumentManager.Instance.getDocumentView(contextDoc);
if (contextView) {
contextView.props.focus(contextDoc);
} else {
CollectionDockingView.Instance.AddRightSplit(contextDoc);
}
- })
+ });
});
}
});
@@ -80,7 +73,7 @@ export class LinkBox extends React.Component<Props> {
if (field) {
field.Data.splice(field.Data.indexOf(this.props.linkDoc));
}
- })
+ });
}
});
this.props.linkDoc.GetTAsync(KeyStore.LinkedToDocs, Document, field => {
@@ -89,7 +82,7 @@ export class LinkBox extends React.Component<Props> {
if (field) {
field.Data.splice(field.Data.indexOf(this.props.linkDoc));
}
- })
+ });
}
});
}
@@ -117,6 +110,6 @@ export class LinkBox extends React.Component<Props> {
<FontAwesomeIcon className="fa-icon-delete" icon="times" size="sm" /></div>
</div>
</div>
- )
+ );
}
} \ No newline at end of file
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/LinkEditor.tsx b/src/client/views/nodes/LinkEditor.tsx
index 3f7b4bf2d..bde50fed8 100644
--- a/src/client/views/nodes/LinkEditor.tsx
+++ b/src/client/views/nodes/LinkEditor.tsx
@@ -2,8 +2,8 @@ import { observable, computed, action } from "mobx";
import React = require("react");
import { SelectionManager } from "../../util/SelectionManager";
import { observer } from "mobx-react";
-import './LinkEditor.scss'
-import { KeyStore } from '../../../fields/KeyStore'
+import './LinkEditor.scss';
+import { KeyStore } from '../../../fields/KeyStore';
import { props } from "bluebird";
import { DocumentView } from "./DocumentView";
import { Document } from "../../../fields/Document";
@@ -43,7 +43,7 @@ export class LinkEditor extends React.Component<Props> {
<div className="save-button" onPointerDown={this.onSaveButtonPressed}>SAVE</div>
</div>
- )
+ );
}
@action
diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx
index 5eeb40772..ac09da305 100644
--- a/src/client/views/nodes/LinkMenu.tsx
+++ b/src/client/views/nodes/LinkMenu.tsx
@@ -13,7 +13,7 @@ import React = require("react");
interface Props {
docView: DocumentView;
- changeFlyout: () => void
+ changeFlyout: () => void;
}
@observer
@@ -24,10 +24,10 @@ export class LinkMenu extends React.Component<Props> {
renderLinkItems(links: Document[], key: Key, type: string) {
return links.map(link => {
let doc = link.GetT(key, Document);
- if (doc && doc != FieldWaiting) {
- return <LinkBox key={doc.Id} linkDoc={link} linkName={link.Title} pairedDoc={doc} showEditor={action(() => this._editingLink = link)} type={type} />
+ if (doc && doc !== FieldWaiting) {
+ return <LinkBox key={doc.Id} linkDoc={link} linkName={link.Title} pairedDoc={doc} showEditor={action(() => this._editingLink = link)} type={type} />;
}
- })
+ });
}
render() {
@@ -43,11 +43,11 @@ export class LinkMenu extends React.Component<Props> {
{this.renderLinkItems(linkFrom, KeyStore.LinkedFromDocs, "Source: ")}
</div>
</div>
- )
+ );
} else {
return (
<LinkEditor linkDoc={this._editingLink} showLinks={action(() => this._editingLink = undefined)}></LinkEditor>
- )
+ );
}
}
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index ad947afd5..830dfe6c6 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -1,12 +1,16 @@
.react-pdf__Page {
transform-origin: left top;
position: absolute;
+ top: 0;
+ left:0;
}
.react-pdf__Document {
position: absolute;
}
.pdfBox-buttonTray {
position:absolute;
+ top: 0;
+ left:0;
z-index: 25;
}
.pdfBox-contentContainer {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 28a1f9757..81ceb37f6 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -1,5 +1,5 @@
import * as htmlToImage from "html-to-image";
-import { action, computed, observable, reaction, IReactionDisposer, trace, keys } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
import { observer } from "mobx-react";
import 'react-image-lightbox/style.css';
import Measure from "react-measure";
@@ -10,15 +10,15 @@ import { FieldWaiting, Opt } from '../../../fields/Field';
import { ImageField } from '../../../fields/ImageField';
import { KeyStore } from '../../../fields/KeyStore';
import { PDFField } from '../../../fields/PDFField';
+import { RouteStore } from "../../../server/RouteStore";
import { Utils } from '../../../Utils';
import { Annotation } from './Annotation';
import { FieldView, FieldViewProps } from './FieldView';
import "./ImageBox.scss";
import "./PDFBox.scss";
import { Sticky } from './Sticky'; //you should look at sticky and annotation, because they are used here
-import React = require("react")
-import { RouteStore } from "../../../server/RouteStore";
-import { NumberField } from "../../../fields/NumberField";
+import React = require("react");
+import { SelectionManager } from "../../util/SelectionManager";
/** ALSO LOOK AT: Annotation.tsx, Sticky.tsx
* This method renders PDF and puts all kinds of functionalities such as annotation, highlighting,
@@ -55,9 +55,11 @@ import { NumberField } from "../../../fields/NumberField";
export class PDFBox extends React.Component<FieldViewProps> {
public static LayoutString() { return FieldView.LayoutString(PDFBox); }
- private _mainDiv = React.createRef<HTMLDivElement>()
+ private _mainDiv = React.createRef<HTMLDivElement>();
private _pdf = React.createRef<HTMLCanvasElement>();
+ @observable private _renderAsSvg = true;
+
//very useful for keeping track of X and y position throughout the PDF Canvas
private initX: number = 0;
private initY: number = 0;
@@ -70,7 +72,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
private _currTool: any; //keeps track of current tool button reference
private _drawToolOn: boolean = false; //boolean that keeps track of the drawing tool
- private _drawTool = React.createRef<HTMLButtonElement>()//drawing tool button reference
+ private _drawTool = React.createRef<HTMLButtonElement>();//drawing tool button reference
private _colorTool = React.createRef<HTMLButtonElement>(); //color button reference
private _currColor: string = "black"; //current color that user selected (for ink/pen)
@@ -83,18 +85,18 @@ export class PDFBox extends React.Component<FieldViewProps> {
@observable private _perPageInfo: Object[] = []; //stores pageInfo
@observable private _pageInfo: any = { area: [], divs: [], anno: [] }; //divs is array of objects linked to anno
- @observable private _currAnno: any = []
+ @observable private _currAnno: any = [];
@observable private _interactive: boolean = false;
@observable private _loaded: boolean = false;
- @computed private get curPage() { return this.props.doc.GetNumber(KeyStore.CurPage, 1); }
- @computed private get thumbnailPage() { return this.props.doc.GetNumber(KeyStore.ThumbnailPage, -1); }
+ @computed private get curPage() { return this.props.Document.GetNumber(KeyStore.CurPage, 1); }
+ @computed private get thumbnailPage() { return this.props.Document.GetNumber(KeyStore.ThumbnailPage, -1); }
componentDidMount() {
this._reactionDisposer = reaction(
- () => [this.curPage, this.thumbnailPage],
+ () => [SelectionManager.SelectedDocuments().slice()],
() => {
- if (this.curPage > 0 && this.thumbnailPage > 0 && this.curPage != this.thumbnailPage) {
+ if (this.curPage > 0 && this.thumbnailPage > 0 && this.curPage !== this.thumbnailPage && !this.props.isSelected()) {
this.saveThumbnail();
this._interactive = true;
}
@@ -151,7 +153,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
*/
makeEditableAndHighlight = (colour: string) => {
var range, sel = window.getSelection();
- if (sel.rangeCount && sel.getRangeAt) {
+ if (sel && sel.rangeCount && sel.getRangeAt) {
range = sel.getRangeAt(0);
}
document.designMode = "on";
@@ -159,31 +161,31 @@ export class PDFBox extends React.Component<FieldViewProps> {
document.execCommand("HiliteColor", false, colour);
}
- if (range) {
+ if (range && sel) {
sel.removeAllRanges();
sel.addRange(range);
let obj: Object = { parentDivs: [], spans: [] };
//@ts-ignore
- if (range.commonAncestorContainer.className == 'react-pdf__Page__textContent') { //multiline highlighting case
- obj = this.highlightNodes(range.commonAncestorContainer.childNodes)
+ if (range.commonAncestorContainer.className === 'react-pdf__Page__textContent') { //multiline highlighting case
+ obj = this.highlightNodes(range.commonAncestorContainer.childNodes);
} else { //single line highlighting case
- let parentDiv = range.commonAncestorContainer.parentElement
+ let parentDiv = range.commonAncestorContainer.parentElement;
if (parentDiv) {
- if (parentDiv.className == 'react-pdf__Page__textContent') { //when highlight is overwritten
- obj = this.highlightNodes(parentDiv.childNodes)
+ if (parentDiv.className === 'react-pdf__Page__textContent') { //when highlight is overwritten
+ obj = this.highlightNodes(parentDiv.childNodes);
} else {
parentDiv.childNodes.forEach((child) => {
- if (child.nodeName == 'SPAN') {
+ if (child.nodeName === 'SPAN') {
//@ts-ignore
- obj.parentDivs.push(parentDiv)
+ obj.parentDivs.push(parentDiv);
//@ts-ignore
- child.id = "highlighted"
+ child.id = "highlighted";
//@ts-ignore
- obj.spans.push(child)
+ obj.spans.push(child);
child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler
}
- })
+ });
}
}
}
@@ -194,21 +196,21 @@ export class PDFBox extends React.Component<FieldViewProps> {
}
highlightNodes = (nodes: NodeListOf<ChildNode>) => {
- let temp = { parentDivs: [], spans: [] }
+ let temp = { parentDivs: [], spans: [] };
nodes.forEach((div) => {
div.childNodes.forEach((child) => {
- if (child.nodeName == 'SPAN') {
+ if (child.nodeName === 'SPAN') {
//@ts-ignore
- temp.parentDivs.push(div)
+ temp.parentDivs.push(div);
//@ts-ignore
- child.id = "highlighted"
+ child.id = "highlighted";
//@ts-ignore
- temp.spans.push(child)
+ temp.spans.push(child);
child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler
}
- })
+ });
- })
+ });
return temp;
}
@@ -221,29 +223,29 @@ export class PDFBox extends React.Component<FieldViewProps> {
let index: any;
this._pageInfo.divs.forEach((obj: any) => {
obj.spans.forEach((element: any) => {
- if (element == span) {
+ if (element === span) {
if (!index) {
index = this._pageInfo.divs.indexOf(obj);
}
}
- })
- })
+ });
+ });
if (this._pageInfo.anno.length >= index + 1) {
- if (this._currAnno.length == 0) {
+ if (this._currAnno.length === 0) {
this._currAnno.push(this._pageInfo.anno[index]);
}
} else {
- if (this._currAnno.length == 0) { //if there are no current annotation
+ if (this._currAnno.length === 0) { //if there are no current annotation
let div = span.offsetParent;
//@ts-ignore
- let divX = div.style.left
+ let divX = div.style.left;
//@ts-ignore
- let divY = div.style.top
+ let divY = div.style.top;
//slicing "px" from the end
divX = divX.slice(0, divX.length - 2); //gets X of the DIV element (parent of Span)
divY = divY.slice(0, divY.length - 2); //gets Y of the DIV element (parent of Span)
- let annotation = <Annotation key={Utils.GenerateGuid()} Span={span} X={divX} Y={divY - 300} Highlights={this._pageInfo.divs} Annotations={this._pageInfo.anno} CurrAnno={this._currAnno} />
+ let annotation = <Annotation key={Utils.GenerateGuid()} Span={span} X={divX} Y={divY - 300} Highlights={this._pageInfo.divs} Annotations={this._pageInfo.anno} CurrAnno={this._currAnno} />;
this._pageInfo.anno.push(annotation);
this._currAnno.push(annotation);
}
@@ -261,7 +263,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
this.makeEditableAndHighlight(color);
}
} catch (ex) {
- this.makeEditableAndHighlight(color)
+ this.makeEditableAndHighlight(color);
}
}
}
@@ -303,7 +305,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
}
if (this._mainDiv.current) {
- let sticky = <Sticky key={Utils.GenerateGuid()} Height={height} Width={width} X={this.initX} Y={this.initY} />
+ let sticky = <Sticky key={Utils.GenerateGuid()} Height={height} Width={width} X={this.initX} Y={this.initY} />;
this._pageInfo.area.push(sticky);
}
this._toolOn = false;
@@ -315,7 +317,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
* starts drawing the line when user presses down.
*/
onDraw = () => {
- if (this._currTool != null) {
+ if (this._currTool !== null) {
this._currTool.style.backgroundColor = "grey";
}
@@ -340,13 +342,13 @@ export class PDFBox extends React.Component<FieldViewProps> {
* for changing color (for ink/pen)
*/
onColorChange = (e: React.PointerEvent) => {
- if (e.currentTarget.innerHTML == "Red") {
+ if (e.currentTarget.innerHTML === "Red") {
this._currColor = "red";
- } else if (e.currentTarget.innerHTML == "Blue") {
+ } else if (e.currentTarget.innerHTML === "Blue") {
this._currColor = "blue";
- } else if (e.currentTarget.innerHTML == "Green") {
+ } else if (e.currentTarget.innerHTML === "Green") {
this._currColor = "green";
- } else if (e.currentTarget.innerHTML == "Black") {
+ } else if (e.currentTarget.innerHTML === "Black") {
this._currColor = "black";
}
@@ -358,7 +360,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
*/
onHighlight = () => {
this._drawToolOn = false;
- if (this._currTool != null) {
+ if (this._currTool !== null) {
this._currTool.style.backgroundColor = "grey";
}
if (this._highlightTool.current) {
@@ -376,42 +378,45 @@ export class PDFBox extends React.Component<FieldViewProps> {
@action
saveThumbnail = () => {
+ this._renderAsSvg = false;
setTimeout(() => {
var me = this;
- htmlToImage.toPng(this._mainDiv.current!,
- { width: me.props.doc.GetNumber(KeyStore.NativeWidth, 0), height: me.props.doc.GetNumber(KeyStore.NativeHeight, 0), quality: 0.5 })
- .then(function (dataUrl: string) {
- me.props.doc.SetData(KeyStore.Thumbnail, new URL(dataUrl), ImageField);
- me.props.doc.SetNumber(KeyStore.ThumbnailPage, me.props.doc.GetNumber(KeyStore.CurPage, -1));
- })
+ let nwidth = me.props.Document.GetNumber(KeyStore.NativeWidth, 0);
+ let nheight = me.props.Document.GetNumber(KeyStore.NativeHeight, 0);
+ htmlToImage.toPng(this._mainDiv.current!, { width: nwidth, height: nheight, quality: 1 })
+ .then(action((dataUrl: string) => {
+ me.props.Document.SetData(KeyStore.Thumbnail, new URL(dataUrl), ImageField);
+ me.props.Document.SetNumber(KeyStore.ThumbnailPage, me.props.Document.GetNumber(KeyStore.CurPage, -1));
+ me._renderAsSvg = true;
+ }))
.catch(function (error: any) {
console.error('oops, something went wrong!', error);
});
- }, 1000);
+ }, 250);
}
@action
onLoaded = (page: any) => {
if (this._mainDiv.current) {
this._mainDiv.current.childNodes.forEach((element) => {
- if (element.nodeName == "DIV") {
+ if (element.nodeName === "DIV") {
element.childNodes[0].childNodes.forEach((e) => {
if (e instanceof HTMLCanvasElement) {
this._pdfCanvas = e;
- this._pdfContext = e.getContext("2d")
+ this._pdfContext = e.getContext("2d");
}
- })
+ });
}
- })
+ });
}
// bcz: the number of pages should really be set when the document is imported.
- this.props.doc.SetNumber(KeyStore.NumPages, page._transport.numPages);
- if (this._perPageInfo.length == 0) { //Makes sure it only runs once
- this._perPageInfo = [...Array(page._transport.numPages)]
+ this.props.Document.SetNumber(KeyStore.NumPages, page._transport.numPages);
+ if (this._perPageInfo.length === 0) { //Makes sure it only runs once
+ this._perPageInfo = [...Array(page._transport.numPages)];
}
this._loaded = true;
}
@@ -421,14 +426,11 @@ export class PDFBox extends React.Component<FieldViewProps> {
// bcz: the nativeHeight should really be set when the document is imported.
// also, the native dimensions could be different for different pages of the PDF
// so this design is flawed.
- var nativeWidth = this.props.doc.GetNumber(KeyStore.NativeWidth, 0);
- if (!this.props.doc.GetNumber(KeyStore.NativeHeight, 0)) {
+ var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
+ if (!this.props.Document.GetNumber(KeyStore.NativeHeight, 0)) {
var nativeHeight = nativeWidth * r.entry.height / r.entry.width;
- this.props.doc.SetNumber(KeyStore.Height, nativeHeight / nativeWidth * this.props.doc.GetNumber(KeyStore.Width, 0));
- this.props.doc.SetNumber(KeyStore.NativeHeight, nativeHeight);
- }
- if (!this.props.doc.GetT(KeyStore.Thumbnail, ImageField)) {
- this.saveThumbnail();
+ this.props.Document.SetNumber(KeyStore.Height, nativeHeight / nativeWidth * this.props.Document.GetNumber(KeyStore.Width, 0));
+ this.props.Document.SetNumber(KeyStore.NativeHeight, nativeHeight);
}
}
@@ -436,10 +438,10 @@ export class PDFBox extends React.Component<FieldViewProps> {
get pdfContent() {
let page = this.curPage;
const renderHeight = 2400;
- let pdfUrl = this.props.doc.GetT(this.props.fieldKey, PDFField);
- let xf = this.props.doc.GetNumber(KeyStore.NativeHeight, 0) / renderHeight;
+ let pdfUrl = this.props.Document.GetT(this.props.fieldKey, PDFField);
+ let xf = this.props.Document.GetNumber(KeyStore.NativeHeight, 0) / renderHeight;
return <div className="pdfBox-contentContainer" key="container" style={{ transform: `scale(${xf}, ${xf})` }}>
- <Document file={window.origin + RouteStore.corsProxy + `/${pdfUrl}`}>
+ <Document file={window.origin + RouteStore.corsProxy + `/${pdfUrl}`} renderMode={this._renderAsSvg ? "svg" : ""}>
<Measure onResize={this.setScaling}>
{({ measureRef }) =>
<div className="pdfBox-page" ref={measureRef}>
@@ -454,8 +456,8 @@ export class PDFBox extends React.Component<FieldViewProps> {
@computed
get pdfRenderer() {
let proxy = this._loaded ? (null) : this.imageProxyRenderer;
- let pdfUrl = this.props.doc.GetT(this.props.fieldKey, PDFField);
- if ((!this._interactive && proxy) || !pdfUrl || pdfUrl == FieldWaiting) {
+ let pdfUrl = this.props.Document.GetT(this.props.fieldKey, PDFField);
+ if ((!this._interactive && proxy) || !pdfUrl || pdfUrl === FieldWaiting) {
return proxy;
}
return [
@@ -468,9 +470,9 @@ export class PDFBox extends React.Component<FieldViewProps> {
@computed
get imageProxyRenderer() {
- let thumbField = this.props.doc.Get(KeyStore.Thumbnail);
+ let thumbField = this.props.Document.Get(KeyStore.Thumbnail);
if (thumbField) {
- let path = thumbField == FieldWaiting || this.thumbnailPage != this.curPage ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
+ let path = thumbField === FieldWaiting || this.thumbnailPage !== this.curPage ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
thumbField instanceof ImageField ? thumbField.Data.href : "http://cs.brown.edu/people/bcz/prairie.jpg";
return <img src={path} width="100%" />;
}
diff --git a/src/client/views/nodes/Sticky.tsx b/src/client/views/nodes/Sticky.tsx
index d57dd5c0b..11719831b 100644
--- a/src/client/views/nodes/Sticky.tsx
+++ b/src/client/views/nodes/Sticky.tsx
@@ -1,83 +1,83 @@
-import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
-import React = require("react")
-import { observer } from "mobx-react"
-import 'react-pdf/dist/Page/AnnotationLayer.css'
+import "react-image-lightbox/style.css"; // This only needs to be imported once in your app
+import React = require("react");
+import { observer } from "mobx-react";
+import "react-pdf/dist/Page/AnnotationLayer.css";
interface IProps {
- Height: number;
- Width: number;
- X: number;
- Y: number;
+ Height: number;
+ Width: number;
+ X: number;
+ Y: number;
}
/**
- * Sticky, also known as area highlighting, is used to highlight large selection of the PDF file.
- * Improvements that could be made: maybe store line array and store that somewhere for future rerendering.
- *
- * Written By: Andrew Kim
+ * Sticky, also known as area highlighting, is used to highlight large selection of the PDF file.
+ * Improvements that could be made: maybe store line array and store that somewhere for future rerendering.
+ *
+ * Written By: Andrew Kim
*/
@observer
export class Sticky extends React.Component<IProps> {
+ private initX: number = 0;
+ private initY: number = 0;
- private initX: number = 0;
- private initY: number = 0;
+ private _ref = React.createRef<HTMLCanvasElement>();
+ private ctx: any; //context that keeps track of sticky canvas
- private _ref = React.createRef<HTMLCanvasElement>();
- private ctx: any; //context that keeps track of sticky canvas
-
- /**
- * drawing. Registers the first point that user clicks when mouse button is pressed down on canvas
- */
- drawDown = (e: React.PointerEvent) => {
- if (this._ref.current) {
- this.ctx = this._ref.current.getContext("2d");
- let mouse = e.nativeEvent;
- this.initX = mouse.offsetX;
- this.initY = mouse.offsetY;
- this.ctx.beginPath();
- this.ctx.lineTo(this.initX, this.initY);
- this.ctx.strokeStyle = "black";
- document.addEventListener("pointermove", this.drawMove);
- document.addEventListener("pointerup", this.drawUp);
- }
+ /**
+ * drawing. Registers the first point that user clicks when mouse button is pressed down on canvas
+ */
+ drawDown = (e: React.PointerEvent) => {
+ if (this._ref.current) {
+ this.ctx = this._ref.current.getContext("2d");
+ let mouse = e.nativeEvent;
+ this.initX = mouse.offsetX;
+ this.initY = mouse.offsetY;
+ this.ctx.beginPath();
+ this.ctx.lineTo(this.initX, this.initY);
+ this.ctx.strokeStyle = "black";
+ document.addEventListener("pointermove", this.drawMove);
+ document.addEventListener("pointerup", this.drawUp);
}
+ }
- //when user drags
- drawMove = (e: PointerEvent): void => {
- //x and y mouse movement
- let x = this.initX += e.movementX,
- y = this.initY += e.movementY;
- //connects the point
- this.ctx.lineTo(x, y);
- this.ctx.stroke();
-
- }
+ //when user drags
+ drawMove = (e: PointerEvent): void => {
+ //x and y mouse movement
+ let x = (this.initX += e.movementX),
+ y = (this.initY += e.movementY);
+ //connects the point
+ this.ctx.lineTo(x, y);
+ this.ctx.stroke();
+ }
- /**
- * when user lifts the mouse, the drawing ends
- */
- drawUp = (e: PointerEvent) => {
- this.ctx.closePath();
- console.log(this.ctx);
- document.removeEventListener("pointermove", this.drawMove);
- }
+ /**
+ * when user lifts the mouse, the drawing ends
+ */
+ drawUp = (e: PointerEvent) => {
+ this.ctx.closePath();
+ console.log(this.ctx);
+ document.removeEventListener("pointermove", this.drawMove);
+ }
- render() {
- return (
- <div onPointerDown={this.drawDown}>
- <canvas ref={this._ref} height={this.props.Height} width={this.props.Width}
- style={{
- position: "absolute",
- top: "20px",
- left: "0px",
- zIndex: 1,
- background: "yellow",
- transform: `translate(${this.props.X}px, ${this.props.Y}px)`,
- opacity: 0.4
- }}
- />
-
- </div>
- );
- }
-} \ No newline at end of file
+ render() {
+ return (
+ <div onPointerDown={this.drawDown}>
+ <canvas
+ ref={this._ref}
+ height={this.props.Height}
+ width={this.props.Width}
+ style={{
+ position: "absolute",
+ top: "20px",
+ left: "0px",
+ zIndex: 1,
+ background: "yellow",
+ transform: `translate(${this.props.X}px, ${this.props.Y}px)`,
+ opacity: 0.4
+ }}
+ />
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 7c0db83a8..9d7c2bc56 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,4 +1,4 @@
-import React = require("react")
+import React = require("react");
import { observer } from "mobx-react";
import { FieldWaiting, Opt } from '../../../fields/Field';
import { VideoField } from '../../../fields/VideoField';
@@ -13,14 +13,14 @@ import { number } from "prop-types";
export class VideoBox extends React.Component<FieldViewProps> {
private _reactionDisposer: Opt<IReactionDisposer>;
- private _videoRef = React.createRef<HTMLVideoElement>()
- public static LayoutString() { return FieldView.LayoutString(VideoBox) }
+ private _videoRef = React.createRef<HTMLVideoElement>();
+ public static LayoutString() { return FieldView.LayoutString(VideoBox); }
constructor(props: FieldViewProps) {
super(props);
}
- @computed private get curPage() { return this.props.doc.GetNumber(KeyStore.CurPage, -1); }
+ @computed private get curPage() { return this.props.Document.GetNumber(KeyStore.CurPage, -1); }
_loaded: boolean = false;
@@ -31,12 +31,12 @@ export class VideoBox extends React.Component<FieldViewProps> {
// bcz: the nativeHeight should really be set when the document is imported.
// also, the native dimensions could be different for different pages of the PDF
// so this design is flawed.
- var nativeWidth = this.props.doc.GetNumber(KeyStore.NativeWidth, 0);
- var nativeHeight = this.props.doc.GetNumber(KeyStore.NativeHeight, 0);
+ var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
+ var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0);
var newNativeHeight = nativeWidth * r.entry.height / r.entry.width;
- if (!nativeHeight && newNativeHeight != nativeHeight && !isNaN(newNativeHeight)) {
- this.props.doc.SetNumber(KeyStore.Height, newNativeHeight / nativeWidth * this.props.doc.GetNumber(KeyStore.Width, 0));
- this.props.doc.SetNumber(KeyStore.NativeHeight, newNativeHeight);
+ if (!nativeHeight && newNativeHeight !== nativeHeight && !isNaN(newNativeHeight)) {
+ this.props.Document.SetNumber(KeyStore.Height, newNativeHeight / nativeWidth * this.props.Document.GetNumber(KeyStore.Width, 0));
+ this.props.Document.SetNumber(KeyStore.NativeHeight, newNativeHeight);
}
} else {
this._loaded = true;
@@ -50,15 +50,15 @@ export class VideoBox extends React.Component<FieldViewProps> {
@action
setVideoRef = (vref: HTMLVideoElement | null) => {
if (this.curPage >= 0 && vref) {
- vref!.currentTime = this.curPage;
- (vref! as any).AHackBecauseSomethingResetsTheVideoToZero = this.curPage;
+ vref.currentTime = this.curPage;
+ (vref as any).AHackBecauseSomethingResetsTheVideoToZero = this.curPage;
}
}
render() {
- let field = this.props.doc.GetT(this.props.fieldKey, VideoField);
+ let field = this.props.Document.GetT(this.props.fieldKey, VideoField);
if (!field || field === FieldWaiting) {
- return <div>Loading</div>
+ return <div>Loading</div>;
}
let path = field.Data.href;
trace();
@@ -66,13 +66,13 @@ 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>
</div>
}
</Measure>
- )
+ );
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index a535b2638..c73bc0c47 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -2,6 +2,8 @@
.webBox-cont {
padding: 0vw;
position: absolute;
+ top: 0;
+ left:0;
width: 100%;
height: 100%;
overflow: scroll;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 2ca8d49ce..90ce72c41 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,9 +1,9 @@
import "./WebBox.scss";
-import React = require("react")
+import React = require("react");
import { WebField } from '../../../fields/WebField';
import { FieldViewProps, FieldView } from './FieldView';
import { FieldWaiting } from '../../../fields/Field';
-import { observer } from "mobx-react"
+import { observer } from "mobx-react";
import { computed } from 'mobx';
import { KeyStore } from '../../../fields/KeyStore';
@@ -16,11 +16,11 @@ export class WebBox extends React.Component<FieldViewProps> {
super(props);
}
- @computed get html(): string { return this.props.doc.GetHtml(KeyStore.Data, ""); }
+ @computed get html(): string { return this.props.Document.GetHtml(KeyStore.Data, ""); }
render() {
- let field = this.props.doc.Get(this.props.fieldKey);
- let path = field == FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
+ let field = this.props.Document.Get(this.props.fieldKey);
+ let path = field === FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
field instanceof WebField ? field.Data.href : "https://crossorigin.me/" + "https://cs.brown.edu";
let content = this.html ?
@@ -33,6 +33,6 @@ export class WebBox extends React.Component<FieldViewProps> {
return (
<div className="webBox-cont" >
{content}
- </div>)
+ </div>);
}
} \ No newline at end of file
diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
index c8de33f41..11f2b0c4e 100644
--- a/src/debug/Test.tsx
+++ b/src/debug/Test.tsx
@@ -1,10 +1,10 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
-import JsxParser from 'react-jsx-parser'
+import JsxParser from 'react-jsx-parser';
class Hello extends React.Component<{ firstName: string, lastName: string }> {
render() {
- return <div>Hello {this.props.firstName} {this.props.lastName}</div>
+ return <div>Hello {this.props.firstName} {this.props.lastName}</div>;
}
}
@@ -16,8 +16,8 @@ class Test extends React.Component {
firstName: "First",
lastName: "Last"
}
- }
- return <JsxParser jsx={jsx} bindings={bindings} components={{ Hello }}></JsxParser>
+ };
+ return <JsxParser jsx={jsx} bindings={bindings} components={{ Hello }}></JsxParser>;
}
}
diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx
index 7fdd77bf3..857da1ebb 100644
--- a/src/debug/Viewer.tsx
+++ b/src/debug/Viewer.tsx
@@ -40,22 +40,22 @@ class ListViewer extends React.Component<{ field: ListField<Field> }>{
<div>
{this.props.field.Data.map(field => <DebugViewer fieldId={field.Id} key={field.Id} />)}
</div>
- )
+ );
} else {
- content = <>[...] ({this.props.field.Id})</>
+ content = <>[...] ({this.props.field.Id})</>;
}
return (
<div>
<button onClick={action(() => this.expanded = !this.expanded)}>Toggle</button>
{content}
</div >
- )
+ );
}
}
@observer
class DocumentViewer extends React.Component<{ field: Document }> {
- private keyMap: ObservableMap<string, Key> = new ObservableMap
+ private keyMap: ObservableMap<string, Key> = new ObservableMap;
private disposer?: Lambda;
@@ -67,12 +67,12 @@ class DocumentViewer extends React.Component<{ field: Document }> {
if (field && field instanceof Key) {
this.keyMap.set(id, field);
}
- })
+ });
}
});
- }
- this.disposer = this.props.field._proxies.observe(f)
- f()
+ };
+ this.disposer = this.props.field._proxies.observe(f);
+ f();
}
componentWillUnmount() {
@@ -87,10 +87,10 @@ class DocumentViewer extends React.Component<{ field: Document }> {
return (
<div key={kv[0]}>
<b>({key ? key.Name : kv[0]}): </b>
- <DebugViewer fieldId={kv[1]!}></DebugViewer>
+ <DebugViewer fieldId={kv[1]}></DebugViewer>
</div>
- )
- })
+ );
+ });
return (
<div>
Document ({this.props.field.Id})
@@ -98,7 +98,7 @@ class DocumentViewer extends React.Component<{ field: Document }> {
{fields}
</div>
</div>
- )
+ );
}
}
@@ -111,15 +111,15 @@ class DebugViewer extends React.Component<{ fieldId: string }> {
private error?: string;
constructor(props: { fieldId: string }) {
- super(props)
- this.update()
+ super(props);
+ this.update();
}
update() {
Server.GetField(this.props.fieldId, action((field: Opt<Field>) => {
this.field = field;
if (!field) {
- this.error = `Field with id ${this.props.fieldId} not found`
+ this.error = `Field with id ${this.props.fieldId} not found`;
}
}));
@@ -130,20 +130,20 @@ class DebugViewer extends React.Component<{ fieldId: string }> {
if (this.field) {
// content = this.field.ToJson();
if (this.field instanceof ListField) {
- content = (<ListViewer field={this.field} />)
+ content = (<ListViewer field={this.field} />);
} else if (this.field instanceof Document) {
- content = (<DocumentViewer field={this.field} />)
+ content = (<DocumentViewer field={this.field} />);
} else if (this.field instanceof BasicField) {
- content = (<FieldViewer field={this.field} />)
+ content = (<FieldViewer field={this.field} />);
} else if (this.field instanceof Key) {
- content = (<KeyViewer field={this.field} />)
+ content = (<KeyViewer field={this.field} />);
} else {
- content = (<span>Unrecognized field type</span>)
+ content = (<span>Unrecognized field type</span>);
}
} else if (this.error) {
- content = <span>Field <b>{this.props.fieldId}</b> not found <button onClick={() => this.update()}>Refresh</button></span>
+ content = <span>Field <b>{this.props.fieldId}</b> not found <button onClick={() => this.update()}>Refresh</button></span>;
} else {
- content = <span>Field loading: {this.props.fieldId}</span>
+ content = <span>Field loading: {this.props.fieldId}</span>;
}
return content;
}
@@ -165,8 +165,8 @@ class Viewer extends React.Component {
@action
onKeyPress = (e: React.KeyboardEvent<HTMLDivElement>) => {
if (e.key === "Enter") {
- this.ids.push(this.idToAdd)
- this.idToAdd = ""
+ this.ids.push(this.idToAdd);
+ this.idToAdd = "";
}
}
@@ -177,12 +177,10 @@ class Viewer extends React.Component {
onChange={this.inputOnChange}
onKeyDown={this.onKeyPress} />
<div>
- {this.ids.map(id => {
- return <DebugViewer fieldId={id} key={id}></DebugViewer>
- })}
+ {this.ids.map(id => <DebugViewer fieldId={id} key={id}></DebugViewer>)}
</div>
</>
- )
+ );
}
}
diff --git a/src/fields/AudioField.ts b/src/fields/AudioField.ts
index 8864471ae..87e47a715 100644
--- a/src/fields/AudioField.ts
+++ b/src/fields/AudioField.ts
@@ -4,7 +4,7 @@ import { Types } from "../server/Message";
export class AudioField extends BasicField<URL> {
constructor(data: URL | undefined = undefined, id?: FieldId, save: boolean = true) {
- super(data == undefined ? new URL("http://techslides.com/demos/samples/sample.mp3") : data, save, id);
+ super(data === undefined ? new URL("http://techslides.com/demos/samples/sample.mp3") : data, save, id);
}
toString(): string {
@@ -20,12 +20,12 @@ export class AudioField extends BasicField<URL> {
return new AudioField(this.Data);
}
- ToJson(): { type: Types, data: string, _id: string } {
+ ToJson() {
return {
type: Types.Audio,
data: this.Data.href,
- _id: this.Id
- }
+ id: this.Id
+ };
}
} \ No newline at end of file
diff --git a/src/fields/BasicField.ts b/src/fields/BasicField.ts
index a92c4a236..17b1fc4e8 100644
--- a/src/fields/BasicField.ts
+++ b/src/fields/BasicField.ts
@@ -1,4 +1,4 @@
-import { Field, FieldId } from "./Field"
+import { Field, FieldId } from "./Field";
import { observable, computed, action } from "mobx";
import { Server } from "../client/Server";
import { UndoManager } from "../client/util/UndoManager";
@@ -9,7 +9,7 @@ export abstract class BasicField<T> extends Field {
this.data = data;
if (save) {
- Server.UpdateField(this)
+ Server.UpdateField(this);
}
}
@@ -36,7 +36,7 @@ export abstract class BasicField<T> extends Field {
UndoManager.AddEvent({
undo: () => this.Data = oldValue,
redo: () => this.Data = value
- })
+ });
Server.UpdateField(this);
}
@@ -46,7 +46,7 @@ export abstract class BasicField<T> extends Field {
@action
TrySetValue(value: any): boolean {
- if (typeof value == typeof this.data) {
+ if (typeof value === typeof this.data) {
this.Data = value;
return true;
}
diff --git a/src/fields/BooleanField.ts b/src/fields/BooleanField.ts
new file mode 100644
index 000000000..d49bfe82b
--- /dev/null
+++ b/src/fields/BooleanField.ts
@@ -0,0 +1,25 @@
+import { BasicField } from "./BasicField";
+import { FieldId } from "./Field";
+import { Types } from "../server/Message";
+
+export class BooleanField extends BasicField<boolean> {
+ constructor(data: boolean = false as boolean, id?: FieldId, save: boolean = true as boolean) {
+ super(data, save, id);
+ }
+
+ ToScriptString(): string {
+ return `new BooleanField("${this.Data}")`;
+ }
+
+ Copy() {
+ return new BooleanField(this.Data);
+ }
+
+ ToJson() {
+ return {
+ type: Types.Boolean,
+ data: this.Data,
+ id: this.Id
+ };
+ }
+}
diff --git a/src/fields/Document.ts b/src/fields/Document.ts
index 85ff6ddcb..7cf784f0e 100644
--- a/src/fields/Document.ts
+++ b/src/fields/Document.ts
@@ -1,6 +1,6 @@
-import { Key } from "./Key"
+import { Key } from "./Key";
import { KeyStore } from "./KeyStore";
-import { Field, Cast, FieldWaiting, FieldValue, FieldId, Opt } from "./Field"
+import { Field, Cast, FieldWaiting, FieldValue, FieldId, Opt } from "./Field";
import { NumberField } from "./NumberField";
import { ObservableMap, computed, action, runInAction } from "mobx";
import { TextField } from "./TextField";
@@ -9,19 +9,29 @@ import { Server } from "../client/Server";
import { Types } from "../server/Message";
import { UndoManager } from "../client/util/UndoManager";
import { HtmlField } from "./HtmlField";
+import { BooleanField } from "./BooleanField";
+import { allLimit } from "async";
+import { prototype } from "nodemailer/lib/smtp-pool";
+import { HistogramField } from "../client/northstar/dash-fields/HistogramField";
export class Document extends Field {
//TODO tfs: We should probably store FieldWaiting in fields when we request it from the server so that we don't set up multiple server gets for the same document and field
- public fields: ObservableMap<string, { key: Key, field: Field }> = new ObservableMap();
+ public fields: ObservableMap<string, { key: Key; field: Field }> = new ObservableMap();
public _proxies: ObservableMap<string, FieldId> = new ObservableMap();
constructor(id?: string, save: boolean = true) {
- super(id)
+ super(id);
if (save) {
- Server.UpdateField(this)
+ Server.UpdateField(this);
}
}
+ static FromJson(data: any, id: string, save: boolean): Document {
+ let doc = new Document(id, save);
+ let fields = data as [string, string][];
+ fields.forEach(pair => doc._proxies.set(pair[0], pair[1]));
+ return doc;
+ }
UpdateFromServer(data: [string, string][]) {
for (const key in data) {
@@ -30,22 +40,24 @@ export class Document extends Field {
}
}
- public Width = () => { return this.GetNumber(KeyStore.Width, 0) }
- public Height = () => { return this.GetNumber(KeyStore.Height, this.GetNumber(KeyStore.NativeWidth, 0) ? this.GetNumber(KeyStore.NativeHeight, 0) / this.GetNumber(KeyStore.NativeWidth, 0) * this.GetNumber(KeyStore.Width, 0) : 0) }
- public Scale = () => { return this.GetNumber(KeyStore.Scale, 1) }
+ public Width = () => this.GetNumber(KeyStore.Width, 0);
+ public Height = () => this.GetNumber(KeyStore.Height, this.GetNumber(KeyStore.NativeWidth, 0) ? (this.GetNumber(KeyStore.NativeHeight, 0) / this.GetNumber(KeyStore.NativeWidth, 0)) * this.GetNumber(KeyStore.Width, 0) : 0);
+ public Scale = () => this.GetNumber(KeyStore.Scale, 1);
@computed
public get Title(): string {
let title = this.Get(KeyStore.Title, true);
- if (title)
- if (title != FieldWaiting && title instanceof TextField)
+ 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 != FieldWaiting)
- return parTitle.Data + ".alias";
+ if (parTitle || parTitle === FieldWaiting) {
+ if (parTitle !== FieldWaiting) return parTitle.Data + ".alias";
else return "-waiting-.alias";
+ }
return "-untitled-";
}
@@ -57,7 +69,7 @@ export class Document extends Field {
/**
* Get the field in the document associated with the given key. If the
* associated field has not yet been filled in from the server, a request
- * to the server will automatically be sent, the value will be filled in
+ * to the server will automatically be sent, the value will be filled in
* when the request is completed, and {@link Field.ts#FieldWaiting} will be returned.
* @param key - The key of the value to get
* @param ignoreProto - If true, ignore any prototype this document
@@ -65,7 +77,7 @@ export class Document extends Field {
* If false (default), search up the prototype chain, starting at this document,
* for a document that has a field associated with the given key, and return the first
* one found.
- *
+ *
* @returns If the document does not have a field associated with the given key, returns `undefined`.
* If the document does have an associated field, but the field has not been fetched from the server, returns {@link Field.ts#FieldWaiting}.
* If the document does have an associated field, and the field has not been fetched from the server, returns the associated field.
@@ -78,10 +90,10 @@ export class Document extends Field {
} else if (this._proxies.has(key.Id)) {
Server.GetDocumentField(this, key);
/*
- The field might have been instantly filled from the cache
- Maybe we want to just switch back to returning the value
- from Server.GetDocumentField if it's in the cache
- */
+ The field might have been instantly filled from the cache
+ Maybe we want to just switch back to returning the value
+ from Server.GetDocumentField if it's in the cache
+ */
if (this.fields.has(key.Id)) {
field = this.fields.get(key.Id)!.field;
} else {
@@ -90,17 +102,17 @@ export class Document extends Field {
}
} else {
let doc: FieldValue<Document> = this;
- while (doc && doc != FieldWaiting && field != FieldWaiting) {
+ while (doc && field !== FieldWaiting) {
let curField = doc.fields.get(key.Id);
let curProxy = doc._proxies.get(key.Id);
if (!curField || (curProxy && curField.field.Id !== curProxy)) {
if (curProxy) {
Server.GetDocumentField(doc, key);
/*
- The field might have been instantly filled from the cache
- Maybe we want to just switch back to returning the value
- from Server.GetDocumentField if it's in the cache
- */
+ The field might have been instantly filled from the cache
+ Maybe we want to just switch back to returning the value
+ from Server.GetDocumentField if it's in the cache
+ */
if (this.fields.has(key.Id)) {
field = this.fields.get(key.Id)!.field;
} else {
@@ -108,7 +120,10 @@ export class Document extends Field {
}
break;
}
- if ((doc.fields.has(KeyStore.Prototype.Id) || doc._proxies.has(KeyStore.Prototype.Id))) {
+ if (
+ doc.fields.has(KeyStore.Prototype.Id) ||
+ doc._proxies.has(KeyStore.Prototype.Id)
+ ) {
doc = doc.GetPrototype();
} else {
break;
@@ -118,8 +133,7 @@ export class Document extends Field {
break;
}
}
- if (doc == FieldWaiting)
- field = FieldWaiting;
+ if (doc === FieldWaiting) field = FieldWaiting;
}
return field;
@@ -148,24 +162,32 @@ export class Document extends Field {
} else {
callback(undefined);
}
- })
+ });
} else {
callback(undefined);
}
}
GetTAsync<T extends Field>(key: Key, ctor: { new(): T }): Promise<Opt<T>>;
- GetTAsync<T extends Field>(key: Key, ctor: { new(): T }, callback: (field: Opt<T>) => void): void;
- GetTAsync<T extends Field>(key: Key, ctor: { new(): T }, callback?: (field: Opt<T>) => void): Promise<Opt<T>> | void {
+ GetTAsync<T extends Field>(
+ key: Key,
+ ctor: { new(): T },
+ callback: (field: Opt<T>) => void
+ ): void;
+ GetTAsync<T extends Field>(
+ key: Key,
+ ctor: { new(): T },
+ callback?: (field: Opt<T>) => void
+ ): Promise<Opt<T>> | void {
let fn = (cb: (field: Opt<T>) => void) => {
- return this.GetAsync(key, (field) => {
+ return this.GetAsync(key, field => {
cb(Cast(field, ctor));
});
- }
+ };
if (callback) {
fn(callback);
} else {
- return new Promise(res => fn(res));
+ return new Promise(fn);
}
}
@@ -175,10 +197,14 @@ export class Document extends Field {
* or the field associated with the given key is not of the given type.
* @param ctor - Constructor of the field type to get. E.g., TextField, ImageField, etc.
*/
- GetOrCreateAsync<T extends Field>(key: Key, ctor: { new(): T }, callback: (field: T) => void): void {
+ GetOrCreateAsync<T extends Field>(
+ key: Key,
+ ctor: { new(): T },
+ callback: (field: T) => void
+ ): void {
//This currently doesn't deal with prototypes
if (this._proxies.has(key.Id)) {
- Server.GetDocumentField(this, key, (field) => {
+ Server.GetDocumentField(this, key, field => {
if (field && field instanceof ctor) {
callback(field);
} else {
@@ -201,17 +227,25 @@ export class Document extends Field {
* @returns Same as {@link Document#Get}, except will return `undefined`
* if there is an associated field but it is of the wrong type.
*/
- GetT<T extends Field = Field>(key: Key, ctor: { new(...args: any[]): T }, ignoreProto: boolean = false): FieldValue<T> {
+ GetT<T extends Field = Field>(
+ key: Key,
+ ctor: { new(...args: any[]): T },
+ ignoreProto: boolean = false
+ ): FieldValue<T> {
var getfield = this.Get(key, ignoreProto);
- if (getfield != FieldWaiting) {
+ if (getfield !== FieldWaiting) {
return Cast(getfield, ctor);
}
return FieldWaiting;
}
- GetOrCreate<T extends Field>(key: Key, ctor: { new(): T }, ignoreProto: boolean = false): T {
+ GetOrCreate<T extends Field>(
+ key: Key,
+ ctor: { new(): T },
+ ignoreProto: boolean = false
+ ): T {
const field = this.GetT(key, ctor, ignoreProto);
- if (field && field != FieldWaiting) {
+ if (field && field !== FieldWaiting) {
return field;
}
const newField = new ctor();
@@ -219,9 +253,13 @@ export class Document extends Field {
return newField;
}
- GetData<T, U extends Field & { Data: T }>(key: Key, ctor: { new(): U }, defaultVal: T): T {
+ GetData<T, U extends Field & { Data: T }>(
+ key: Key,
+ ctor: { new(): U },
+ defaultVal: T
+ ): T {
let val = this.Get(key);
- let vval = (val && val instanceof ctor) ? val.Data : defaultVal;
+ let vval = val && val instanceof ctor ? val.Data : defaultVal;
return vval;
}
@@ -229,6 +267,10 @@ export class Document extends Field {
return this.GetData(key, HtmlField, defaultVal);
}
+ GetBoolean(key: Key, defaultVal: boolean): boolean {
+ return this.GetData(key, BooleanField, defaultVal);
+ }
+
GetNumber(key: Key, defaultVal: number): number {
return this.GetData(key, NumberField, defaultVal);
}
@@ -238,7 +280,7 @@ export class Document extends Field {
}
GetList<T extends Field>(key: Key, defaultVal: T[]): T[] {
- return this.GetData<T[], ListField<T>>(key, ListField, defaultVal)
+ return this.GetData<T[], ListField<T>>(key, ListField, defaultVal);
}
@action
@@ -246,16 +288,15 @@ export class Document extends Field {
let old = this.fields.get(key.Id);
let oldField = old ? old.field : undefined;
if (setOnPrototype) {
- this.SetOnPrototype(key, field)
- }
- else {
+ this.SetOnPrototype(key, field);
+ } else {
if (field) {
this.fields.set(key.Id, { key, field });
- this._proxies.set(key.Id, field.Id)
+ this._proxies.set(key.Id, field.Id);
// Server.AddDocumentField(this, key, field);
} else {
this.fields.delete(key.Id);
- this._proxies.delete(key.Id)
+ this._proxies.delete(key.Id);
// Server.DeleteDocumentField(this, key);
}
Server.UpdateField(this);
@@ -264,22 +305,22 @@ export class Document extends Field {
UndoManager.AddEvent({
undo: () => this.Set(key, oldField, setOnPrototype),
redo: () => this.Set(key, field, setOnPrototype)
- })
+ });
}
}
@action
SetOnPrototype(key: Key, field: Field | undefined): void {
this.GetTAsync(KeyStore.Prototype, Document, (f: Opt<Document>) => {
- f && f.Set(key, field)
- })
+ f && f.Set(key, field);
+ });
}
@action
SetDataOnPrototype<T, U extends Field & { Data: T }>(key: Key, value: T, ctor: { new(): U }, replaceWrongType = true) {
this.GetTAsync(KeyStore.Prototype, Document, (f: Opt<Document>) => {
- f && f.SetData(key, value, ctor)
- })
+ f && f.SetData(key, value, ctor, replaceWrongType);
+ });
}
@action
@@ -298,7 +339,10 @@ export class Document extends Field {
SetText(key: Key, value: string, replaceWrongType = true) {
this.SetData(key, value, TextField, replaceWrongType);
}
-
+ @action
+ SetBoolean(key: Key, value: boolean, replaceWrongType = true) {
+ this.SetData(key, value, BooleanField, replaceWrongType);
+ }
@action
SetNumber(key: Key, value: number, replaceWrongType = true) {
this.SetData(key, value, NumberField, replaceWrongType);
@@ -311,7 +355,7 @@ export class Document extends Field {
GetAllPrototypes(): Document[] {
let protos: Document[] = [];
let doc: FieldValue<Document> = this;
- while (doc && doc != FieldWaiting) {
+ while (doc && doc !== FieldWaiting) {
protos.push(doc);
doc = doc.GetPrototype();
}
@@ -319,12 +363,12 @@ export class Document extends Field {
}
CreateAlias(id?: string): Document {
- let alias = new Document(id)
+ let alias = new Document(id);
this.GetTAsync(KeyStore.Prototype, Document, (f: Opt<Document>) => {
- f && alias.Set(KeyStore.Prototype, f)
- })
+ f && alias.Set(KeyStore.Prototype, f);
+ });
- return alias
+ return alias;
}
MakeDelegate(id?: string): Document {
@@ -348,22 +392,39 @@ export class Document extends Field {
return title;
//throw new Error("Method not implemented.");
}
- Copy(): Field {
- throw new Error("Method not implemented.");
- }
-
- ToJson(): { type: Types, data: [string, string][], _id: string } {
- let fields: [string, string][] = []
- this._proxies.forEach((field, key) => {
- if (field) {
- fields.push([key, field as string])
+ Copy(copyProto?: boolean, id?: string): Field {
+ let copy = new Document();
+ this._proxies.forEach((fieldid, keyid) => { // copy each prototype field
+ let key = KeyStore.KeyLookup(keyid);
+ if (key) {
+ this.GetAsync(key, (field: Opt<Field>) => {
+ if (key === KeyStore.Prototype && copyProto) { // handle prototype field specially
+ if (field instanceof Document) {
+ copy.Set(key, field.Copy(false)); // only copying one level of prototypes for now...
+ }
+ }
+ else
+ if (field instanceof Document) { // ... TODO bcz: should we copy documents or reference them
+ copy.Set(key!, field);
+ }
+ else if (field) {
+ copy.Set(key!, field.Copy());
+ }
+ });
}
});
+ return copy;
+ }
+
+ ToJson() {
+ let fields: [string, string][] = [];
+ this._proxies.forEach((field, key) =>
+ field && fields.push([key, field]));
return {
type: Types.Document,
data: fields,
- _id: this.Id
- }
+ id: this.Id
+ };
}
-} \ No newline at end of file
+}
diff --git a/src/fields/DocumentReference.ts b/src/fields/DocumentReference.ts
index 9d3c209b4..303754177 100644
--- a/src/fields/DocumentReference.ts
+++ b/src/fields/DocumentReference.ts
@@ -47,11 +47,11 @@ export class DocumentReference extends Field {
return "";
}
- ToJson(): { type: Types, data: FieldId, _id: string } {
+ ToJson() {
return {
type: Types.DocumentReference,
data: this.document.Id,
- _id: this.Id
- }
+ id: this.Id
+ };
}
} \ No newline at end of file
diff --git a/src/fields/Field.ts b/src/fields/Field.ts
index d48509a47..3b3e95c2b 100644
--- a/src/fields/Field.ts
+++ b/src/fields/Field.ts
@@ -1,6 +1,6 @@
import { Utils } from "../Utils";
-import { Types } from "../server/Message";
+import { Types, Transferable } from "../server/Message";
import { computed } from "mobx";
export function Cast<T extends Field>(field: FieldValue<Field>, ctor: { new(): T }): Opt<T> {
@@ -12,8 +12,8 @@ export function Cast<T extends Field>(field: FieldValue<Field>, ctor: { new(): T
return undefined;
}
-export const FieldWaiting: FIELD_WAITING = "<Waiting>";
-export type FIELD_WAITING = "<Waiting>";
+export const FieldWaiting: FIELD_WAITING = null;
+export type FIELD_WAITING = null;
export type FieldId = string;
export type Opt<T> = T | undefined;
export type FieldValue<T> = Opt<T> | FIELD_WAITING;
@@ -65,5 +65,5 @@ export abstract class Field {
abstract Copy(): Field;
- abstract ToJson(): { _id: string, type: Types, data: any }
+ abstract ToJson(): Transferable;
} \ No newline at end of file
diff --git a/src/fields/HtmlField.ts b/src/fields/HtmlField.ts
index 7cbdf7e58..a1d880070 100644
--- a/src/fields/HtmlField.ts
+++ b/src/fields/HtmlField.ts
@@ -15,11 +15,11 @@ export class HtmlField extends BasicField<string> {
return new HtmlField(this.Data);
}
- ToJson(): { _id: string; type: Types; data: string; } {
+ ToJson() {
return {
type: Types.Html,
data: this.Data,
- _id: this.Id,
- }
+ id: this.Id,
+ };
}
} \ No newline at end of file
diff --git a/src/fields/ImageField.ts b/src/fields/ImageField.ts
index a9ece7d7b..bce20f242 100644
--- a/src/fields/ImageField.ts
+++ b/src/fields/ImageField.ts
@@ -4,7 +4,7 @@ import { Types } from "../server/Message";
export class ImageField extends BasicField<URL> {
constructor(data: URL | undefined = undefined, id?: FieldId, save: boolean = true) {
- super(data == undefined ? new URL("http://cs.brown.edu/~bcz/bob_fettucine.jpg") : data, save, id);
+ super(data === undefined ? new URL("http://cs.brown.edu/~bcz/bob_fettucine.jpg") : data, save, id);
}
toString(): string {
@@ -19,11 +19,11 @@ export class ImageField extends BasicField<URL> {
return new ImageField(this.Data);
}
- ToJson(): { type: Types, data: string, _id: string } {
+ ToJson() {
return {
type: Types.Image,
data: this.Data.href,
- _id: this.Id
- }
+ id: this.Id
+ };
}
} \ No newline at end of file
diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts
index 2a4ed18e7..2eacd7d0c 100644
--- a/src/fields/InkField.ts
+++ b/src/fields/InkField.ts
@@ -31,12 +31,12 @@ export class InkField extends BasicField<StrokeMap> {
return new InkField(this.Data);
}
- ToJson(): { _id: string; type: Types; data: any; } {
+ ToJson() {
return {
type: Types.Ink,
data: this.Data,
- _id: this.Id,
- }
+ id: this.Id,
+ };
}
UpdateFromServer(data: any) {
diff --git a/src/fields/Key.ts b/src/fields/Key.ts
index 00d78d516..57e2dadf0 100644
--- a/src/fields/Key.ts
+++ b/src/fields/Key.ts
@@ -1,4 +1,4 @@
-import { Field, FieldId } from "./Field"
+import { Field, FieldId } from "./Field";
import { Utils } from "../Utils";
import { observable } from "mobx";
import { Types } from "../server/Message";
@@ -16,7 +16,7 @@ export class Key extends Field {
this.name = name;
if (save) {
- Server.UpdateField(this)
+ Server.UpdateField(this);
}
}
@@ -40,11 +40,11 @@ export class Key extends Field {
return name;
}
- ToJson(): { type: Types, data: string, _id: string } {
+ ToJson() {
return {
type: Types.Key,
data: this.name,
- _id: this.Id
- }
+ id: this.Id
+ };
}
} \ No newline at end of file
diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts
index f8c03b085..1fb65965b 100644
--- a/src/fields/KeyStore.ts
+++ b/src/fields/KeyStore.ts
@@ -15,6 +15,7 @@ export namespace KeyStore {
export const Width = new Key("Width");
export const Height = new Key("Height");
export const ZIndex = new Key("ZIndex");
+ export const Zoom = new Key("Zoom");
export const Data = new Key("Data");
export const Annotations = new Key("Annotations");
export const ViewType = new Key("ViewType");
@@ -45,6 +46,22 @@ export namespace KeyStore {
export const Cursors = new Key("Cursors");
export const OptionalRightCollection = new Key("OptionalRightCollection");
export const Archives = new Key("Archives");
- export const Updated = new Key("Updated");
export const Workspaces = new Key("Workspaces");
+ export const Minimized = new Key("Minimized");
+ export const CopyDraggedItems = new Key("CopyDraggedItems");
+
+ export const KeyList: Key[] = [Prototype, X, Y, Page, Title, Author, PanX, PanY, Scale, NativeWidth, NativeHeight,
+ Width, Height, ZIndex, Zoom, Data, Annotations, ViewType, Layout, BackgroundColor, BackgroundLayout, OverlayLayout, LayoutKeys,
+ LayoutFields, ColumnsKey, SchemaSplitPercentage, Caption, ActiveWorkspace, DocumentText, BrushingDocs, LinkedToDocs, LinkedFromDocs,
+ LinkDescription, LinkTags, Thumbnail, ThumbnailPage, CurPage, AnnotationOn, NumPages, Ink, Cursors, OptionalRightCollection,
+ Archives, Workspaces, Minimized, CopyDraggedItems
+ ];
+ export function KeyLookup(keyid: string) {
+ for (const key of KeyList) {
+ if (key.Id === keyid) {
+ return key;
+ }
+ }
+ return undefined;
+ }
}
diff --git a/src/fields/ListField.ts b/src/fields/ListField.ts
index c4008bd12..e24099126 100644
--- a/src/fields/ListField.ts
+++ b/src/fields/ListField.ts
@@ -5,12 +5,18 @@ import { Types } from "../server/Message";
import { BasicField } from "./BasicField";
import { Field, FieldId } from "./Field";
import { FieldMap } from "../client/SocketStub";
+import { ScriptField } from "./ScriptField";
export class ListField<T extends Field> extends BasicField<T[]> {
- private _proxies: string[] = []
- constructor(data: T[] = [], id?: FieldId, save: boolean = true) {
+ private _proxies: string[] = [];
+ private _scriptIds: string[] = [];
+ private scripts: ScriptField[] = [];
+
+ constructor(data: T[] = [], scripts: ScriptField[] = [], id?: FieldId, save: boolean = true) {
super(data, save, id);
+ this.scripts = scripts;
this.updateProxies();
+ this._scriptIds = this.scripts.map(script => script.Id);
if (save) {
Server.UpdateField(this);
}
@@ -22,43 +28,90 @@ export class ListField<T extends Field> extends BasicField<T[]> {
private observeDisposer: Lambda | undefined;
private observeList(): void {
if (this.observeDisposer) {
- this.observeDisposer()
+ this.observeDisposer();
}
this.observeDisposer = observe(this.Data as IObservableArray<T>, (change: IArrayChange<T> | IArraySplice<T>) => {
- this.updateProxies()
- if (change.type == "splice") {
+ const target = change.object;
+ this.updateProxies();
+ if (change.type === "splice") {
+ this.runScripts(change.removed, false);
UndoManager.AddEvent({
- undo: () => this.Data.splice(change.index, change.addedCount, ...change.removed),
- redo: () => this.Data.splice(change.index, change.removedCount, ...change.added)
- })
+ undo: () => target.splice(change.index, change.addedCount, ...change.removed),
+ redo: () => target.splice(change.index, change.removedCount, ...change.added)
+ });
+ this.runScripts(change.added, true);
} else {
+ this.runScripts([change.oldValue], false);
UndoManager.AddEvent({
- undo: () => this.Data[change.index] = change.oldValue,
- redo: () => this.Data[change.index] = change.newValue
- })
+ undo: () => target[change.index] = change.oldValue,
+ redo: () => target[change.index] = change.newValue
+ });
+ this.runScripts([change.newValue], true);
}
- if (!this._processingServerUpdate)
+ if (!this._processingServerUpdate) {
Server.UpdateField(this);
+ }
+ });
+ }
+
+ private runScripts(fields: T[], added: boolean) {
+ for (const script of this.scripts) {
+ this.runScript(fields, script, added);
+ }
+ }
+
+ private runScript(fields: T[], script: ScriptField, added: boolean) {
+ if (!this._processingServerUpdate) {
+ for (const field of fields) {
+ script.script.run({ field, added });
+ }
+ }
+ }
+
+ addScript(script: ScriptField) {
+ this.scripts.push(script);
+ this._scriptIds.push(script.Id);
+
+ this.runScript(this.Data, script, true);
+ UndoManager.AddEvent({
+ undo: () => this.removeScript(script),
+ redo: () => this.addScript(script),
});
+ Server.UpdateField(this);
+ }
+
+ removeScript(script: ScriptField) {
+ const index = this.scripts.indexOf(script);
+ if (index === -1) {
+ return;
+ }
+ this.scripts.splice(index, 1);
+ this._scriptIds.splice(index, 1);
+ UndoManager.AddEvent({
+ undo: () => this.addScript(script),
+ redo: () => this.removeScript(script),
+ });
+ this.runScript(this.Data, script, false);
+ Server.UpdateField(this);
}
protected setData(value: T[]) {
+ this.runScripts(this.data, false);
+
this.data = observable(value);
this.updateProxies();
this.observeList();
+ this.runScripts(this.data, true);
}
private updateProxies() {
this._proxies = this.Data.map(field => field.Id);
}
- UpdateFromServer(fields: string[]) {
- this._proxies = fields;
- }
private arraysEqual(a: any[], b: any[]) {
if (a === b) return true;
- if (a == null || b == null) return false;
- if (a.length != b.length) return false;
+ if (a === null || b === null) return false;
+ if (a.length !== b.length) return false;
// If you don't care about the order of the elements inside
// the array, you should sort both arrays here.
@@ -72,33 +125,42 @@ export class ListField<T extends Field> extends BasicField<T[]> {
}
init(callback: (field: Field) => any) {
- Server.GetFields(this._proxies, action((fields: FieldMap) => {
+ const fieldsPromise = Server.GetFields(this._proxies).then(action((fields: FieldMap) => {
if (!this.arraysEqual(this._proxies, this.data.map(field => field.Id))) {
var dataids = this.data.map(d => d.Id);
var proxies = this._proxies.map(p => p);
var added = this.data.length < this._proxies.length;
var deleted = this.data.length > this._proxies.length;
- for (let i = 0; i < dataids.length && added; i++)
- added = proxies.indexOf(dataids[i]) != -1;
- for (let i = 0; i < this._proxies.length && deleted; i++)
- deleted = dataids.indexOf(proxies[i]) != -1;
+ for (let i = 0; i < dataids.length && added; i++) {
+ added = proxies.indexOf(dataids[i]) !== -1;
+ }
+ for (let i = 0; i < this._proxies.length && deleted; i++) {
+ deleted = dataids.indexOf(proxies[i]) !== -1;
+ }
this._processingServerUpdate = true;
for (let i = 0; i < proxies.length && added; i++) {
- if (dataids.indexOf(proxies[i]) === -1)
+ if (dataids.indexOf(proxies[i]) === -1) {
this.Data.splice(i, 0, fields[proxies[i]] as T);
+ }
}
for (let i = dataids.length - 1; i >= 0 && deleted; i--) {
- if (proxies.indexOf(dataids[i]) === -1)
+ if (proxies.indexOf(dataids[i]) === -1) {
this.Data.splice(i, 1);
+ }
}
if (!added && !deleted) {// otherwise, just rebuild the whole list
this.setData(proxies.map(id => fields[id] as T));
}
this._processingServerUpdate = false;
}
- callback(this);
- }))
+ }));
+
+ const scriptsPromise = Server.GetFields(this._scriptIds).then((fields: FieldMap) => {
+ this.scripts = this._scriptIds.map(id => fields[id] as ScriptField);
+ });
+
+ Promise.all([fieldsPromise, scriptsPromise]).then(() => callback(this));
}
ToScriptString(): string {
@@ -109,17 +171,26 @@ export class ListField<T extends Field> extends BasicField<T[]> {
return new ListField<T>(this.Data);
}
- ToJson(): { type: Types, data: string[], _id: string } {
+
+ UpdateFromServer(data: { fields: string[], scripts: string[] }) {
+ this._proxies = data.fields;
+ this._scriptIds = data.scripts;
+ }
+ ToJson() {
return {
type: Types.List,
- data: this._proxies || [],
- _id: this.Id
- }
+ data: {
+ fields: this._proxies,
+ scripts: this._scriptIds,
+ },
+ id: this.Id
+ };
}
- static FromJson(id: string, ids: string[]): ListField<Field> {
- let list = new ListField([], id, false);
- list._proxies = ids;
- return list
+ static FromJson(id: string, data: { fields: string[], scripts: string[] }): ListField<Field> {
+ let list = new ListField([], [], id, false);
+ list._proxies = data.fields;
+ list._scriptIds = data.scripts;
+ return list;
}
} \ No newline at end of file
diff --git a/src/fields/NumberField.ts b/src/fields/NumberField.ts
index 47dfc74cb..7eea360c0 100644
--- a/src/fields/NumberField.ts
+++ b/src/fields/NumberField.ts
@@ -1,4 +1,4 @@
-import { BasicField } from "./BasicField"
+import { BasicField } from "./BasicField";
import { Types } from "../server/Message";
import { FieldId } from "./Field";
@@ -8,18 +8,18 @@ export class NumberField extends BasicField<number> {
}
ToScriptString(): string {
- return "new NumberField(this.Data)";
+ return `new NumberField(${this.Data})`;
}
Copy() {
return new NumberField(this.Data);
}
- ToJson(): { _id: string, type: Types, data: number } {
+ ToJson() {
return {
- _id: this.Id,
+ id: this.Id,
type: Types.Number,
data: this.Data
- }
+ };
}
} \ No newline at end of file
diff --git a/src/fields/PDFField.ts b/src/fields/PDFField.ts
index b6625387e..718a1a4c0 100644
--- a/src/fields/PDFField.ts
+++ b/src/fields/PDFField.ts
@@ -1,13 +1,13 @@
import { BasicField } from "./BasicField";
import { Field, FieldId } from "./Field";
-import { observable } from "mobx"
+import { observable } from "mobx";
import { Types } from "../server/Message";
export class PDFField extends BasicField<URL> {
constructor(data: URL | undefined = undefined, id?: FieldId, save: boolean = true) {
- super(data == undefined ? new URL("http://cs.brown.edu/~bcz/bob_fettucine.jpg") : data, save, id);
+ super(data === undefined ? new URL("http://cs.brown.edu/~bcz/bob_fettucine.jpg") : data, save, id);
}
toString(): string {
@@ -22,12 +22,12 @@ export class PDFField extends BasicField<URL> {
return `new PDFField("${this.Data}")`;
}
- ToJson(): { type: Types, data: string, _id: string } {
+ ToJson() {
return {
type: Types.PDF,
data: this.Data.href,
- _id: this.Id
- }
+ id: this.Id
+ };
}
@observable
diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts
index 5efb43314..f53f48ca6 100644
--- a/src/fields/RichTextField.ts
+++ b/src/fields/RichTextField.ts
@@ -15,12 +15,12 @@ export class RichTextField extends BasicField<string> {
return new RichTextField(this.Data);
}
- ToJson(): { type: Types, data: string, _id: string } {
+ ToJson() {
return {
type: Types.RichText,
data: this.Data,
- _id: this.Id
- }
+ id: this.Id
+ };
}
} \ No newline at end of file
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
new file mode 100644
index 000000000..7f87be45d
--- /dev/null
+++ b/src/fields/ScriptField.ts
@@ -0,0 +1,101 @@
+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: SerializableOptions;
+}
+
+export class ScriptField extends Field {
+ private _script?: CompiledScript;
+ get script(): CompiledScript {
+ return this._script!;
+ }
+ private options?: ScriptData;
+
+ constructor(script?: CompiledScript, id?: FieldId, save: boolean = true) {
+ super(id);
+
+ this._script = script;
+
+ if (save) {
+ Server.UpdateField(this);
+ }
+ }
+
+ ToScriptString() {
+ return "new ScriptField(...)";
+ }
+
+ GetValue() {
+ return this.script;
+ }
+
+ TrySetValue(): boolean {
+ throw new Error("Script fields currently can't be modified");
+ }
+
+ UpdateFromServer() {
+ 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() {
+ 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: opts,
+ },
+ };
+ }
+
+ Copy(): Field {
+ //Script fields are currently immutable, so we can fake copy them
+ return this;
+ }
+} \ No newline at end of file
diff --git a/src/fields/TextField.ts b/src/fields/TextField.ts
index 71d8ea310..ddedec9b1 100644
--- a/src/fields/TextField.ts
+++ b/src/fields/TextField.ts
@@ -1,4 +1,4 @@
-import { BasicField } from "./BasicField"
+import { BasicField } from "./BasicField";
import { FieldId } from "./Field";
import { Types } from "../server/Message";
@@ -15,11 +15,11 @@ export class TextField extends BasicField<string> {
return new TextField(this.Data);
}
- ToJson(): { type: Types, data: string, _id: string } {
+ ToJson() {
return {
type: Types.Text,
data: this.Data,
- _id: this.Id
- }
+ id: this.Id
+ };
}
} \ No newline at end of file
diff --git a/src/fields/TupleField.ts b/src/fields/TupleField.ts
index e2162c751..347f1fa05 100644
--- a/src/fields/TupleField.ts
+++ b/src/fields/TupleField.ts
@@ -21,7 +21,7 @@ export class TupleField<T, U> extends BasicField<[T, U]> {
UndoManager.AddEvent({
undo: () => this.Data[change.index] = change.oldValue,
redo: () => this.Data[change.index] = change.newValue
- })
+ });
Server.UpdateField(this);
} else {
throw new Error("Why are you messing with the length of a tuple, huh?");
@@ -31,7 +31,7 @@ export class TupleField<T, U> extends BasicField<[T, U]> {
protected setData(value: [T, U]) {
if (this.observeDisposer) {
- this.observeDisposer()
+ this.observeDisposer();
}
this.data = observable(value) as (T | U)[] as [T, U];
this.observeTuple();
@@ -49,11 +49,11 @@ export class TupleField<T, U> extends BasicField<[T, U]> {
return new TupleField<T, U>(this.Data);
}
- ToJson(): { type: Types, data: [T, U], _id: string } {
+ ToJson() {
return {
type: Types.Tuple,
data: this.Data,
- _id: this.Id
- }
+ id: this.Id
+ };
}
} \ No newline at end of file
diff --git a/src/fields/VideoField.ts b/src/fields/VideoField.ts
index 626e4ec83..838b811b1 100644
--- a/src/fields/VideoField.ts
+++ b/src/fields/VideoField.ts
@@ -4,7 +4,7 @@ import { Types } from "../server/Message";
export class VideoField extends BasicField<URL> {
constructor(data: URL | undefined = undefined, id?: FieldId, save: boolean = true) {
- super(data == undefined ? new URL("http://techslides.com/demos/sample-videos/small.mp4") : data, save, id);
+ super(data === undefined ? new URL("http://techslides.com/demos/sample-videos/small.mp4") : data, save, id);
}
toString(): string {
@@ -19,12 +19,12 @@ export class VideoField extends BasicField<URL> {
return new VideoField(this.Data);
}
- ToJson(): { type: Types, data: string, _id: string } {
+ ToJson() {
return {
type: Types.Video,
data: this.Data.href,
- _id: this.Id
- }
+ id: this.Id
+ };
}
} \ No newline at end of file
diff --git a/src/fields/WebField.ts b/src/fields/WebField.ts
index 6c4de5000..8b276a552 100644
--- a/src/fields/WebField.ts
+++ b/src/fields/WebField.ts
@@ -4,7 +4,7 @@ import { Types } from "../server/Message";
export class WebField extends BasicField<URL> {
constructor(data: URL | undefined = undefined, id?: FieldId, save: boolean = true) {
- super(data == undefined ? new URL("https://crossorigin.me/" + "https://cs.brown.edu/") : data, save, id);
+ super(data === undefined ? new URL("https://crossorigin.me/" + "https://cs.brown.edu/") : data, save, id);
}
toString(): string {
@@ -19,12 +19,12 @@ export class WebField extends BasicField<URL> {
return new WebField(this.Data);
}
- ToJson(): { type: Types, data: string, _id: string } {
+ ToJson() {
return {
type: Types.Web,
data: this.Data.href,
- _id: this.Id
- }
+ id: this.Id
+ };
}
} \ No newline at end of file
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index ae48dd2c6..ec89a1194 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -9,6 +9,7 @@ import { RouteStore } from '../server/RouteStore';
import { ServerUtils } from '../server/ServerUtil';
import "./ImageUpload.scss";
import React = require('react');
+import { Opt } from '../fields/Field';
@@ -20,51 +21,47 @@ import React = require('react');
// }
// }
-const onFileLoad = (file: any) => {
- let imgPrev = document.getElementById("img_preview")
+const onFileLoad = async (file: any) => {
+ let imgPrev = document.getElementById("img_preview");
if (imgPrev) {
let files: File[] = file.target.files;
- if (files.length != 0) {
+ if (files.length !== 0) {
console.log(files[0]);
let formData = new FormData();
formData.append("file", files[0]);
- const upload = window.location.origin + "/upload"
- fetch(upload, {
+ const upload = window.location.origin + "/upload";
+ const res = await fetch(upload, {
method: 'POST',
body: formData
- }).then((res: Response) => {
- return res.json()
- }).then(json => {
- json.map((file: any) => {
- let path = window.location.origin + file
- var doc: Document = Documents.ImageDocument(path, { nativeWidth: 200, width: 200 })
+ });
+ const json = await res.json();
+ json.map(async (file: any) => {
+ let path = window.location.origin + file;
+ var doc: Document = Documents.ImageDocument(path, { nativeWidth: 200, width: 200 });
- rp.get(ServerUtils.prepend(RouteStore.getUserDocumentId)).then(res => {
- if (res) {
- return Server.GetField(res);
- }
- throw new Error("No user id returned");
- }).then(field => {
- if (field instanceof Document) {
- return field.GetTAsync(KeyStore.OptionalRightCollection, Document)
- }
- }).then(pending => {
- if (pending) {
- pending.GetOrCreateAsync(KeyStore.Data, ListField, list => {
- list.Data.push(doc);
- })
- }
+ const res = await rp.get(ServerUtils.prepend(RouteStore.getUserDocumentId));
+ if (!res) {
+ throw new Error("No user id returned");
+ }
+ const field = await Server.GetField(res);
+ let pending: Opt<Document>;
+ if (field instanceof Document) {
+ pending = await field.GetTAsync(KeyStore.OptionalRightCollection, Document);
+ }
+ if (pending) {
+ pending.GetOrCreateAsync(KeyStore.Data, ListField, list => {
+ list.Data.push(doc);
});
+ }
+ });
- // console.log(window.location.origin + file[0])
+ // console.log(window.location.origin + file[0])
- //imgPrev.setAttribute("src", window.location.origin + files[0].name)
- })
- })
+ //imgPrev.setAttribute("src", window.location.origin + files[0].name)
}
}
-}
+};
ReactDOM.render((
<div className="imgupload_cont">
diff --git a/src/server/Client.ts b/src/server/Client.ts
index 6b8841658..e6f953712 100644
--- a/src/server/Client.ts
+++ b/src/server/Client.ts
@@ -1,15 +1,11 @@
import { computed } from "mobx";
export class Client {
- constructor(guid: string) {
- this.guid = guid
- }
+ private _guid: string;
- private guid: string;
-
- @computed
- public get GUID(): string {
- return this.guid
+ constructor(guid: string) {
+ this._guid = guid;
}
+ @computed public get GUID(): string { return this._guid; }
} \ No newline at end of file
diff --git a/src/server/Message.ts b/src/server/Message.ts
index 05ae0f19a..bbe4ffcad 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -1,125 +1,35 @@
import { Utils } from "../Utils";
export class Message<T> {
- private name: string;
- private guid: string;
-
- get Name(): string {
- return this.name;
- }
-
- get Message(): string {
- return this.guid
- }
+ private _name: string;
+ private _guid: string;
constructor(name: string) {
- this.name = name;
- this.guid = Utils.GenerateDeterministicGuid(name)
+ this._name = name;
+ this._guid = Utils.GenerateDeterministicGuid(name);
}
- GetValue() {
- return this.Name;
- }
-}
-
-class TestMessageArgs {
- hello: string = "";
-}
-
-export class SetFieldArgs {
- field: string;
- value: any;
-
- constructor(f: string, v: any) {
- this.field = f
- this.value = v
- }
-}
-
-export class GetFieldArgs {
- field: string;
-
- constructor(f: string) {
- this.field = f
- }
+ get Name(): string { return this._name; }
+ get Message(): string { return this._guid; }
}
export enum Types {
- Number, List, Key, Image, Web, Document, Text, RichText, DocumentReference, Html, Video, Audio, Ink, PDF, Tuple, HistogramOp
-}
-
-export class DocumentTransfer implements Transferable {
- readonly type = Types.Document
- _id: string
-
- constructor(readonly obj: { type: Types, data: [string, string][], _id: string }) {
- this._id = obj._id
- }
-}
-
-export class ImageTransfer implements Transferable {
- readonly type = Types.Image
-
- constructor(readonly _id: string) { }
-}
-
-export class KeyTransfer implements Transferable {
- name: string
- readonly _id: string
- readonly type = Types.Key
-
- constructor(i: string, n: string) {
- this.name = n
- this._id = i
- }
-}
-
-export class ListTransfer implements Transferable {
- type = Types.List;
-
- constructor(readonly _id: string) { }
-}
-
-export class NumberTransfer implements Transferable {
- readonly type = Types.Number
-
- constructor(readonly value: number, readonly _id: string) { }
-}
-
-export class TextTransfer implements Transferable {
- value: string
- readonly _id: string
- readonly type = Types.Text
-
- constructor(t: string, i: string) {
- this.value = t
- this._id = i
- }
-}
-
-export class RichTextTransfer implements Transferable {
- value: string
- readonly _id: string
- readonly type = Types.Text
-
- constructor(t: string, i: string) {
- this.value = t
- this._id = i
- }
+ Number, List, Key, Image, Web, Document, Text, RichText, DocumentReference,
+ Html, Video, Audio, Ink, PDF, Tuple, HistogramOp, Boolean, Script,
}
export interface Transferable {
- readonly _id: string
- readonly type: Types
+ readonly id: string;
+ readonly type: Types;
+ readonly data?: any;
}
export namespace MessageStore {
export const Foo = new Message<string>("Foo");
export const Bar = new Message<string>("Bar");
- export const AddDocument = new Message<DocumentTransfer>("Add Document");
- export const SetField = new Message<{ _id: string, data: any, type: Types }>("Set Field")
- export const GetField = new Message<string>("Get Field")
- export const GetFields = new Message<string[]>("Get Fields")
+ export const SetField = new Message<Transferable>("Set Field"); // send Transferable (no reply)
+ export const GetField = new Message<string>("Get Field"); // send string 'id' get Transferable back
+ export const GetFields = new Message<string[]>("Get Fields"); // send string[] of 'id' get Transferable[] back
export const GetDocument = new Message<string>("Get Document");
export const DeleteAll = new Message<any>("Delete All");
-} \ No newline at end of file
+}
diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts
index 98a7a1451..818230c1a 100644
--- a/src/server/ServerUtil.ts
+++ b/src/server/ServerUtil.ts
@@ -1,78 +1,60 @@
-
-import { Field } from './../fields/Field';
-import { TextField } from './../fields/TextField';
-import { NumberField } from './../fields/NumberField';
-import { RichTextField } from './../fields/RichTextField';
-import { Key } from './../fields/Key';
-import { ImageField } from './../fields/ImageField';
-import { ListField } from './../fields/ListField';
-import { Document } from './../fields/Document';
-import { Server } from './../client/Server';
-import { Types } from './Message';
-import { Utils } from '../Utils';
-import { HtmlField } from '../fields/HtmlField';
-import { WebField } from '../fields/WebField';
-import { AudioField } from '../fields/AudioField';
-import { VideoField } from '../fields/VideoField';
-import { InkField } from '../fields/InkField';
-import { PDFField } from '../fields/PDFField';
-import { TupleField } from '../fields/TupleField';
-import { HistogramField } from '../client/northstar/dash-fields/HistogramField';
-
-
+import { HistogramField } from "../client/northstar/dash-fields/HistogramField";
+import { AudioField } from "../fields/AudioField";
+import { BooleanField } from "../fields/BooleanField";
+import { HtmlField } from "../fields/HtmlField";
+import { InkField } from "../fields/InkField";
+import { PDFField } from "../fields/PDFField";
+import { ScriptField } from "../fields/ScriptField";
+import { TupleField } from "../fields/TupleField";
+import { VideoField } from "../fields/VideoField";
+import { WebField } from "../fields/WebField";
+import { Utils } from "../Utils";
+import { Document } from "./../fields/Document";
+import { Field } from "./../fields/Field";
+import { ImageField } from "./../fields/ImageField";
+import { Key } from "./../fields/Key";
+import { ListField } from "./../fields/ListField";
+import { NumberField } from "./../fields/NumberField";
+import { RichTextField } from "./../fields/RichTextField";
+import { TextField } from "./../fields/TextField";
+import { Transferable, Types } from "./Message";
export class ServerUtils {
- public static prepend(extension: string): string { return window.location.origin + extension; }
+ public static prepend(extension: string): string {
+ return window.location.origin + extension;
+ }
- public static FromJson(json: any): Field {
- let obj = json
- let data: any = obj.data
- let id: string = obj._id
- let type: Types = obj.type
+ public static FromJson(json: Transferable): Field {
- if (!(data !== undefined && id && type !== undefined)) {
- console.log("how did you manage to get an object that doesn't have a data or an id?")
+ if (!(json.data !== undefined && json.id && json.type !== undefined)) {
+ console.log(
+ "how did you manage to get an object that doesn't have a data or an id?"
+ );
return new TextField("Something to fill the space", Utils.GenerateGuid());
}
- switch (type) {
- case Types.Number:
- return new NumberField(data, id, false)
- case Types.Text:
- return new TextField(data, id, false)
- case Types.Html:
- return new HtmlField(data, id, false)
- case Types.Web:
- return new WebField(new URL(data), id, false)
- case Types.RichText:
- return new RichTextField(data, id, false)
- case Types.Key:
- return new Key(data, id, false)
- case Types.Image:
- return new ImageField(new URL(data), id, false)
- case Types.HistogramOp:
- return HistogramField.FromJson(id, data);
- case Types.PDF:
- return new PDFField(new URL(data), id, false)
- case Types.List:
- return ListField.FromJson(id, data)
- case Types.Audio:
- return new AudioField(new URL(data), id, false)
- case Types.Video:
- return new VideoField(new URL(data), id, false)
- case Types.Tuple:
- return new TupleField(data, id, false);
- case Types.Ink:
- return InkField.FromJson(id, data);
- case Types.Document:
- let doc: Document = new Document(id, false)
- let fields: [string, string][] = data as [string, string][]
- fields.forEach(element => {
- doc._proxies.set(element[0], element[1]);
- });
- return doc
+ switch (json.type) {
+ case Types.Boolean: return new BooleanField(json.data, json.id, false);
+ case Types.Number: return new NumberField(json.data, json.id, false);
+ case Types.Text: return new TextField(json.data, json.id, false);
+ case Types.Html: return new HtmlField(json.data, json.id, false);
+ case Types.Web: return new WebField(new URL(json.data), json.id, false);
+ case Types.RichText: return new RichTextField(json.data, json.id, false);
+ case Types.Key: return new Key(json.data, json.id, false);
+ case Types.Image: return new ImageField(new URL(json.data), json.id, false);
+ case Types.HistogramOp: return HistogramField.FromJson(json.id, json.data);
+ case Types.PDF: return new PDFField(new URL(json.data), json.id, false);
+ case Types.List: return ListField.FromJson(json.id, json.data);
+ case Types.Script: return ScriptField.FromJson(json.id, json.data);
+ case Types.Audio: return new AudioField(new URL(json.data), json.id, false);
+ case Types.Video: return new VideoField(new URL(json.data), json.id, false);
+ case Types.Tuple: return new TupleField(json.data, json.id, false);
+ case Types.Ink: return InkField.FromJson(json.id, json.data);
+ case Types.Document: return Document.FromJson(json.data, json.id, false);
default:
- throw Error("Error, unrecognized field type received from server. If you just created a new field type, be sure to add it here");
+ throw Error(
+ "Error, unrecognized field type received from server. If you just created a new field type, be sure to add it here"
+ );
}
}
-} \ No newline at end of file
+}
diff --git a/src/server/authentication/config/passport.ts b/src/server/authentication/config/passport.ts
index b6fe15655..d42741410 100644
--- a/src/server/authentication/config/passport.ts
+++ b/src/server/authentication/config/passport.ts
@@ -1,4 +1,4 @@
-import * as passport from 'passport'
+import * as passport from 'passport';
import * as passportLocal from 'passport-local';
import * as mongodb from 'mongodb';
import * as _ from "lodash";
@@ -22,7 +22,7 @@ passport.deserializeUser<any, any>((id, done) => {
passport.use(new LocalStrategy({ usernameField: 'email', passReqToCallback: true }, (req, email, password, done) => {
User.findOne({ email: email.toLowerCase() }, (error: any, user: any) => {
if (error) return done(error);
- if (!user) return done(undefined, false, { message: "Invalid email or password" }) // invalid email
+ if (!user) return done(undefined, false, { message: "Invalid email or password" }); // invalid email
user.comparePassword(password, (error: Error, isMatch: boolean) => {
if (error) return done(error);
if (!isMatch) return done(undefined, false, { message: "Invalid email or password" }); // invalid password
@@ -37,7 +37,7 @@ export let isAuthenticated = (req: Request, res: Response, next: NextFunction) =
return next();
}
return res.redirect(RouteStore.login);
-}
+};
export let isAuthorized = (req: Request, res: Response, next: NextFunction) => {
const provider = req.path.split("/").slice(-1)[0];
diff --git a/src/server/authentication/controllers/WorkspacesMenu.tsx b/src/server/authentication/controllers/WorkspacesMenu.tsx
index 8e14cf98e..b08c1aebe 100644
--- a/src/server/authentication/controllers/WorkspacesMenu.tsx
+++ b/src/server/authentication/controllers/WorkspacesMenu.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import { observable, action, configure, reaction, computed, ObservableMap, runInAction } from 'mobx';
import { observer } from "mobx-react";
-import './WorkspacesMenu.css'
+import './WorkspacesMenu.css';
import { Document } from '../../../fields/Document';
import { EditableView } from '../../../client/views/EditableView';
import { KeyStore } from '../../../fields/KeyStore';
@@ -73,7 +73,7 @@ export class WorkspacesMenu extends React.Component<WorkspaceMenuProps> {
<span>{i + 1} - </span>
<EditableView
display={"inline"}
- GetValue={() => { return s.Title }}
+ GetValue={() => s.Title}
SetValue={(title: string): boolean => {
s.SetText(KeyStore.Title, title);
return true;
diff --git a/src/server/authentication/controllers/user_controller.ts b/src/server/authentication/controllers/user_controller.ts
index e365b8dce..1dacdf3fa 100644
--- a/src/server/authentication/controllers/user_controller.ts
+++ b/src/server/authentication/controllers/user_controller.ts
@@ -4,7 +4,7 @@ import * as passport from "passport";
import { IVerifyOptions } from "passport-local";
import "../config/passport";
import * as request from "express-validator";
-const flash = require("express-flash");
+import flash = require("express-flash");
import * as session from "express-session";
import * as pug from 'pug';
import * as async from 'async';
@@ -109,12 +109,12 @@ export let postLogin = (req: Request, res: Response, next: NextFunction) => {
}
passport.authenticate("local", (err: Error, user: DashUserModel, info: IVerifyOptions) => {
- if (err) { return next(err); }
+ if (err) { next(err); return; }
if (!user) {
return res.redirect(RouteStore.signup);
}
req.logIn(user, (err) => {
- if (err) { return next(err); }
+ if (err) { next(err); return; }
res.redirect(RouteStore.home);
});
})(req, res, next);
@@ -132,14 +132,14 @@ export let getLogout = (req: Request, res: Response) => {
sess.destroy((err) => { if (err) { console.log(err); } });
}
res.redirect(RouteStore.login);
-}
+};
export let getForgot = function (req: Request, res: Response) {
res.render("forgot.pug", {
title: "Recover Password",
user: req.user,
});
-}
+};
export let postForgot = function (req: Request, res: Response, next: NextFunction) {
const email = req.body.email;
@@ -152,13 +152,14 @@ export let postForgot = function (req: Request, res: Response, next: NextFunctio
return;
}
done(null, buffer.toString('hex'));
- })
+ });
},
function (token: string, done: any) {
User.findOne({ email }, function (err, user: DashUserModel) {
if (!user) {
// NO ACCOUNT WITH SUBMITTED EMAIL
- return res.redirect(RouteStore.forgot);
+ res.redirect(RouteStore.forgot);
+ return;
}
user.passwordResetToken = token;
user.passwordResetExpires = new Date(Date.now() + 3600000); // 1 HOUR
@@ -192,8 +193,8 @@ export let postForgot = function (req: Request, res: Response, next: NextFunctio
], function (err) {
if (err) return next(err);
res.redirect(RouteStore.forgot);
- })
-}
+ });
+};
export let getReset = function (req: Request, res: Response) {
User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } }, function (err, user: DashUserModel) {
@@ -205,7 +206,7 @@ export let getReset = function (req: Request, res: Response) {
user: req.user,
});
});
-}
+};
export let postReset = function (req: Request, res: Response) {
async.waterfall([
@@ -228,7 +229,8 @@ export let postReset = function (req: Request, res: Response) {
user.save(function (err) {
if (err) {
- return res.redirect(RouteStore.login);
+ res.redirect(RouteStore.login);
+ return;
}
req.logIn(user, function (err) {
if (err) {
@@ -261,4 +263,4 @@ export let postReset = function (req: Request, res: Response) {
], function (err) {
res.redirect(RouteStore.login);
});
-} \ No newline at end of file
+}; \ No newline at end of file
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 0ac85b446..5d4479c88 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -1,102 +1,75 @@
-import { DashUserModel } from "./user_model";
+import { computed, observable, action, runInAction } from "mobx";
import * as rp from 'request-promise';
-import { RouteStore } from "../../RouteStore";
-import { ServerUtils } from "../../ServerUtil";
+import { Documents } from "../../../client/documents/Documents";
+import { Attribute, AttributeGroup, Catalog, Schema } from "../../../client/northstar/model/idea/idea";
+import { ArrayUtil } from "../../../client/northstar/utils/ArrayUtil";
import { Server } from "../../../client/Server";
import { Document } from "../../../fields/Document";
import { KeyStore } from "../../../fields/KeyStore";
import { ListField } from "../../../fields/ListField";
-import { Documents } from "../../../client/documents/Documents";
-import { Schema, Attribute, AttributeGroup, Catalog } from "../../../client/northstar/model/idea/idea";
-import { observable, computed, action } from "mobx";
-import { ArrayUtil } from "../../../client/northstar/utils/ArrayUtil";
+import { RouteStore } from "../../RouteStore";
+import { ServerUtils } from "../../ServerUtil";
export class CurrentUserUtils {
private static curr_email: string;
private static curr_id: string;
- private static user_document: Document;
+ @observable private static user_document: Document;
//TODO tfs: these should be temporary...
private static mainDocId: string | undefined;
- @observable private static catalog?: Catalog;
-
- public static get email(): string {
- return this.curr_email;
- }
-
- public static get id(): string {
- return this.curr_id;
- }
-
- public static get UserDocument(): Document {
- return this.user_document;
- }
- public static get MainDocId(): string | undefined {
- return this.mainDocId;
- }
-
- public static set MainDocId(id: string | undefined) {
- this.mainDocId = id;
- }
-
- @computed public static get NorthstarDBCatalog(): Catalog | undefined {
- return this.catalog;
- }
- public static set NorthstarDBCatalog(ctlog: Catalog | undefined) {
- this.catalog = ctlog;
- }
- public static GetNorthstarSchema(name: string): Schema | undefined {
- return !this.catalog || !this.catalog.schemas ? undefined :
- ArrayUtil.FirstOrDefault<Schema>(this.catalog.schemas, (s: Schema) => s.displayName === name);
- }
- public static GetAllNorthstarColumnAttributes(schema: Schema) {
- if (!schema || !schema.rootAttributeGroup) {
- return [];
- }
- const recurs = (attrs: Attribute[], g: AttributeGroup) => {
- if (g.attributes) {
- attrs.push.apply(attrs, g.attributes);
- if (g.attributeGroups) {
- g.attributeGroups.forEach(ng => recurs(attrs, ng));
- }
- }
- };
- const allAttributes: Attribute[] = new Array<Attribute>();
- recurs(allAttributes, schema.rootAttributeGroup);
- return allAttributes;
- }
+ public static get email() { return this.curr_email; }
+ public static get id() { return this.curr_id; }
+ @computed public static get UserDocument() { return this.user_document; }
+ public static get MainDocId() { return this.mainDocId; }
+ public static set MainDocId(id: string | undefined) { this.mainDocId = id; }
private static createUserDocument(id: string): Document {
let doc = new Document(id);
-
doc.Set(KeyStore.Workspaces, new ListField<Document>());
- doc.Set(KeyStore.OptionalRightCollection, Documents.SchemaDocument([], { title: "Pending documents" }))
+ doc.Set(KeyStore.OptionalRightCollection, Documents.SchemaDocument([], { title: "Pending documents" }));
return doc;
}
public static loadCurrentUser(): Promise<any> {
- let userPromise = rp.get(ServerUtils.prepend(RouteStore.getCurrUser)).then((response) => {
+ let userPromise = rp.get(ServerUtils.prepend(RouteStore.getCurrUser)).then(response => {
if (response) {
let obj = JSON.parse(response);
CurrentUserUtils.curr_id = obj.id as string;
CurrentUserUtils.curr_email = obj.email as string;
} else {
- throw new Error("There should be a user! Why does Dash think there isn't one?")
+ throw new Error("There should be a user! Why does Dash think there isn't one?");
}
});
let userDocPromise = rp.get(ServerUtils.prepend(RouteStore.getUserDocumentId)).then(id => {
if (id) {
- return Server.GetField(id).then(field => {
- if (field instanceof Document) {
- this.user_document = field;
- } else {
- this.user_document = this.createUserDocument(id);
- }
- })
+ return Server.GetField(id).then(field =>
+ runInAction(() => this.user_document = field instanceof Document ? field : this.createUserDocument(id)));
} else {
- throw new Error("There should be a user id! Why does Dash think there isn't one?")
+ throw new Error("There should be a user id! Why does Dash think there isn't one?");
}
});
return Promise.all([userPromise, userDocPromise]);
}
+
+ /* Northstar catalog ... really just for testing so this should eventually go away */
+ @observable private static _northstarCatalog?: Catalog;
+ @computed public static get NorthstarDBCatalog() { return this._northstarCatalog; }
+ public static set NorthstarDBCatalog(ctlog: Catalog | undefined) { this._northstarCatalog = ctlog; }
+
+ public static GetNorthstarSchema(name: string): Schema | undefined {
+ return !this._northstarCatalog || !this._northstarCatalog.schemas ? undefined :
+ ArrayUtil.FirstOrDefault<Schema>(this._northstarCatalog.schemas, (s: Schema) => s.displayName === name);
+ }
+ public static GetAllNorthstarColumnAttributes(schema: Schema) {
+ const recurs = (attrs: Attribute[], g?: AttributeGroup) => {
+ if (g && g.attributes) {
+ attrs.push.apply(attrs, g.attributes);
+ if (g.attributeGroups) {
+ g.attributeGroups.forEach(ng => recurs(attrs, ng));
+ }
+ }
+ return attrs;
+ };
+ return recurs([] as Attribute[], schema ? schema.rootAttributeGroup : undefined);
+ }
} \ No newline at end of file
diff --git a/src/server/authentication/models/user_model.ts b/src/server/authentication/models/user_model.ts
index 81580aad5..d5c84c311 100644
--- a/src/server/authentication/models/user_model.ts
+++ b/src/server/authentication/models/user_model.ts
@@ -2,7 +2,7 @@
import * as bcrypt from "bcrypt-nodejs";
//@ts-ignore
import * as mongoose from "mongoose";
-var url = 'mongodb://localhost:27017/Dash'
+var url = 'mongodb://localhost:27017/Dash';
mongoose.connect(url, { useNewUrlParser: true });
@@ -18,8 +18,8 @@ mongoose.connection.on('disconnected', function () {
export type DashUserModel = mongoose.Document & {
email: string,
password: string,
- passwordResetToken: string | undefined,
- passwordResetExpires: Date | undefined,
+ passwordResetToken?: string,
+ passwordResetExpires?: Date,
userDocumentId: string;
@@ -67,11 +67,17 @@ const userSchema = new mongoose.Schema({
*/
userSchema.pre("save", function save(next) {
const user = this as DashUserModel;
- if (!user.isModified("password")) { return next(); }
+ if (!user.isModified("password")) {
+ return next();
+ }
bcrypt.genSalt(10, (err, salt) => {
- if (err) { return next(err); }
+ if (err) {
+ return next(err);
+ }
bcrypt.hash(user.password, salt, () => void {}, (err: mongoose.Error, hash) => {
- if (err) { return next(err); }
+ if (err) {
+ return next(err);
+ }
user.password = hash;
next();
});
@@ -79,9 +85,8 @@ userSchema.pre("save", function save(next) {
});
const comparePassword: comparePasswordFunction = function (this: DashUserModel, candidatePassword, cb) {
- bcrypt.compare(candidatePassword, this.password, (err: mongoose.Error, isMatch: boolean) => {
- cb(err, isMatch);
- });
+ bcrypt.compare(candidatePassword, this.password, (err: mongoose.Error, isMatch: boolean) =>
+ cb(err, isMatch));
};
userSchema.methods.comparePassword = comparePassword;
diff --git a/src/server/database.ts b/src/server/database.ts
index a42d29aac..7914febf8 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -1,96 +1,78 @@
-import { action, configure } from 'mobx';
import * as mongodb from 'mongodb';
-import { ObjectID } from 'mongodb';
import { Transferable } from './Message';
-import { Utils } from '../Utils';
export class Database {
- public static Instance = new 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());
}
public update(id: string, value: any, callback: () => void) {
if (this.db) {
let collection = this.db.collection('documents');
- 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));
- }
- callback()
- });
+ const prom = this.currentWrites[id];
+ let newProm: Promise<void>;
+ const run = (): Promise<void> => {
+ return 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();
+ });
+ });
+ };
+ newProm = prom ? prom.then(run) : run();
+ this.currentWrites[id] = newProm;
}
}
- 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: (result?: Transferable) => void, collectionName = Database.DocumentsCollection) {
+ this.db && this.db.collection(collectionName).findOne({ id: id }, (err, result) =>
+ fn(result ? ({ id: result._id, type: result.type, data: result.data }) : 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: (result: Transferable[]) => 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.map(doc => ({ id: doc._id, type: doc.type, data: doc.data })));
+ });
}
public print() {
- console.log("db says hi!")
+ console.log("db says hi!");
}
}
diff --git a/src/server/index.ts b/src/server/index.ts
index 17d7432e0..3cbe1ca76 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1,49 +1,45 @@
-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 formidable from 'formidable'
+import * as bodyParser from 'body-parser';
+import { exec } from 'child_process';
+import * as cookieParser from 'cookie-parser';
+import * as express from 'express';
+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.connect(mongoUrl);
+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,
@@ -95,10 +87,12 @@ function addSecureRoute(method: Method,
...subscribers: string[]
) {
let abstracted = (req: express.Request, res: express.Response) => {
- const dashUser: DashUserModel = req.user;
- if (!dashUser) return onRejection(res);
- handler(dashUser, res, req);
- }
+ if (req.user) {
+ handler(req.user, res, req);
+ } else {
+ onRejection(res);
+ }
+ };
subscribers.forEach(route => {
switch (method) {
case Method.GET:
@@ -112,21 +106,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.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 +133,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 +150,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
);
@@ -178,17 +160,16 @@ addSecureRoute(
addSecureRoute(
Method.POST,
(user, res, req) => {
- let form = new formidable.IncomingForm()
- form.uploadDir = __dirname + "/public/files/"
- form.keepExtensions = true
+ let form = new formidable.IncomingForm();
+ form.uploadDir = __dirname + "/public/files/";
+ form.keepExtensions = true;
// let path = req.body.path;
- console.log("upload")
+ console.log("upload");
form.parse(req, (err, fields, files) => {
- console.log("parsing")
- let names: any[] = [];
+ console.log("parsing");
+ 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);
});
@@ -211,89 +192,74 @@ app.post(RouteStore.login, postLogin);
app.get(RouteStore.logout, getLogout);
// FORGOT PASSWORD EMAIL HANDLING
-app.get(RouteStore.forgot, getForgot)
-app.post(RouteStore.forgot, postForgot)
+app.get(RouteStore.forgot, getForgot);
+app.post(RouteStore.forgot, postForgot);
// RESET PASSWORD EMAIL HANDLING
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))
+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 {
[key: string]: Client;
}
-let clients: Map = {}
+let clients: Map = {};
server.on("connection", function (socket: Socket) {
- console.log("a user has connected")
+ console.log("a user has connected");
- Utils.Emit(socket, MessageStore.Foo, "handshooken")
+ Utils.Emit(socket, MessageStore.Foo, "handshooken");
- Utils.AddServerHandler(socket, MessageStore.Bar, barReceived)
- Utils.AddServerHandler(socket, MessageStore.SetField, (args) => setField(socket, args))
- Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField)
- Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields)
- Utils.AddServerHandler(socket, MessageStore.DeleteAll, deleteFields)
-})
+ Utils.AddServerHandler(socket, MessageStore.Bar, barReceived);
+ Utils.AddServerHandler(socket, MessageStore.SetField, (args) => setField(socket, args));
+ Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField);
+ Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields);
+ Utils.AddServerHandler(socket, MessageStore.DeleteAll, deleteFields);
+});
function deleteFields() {
return Database.Instance.deleteAll();
}
-function deleteAll() {
- return Database.Instance.deleteAll().then(() => {
- return Database.Instance.deleteAll('sessions')
- }).then(() => {
- return Database.Instance.deleteAll('users')
- });
+async function deleteAll() {
+ await Database.Instance.deleteAll();
+ await Database.Instance.deleteAll('sessions');
+ await Database.Instance.deleteAll('users');
}
function barReceived(guid: String) {
clients[guid.toString()] = new Client(guid.toString());
}
-function getField([id, callback]: [string, (result: any) => void]) {
- Database.Instance.getDocument(id, (result: any) => {
- if (result) {
- callback(result)
- }
- else {
- callback(undefined)
- }
- })
+function getField([id, callback]: [string, (result?: Transferable) => void]) {
+ Database.Instance.getDocument(id, (result?: Transferable) =>
+ callback(result ? result : undefined));
}
-function getFields([ids, callback]: [string[], (result: any) => void]) {
+function getFields([ids, callback]: [string[], (result: Transferable[]) => void]) {
Database.Instance.getDocuments(ids, callback);
}
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);
diff --git a/src/server/public/files/.gitignore b/src/server/public/files/.gitignore
new file mode 100644
index 000000000..f59ec20aa
--- /dev/null
+++ b/src/server/public/files/.gitignore
@@ -0,0 +1 @@
+* \ No newline at end of file
diff --git a/src/server/public/files/upload_e72669595eae4384a2a32196496f4f05.pdf b/src/server/public/files/upload_e72669595eae4384a2a32196496f4f05.pdf
deleted file mode 100644
index 8e58bfddd..000000000
--- a/src/server/public/files/upload_e72669595eae4384a2a32196496f4f05.pdf
+++ /dev/null
Binary files differ