diff options
author | Bob Zeleznik <zzzman@gmail.com> | 2019-04-14 21:44:36 -0400 |
---|---|---|
committer | Bob Zeleznik <zzzman@gmail.com> | 2019-04-14 21:44:36 -0400 |
commit | a5ae4d9867ed98e0e3ae52dee47596c62e2f74f7 (patch) | |
tree | 46bd0ab317c5a61892071071a5ab6889a1a97911 /src | |
parent | 7b7f1fb2865522da414314afbdb09847e7a9409c (diff) | |
parent | c787b0eac374b4dabf6ede7ee40e77a28815d5c8 (diff) |
Merge branch 'master' into interaction_changes
Diffstat (limited to 'src')
60 files changed, 652 insertions, 846 deletions
diff --git a/src/Utils.ts b/src/Utils.ts index c629bc263..e07d4e82d 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -1,7 +1,7 @@ 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 { @@ -48,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)}`); } diff --git a/src/client/Server.ts b/src/client/Server.ts index 3bbbebe72..66e9878d9 100644 --- a/src/client/Server.ts +++ b/src/client/Server.ts @@ -12,7 +12,6 @@ export class Server { static Socket: SocketIOClient.Socket = OpenSocket(`${window.location.protocol}//${window.location.hostname}:4321`); 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. public static GetField(fieldid: FieldId): Promise<Opt<Field>>; @@ -127,13 +126,6 @@ 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); } @@ -161,18 +153,18 @@ export class Server { } @action - static updateField(field: { _id: string, data: any, type: Types }) { - if (Server.ClientFieldsCached.has(field._id)) { - var f = Server.ClientFieldsCached.get(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); + // console.log("Applying : " + field.id); f.UpdateFromServer(field.data); 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 5e2ca6a98..382a81f66 100644 --- a/src/client/SocketStub.ts +++ b/src/client/SocketStub.ts @@ -2,7 +2,7 @@ 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 { MessageStore, Transferable } from "../server/Message"; import { Utils } from "../Utils"; import { Server } from "./Server"; import { ServerUtils } from "../server/ServerUtil"; @@ -16,35 +16,12 @@ 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 { @@ -60,36 +37,15 @@ export class SocketStub { } public static SEND_FIELDS_REQUEST(fieldIds: FieldId[], callback: (fields: FieldMap) => any) { - Utils.EmitCallback(Server.Socket, MessageStore.GetFields, fieldIds, (fields: any[]) => { - let fieldMap: any = {}; - let proms: Promise<any>[] = []; - for (let field of fields) { - let f = ServerUtils.FromJson(field); - fieldMap[field._id] = f; - proms.push(new Promise(res => f.init(res))); - } + 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 diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 3c36fe500..4febfa7eb 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -127,7 +127,7 @@ export namespace Documents { function CreateImagePrototype(): Document { let imageProto = setupPrototypeOptions(imageProtoId, "IMAGE_PROTO", CollectionView.LayoutString("AnnotationsKey"), - { x: 0, y: 0, nativeWidth: 300, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] }); + { 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; diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts index 6abde4677..82de51d56 100644 --- a/src/client/northstar/dash-fields/HistogramField.ts +++ b/src/client/northstar/dash-fields/HistogramField.ts @@ -35,12 +35,11 @@ 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 }; } diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx index 2084fc346..3e94fed81 100644 --- a/src/client/northstar/dash-nodes/HistogramBox.tsx +++ b/src/client/northstar/dash-nodes/HistogramBox.tsx @@ -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"; diff --git a/src/client/northstar/manager/Gateway.ts b/src/client/northstar/manager/Gateway.ts index 207a9ad19..d26f2724f 100644 --- a/src/client/northstar/manager/Gateway.ts +++ b/src/client/northstar/manager/Gateway.ts @@ -36,7 +36,7 @@ export class Gateway { 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"); @@ -180,18 +180,18 @@ export class Gateway { public static ConstructUrl(appendix: string): string { - let base = Settings.Instance.ServerUrl; + 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 @@ -248,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") { @@ -278,9 +278,9 @@ export class Settings { 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/binRanges/VisualBinRangeHelper.ts b/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts index 9671e55f8..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"; diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 3e093c8dc..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 { computed, observable } from 'mobx'; import { Document } from "../../fields/Document"; -import { DocumentView } from '../views/nodes/DocumentView'; -import { KeyStore } from '../../fields/KeyStore'; 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,10 +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; @@ -39,7 +33,6 @@ 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 (doc === toFind) { toReturn = view; @@ -51,7 +44,7 @@ export class DocumentManager { } }); - return (toReturn); + return toReturn; } public getDocumentViews(toFind: Document): DocumentView[] { @@ -72,7 +65,7 @@ export class DocumentManager { } }); - return (toReturn); + return toReturn; } @computed @@ -84,9 +77,8 @@ export class DocumentManager { 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 }); - }); + 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 6a7047725..4bd654e15 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -1,13 +1,12 @@ import { action } from "mobx"; import { Document } from "../../fields/Document"; +import { FieldWaiting } from "../../fields/Field"; +import { KeyStore } from "../../fields/KeyStore"; import { emptyFunction } from "../../Utils"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; import { DocumentDecorations } from "../views/DocumentDecorations"; -import { Main } from "../views/Main"; -import { DocumentView } from "../views/nodes/DocumentView"; import * as globalCssVariables from "../views/globalCssVariables.scss"; -import { KeyStore } from "../../fields/KeyStore"; -import { FieldWaiting } from "../../fields/Field"; +import { MainOverlayTextBox } from "../views/MainOverlayTextBox"; export function SetupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Document, moveFunc?: DragManager.MoveFunction, copyOnDrop: boolean = false) { let onRowMove = action((e: PointerEvent): void => { @@ -140,11 +139,13 @@ export namespace DragManager { 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; copyOnDrop?: boolean; moveDocument?: MoveFunction; @@ -175,7 +176,7 @@ export namespace DragManager { dragDiv.className = "dragManager-dragDiv"; DragManager.Root().appendChild(dragDiv); } - Main.Instance.SetTextDoc(); + MainOverlayTextBox.Instance.SetTextDoc(); let scaleXs: number[] = []; let scaleYs: number[] = []; diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 2fa45a086..c56f6a4ff 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -2,6 +2,7 @@ import { observable, action } from "mobx"; import { DocumentView } from "../views/nodes/DocumentView"; import { Document } from "../../fields/Document"; import { Main } from "../views/Main"; +import { MainOverlayTextBox } from "../views/MainOverlayTextBox"; export namespace SelectionManager { class Manager { @@ -25,7 +26,7 @@ export namespace SelectionManager { DeselectAll(): void { manager.SelectedDocuments.map(dv => dv.props.onActiveChanged(false)); manager.SelectedDocuments = []; - Main.Instance.SetTextDoc(); + MainOverlayTextBox.Instance.SetTextDoc(); } } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index a72f02f14..3dbb1cb0f 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -1,24 +1,19 @@ -import { action, computed, observable, trace, runInAction } from "mobx"; +import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; 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 { Document } from "../../fields/Document"; import { TextField } from "../../fields/TextField"; -import { DragManager, DragLinksAsDocuments } from "../util/DragManager"; +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"); -import { FieldWaiting, Field } from "../../fields/Field"; -import { emptyFunction } from "../../Utils"; -import { Main } from "./Main"; -import { undo } from "prosemirror-history"; -import { undoBatch } from "../util/UndoManager"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -77,14 +72,12 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> if (this._documents.length > 0) { let field = this._documents[0].props.Document.Get(this._fieldKey); if (field instanceof TextField) { - this._documents.forEach(d => { - d.props.Document.Set(this._fieldKey, new TextField(this._title)); - }); + this._documents.forEach(d => + d.props.Document.Set(this._fieldKey, new TextField(this._title))); } else if (field instanceof NumberField) { - this._documents.forEach(d => { - d.props.Document.Set(this._fieldKey, new NumberField(+this._title)) - }); + this._documents.forEach(d => + d.props.Document.Set(this._fieldKey, new NumberField(+this._title))); } this._title = "changed"; } @@ -317,7 +310,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> break; } - Main.Instance.SetTextDoc(); + MainOverlayTextBox.Instance.SetTextDoc(); SelectionManager.SelectedDocuments().forEach(element => { const rect = element.screenRect(); if (rect.width !== 0) { diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss index 13cadb10d..4373534b2 100644 --- a/src/client/views/Main.scss +++ b/src/client/views/Main.scss @@ -168,24 +168,6 @@ button:hover { left:0; overflow: scroll; } -.mainDiv-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; - } -} #mainContent-div { width:100%; height:100%; diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index ed61aa5a7..d26586216 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -1,7 +1,7 @@ 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, trace } from 'mobx'; +import { action, computed, configure, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import "normalize.css"; import * as React from 'react'; @@ -9,26 +9,25 @@ 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 } from '../../fields/Field'; +import { Field, FieldWaiting, Opt, FIELD_WAITING } from '../../fields/Field'; import { KeyStore } from '../../fields/KeyStore'; import { ListField } from '../../fields/ListField'; import { WorkspacesMenu } from '../../server/authentication/controllers/WorkspacesMenu'; import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; import { MessageStore } from '../../server/Message'; -import { Utils, returnTrue, emptyFunction, emptyDocFunction } from '../../Utils'; -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, Settings } from '../northstar/manager/Gateway'; -import { AggregateFunction, Catalog, Point } from '../northstar/model/idea/idea'; +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, DragManager } from '../util/DragManager'; +import { SetupDrag } from '../util/DragManager'; import { Transform } from '../util/Transform'; import { UndoManager } from '../util/UndoManager'; import { CollectionDockingView } from './collections/CollectionDockingView'; @@ -36,41 +35,27 @@ import { ContextMenu } from './ContextMenu'; import { DocumentDecorations } from './DocumentDecorations'; import { InkingControl } from './InkingControl'; import "./Main.scss"; +import { MainOverlayTextBox } from './MainOverlayTextBox'; import { DocumentView } from './nodes/DocumentView'; -import { FormattedTextBox } from './nodes/FormattedTextBox'; -import { REPLCommand } from 'repl'; -import { Key } from '../../fields/Key'; import { PreviewCursor } from './PreviewCursor'; @observer export class Main extends React.Component { - // 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; - } - private set mainContainer(doc: Document | undefined) { - if (doc) { - this.userDocument.Set(KeyStore.ActiveWorkspace, doc); - } + @computed private get mainContainer(): Document | undefined | FIELD_WAITING { + return CurrentUserUtils.UserDocument.GetT(KeyStore.ActiveWorkspace, Document); } - - private get userDocument(): Document { - return CurrentUserUtils.UserDocument; + private set mainContainer(doc: Document | undefined | FIELD_WAITING) { + doc && CurrentUserUtils.UserDocument.Set(KeyStore.ActiveWorkspace, doc); } - public static Instance: Main; - constructor(props: Readonly<{}>) { super(props); - this._textProxyDiv = React.createRef(); Main.Instance = this; // causes errors to be generated when modifying an observable outside of an action configure({ enforceActions: "observed" }); @@ -102,6 +87,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("/"); @@ -114,14 +103,6 @@ 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 @@ -137,7 +118,7 @@ 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); @@ -146,19 +127,15 @@ export class Main extends React.Component { } }); } 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)] }] }; @@ -179,139 +156,49 @@ 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); - }); - } - - @observable - workspacesShown: boolean = false; - - areWorkspacesShown = () => this.workspacesShown; - @action - toggleWorkspaces = () => { - this.workspacesShown = !this.workspacesShown; - } - - pwidthFunc = () => this.pwidth; - pheightFunc = () => this.pheight; - noScaling = () => 1; - - @observable _textDoc?: Document = undefined; - _textRect: any; - _textXf: Transform = Transform.Identity(); - _textScroll: number = 0; - _textFieldKey: Key = KeyStore.Data; - _textColor: string | null = null; - _textTargetDiv: HTMLDivElement | undefined; - _textProxyDiv: React.RefObject<HTMLDivElement>; - @action - SetTextDoc(textDoc?: Document, textFieldKey?: Key, div?: HTMLDivElement, tx?: Transform) { - if (this._textTargetDiv) { - this._textTargetDiv.style.color = this._textColor; - } - - this._textDoc = undefined; - this._textDoc = textDoc; - this._textFieldKey = textFieldKey!; - this._textXf = tx ? tx : Transform.Identity(); - this._textTargetDiv = div; - if (div) { - this._textColor = div.style.color; - div.style.color = "transparent"; - this._textRect = div.getBoundingClientRect(); - this._textScroll = div.scrollTop; - } - } - - @action - textScroll = (e: React.UIEvent) => { - if (this._textProxyDiv.current && this._textTargetDiv) { - this._textTargetDiv.scrollTop = this._textScroll = this._textProxyDiv.current.children[0].scrollTop; - } - } - - textBoxDown = (e: React.PointerEvent) => { - if (e.button !== 0 || e.metaKey || e.altKey) { - document.addEventListener("pointermove", this.textBoxMove); - document.addEventListener('pointerup', this.textBoxUp); - } - } - textBoxMove = (e: PointerEvent) => { - if (e.movementX > 1 || e.movementY > 1) { - document.removeEventListener("pointermove", this.textBoxMove); - document.removeEventListener('pointerup', this.textBoxUp); - let dragData = new DragManager.DocumentDragData([this._textDoc!]); - const [left, top] = this._textXf - .inverse() - .transformPoint(0, 0); - dragData.xOffset = e.clientX - left; - dragData.yOffset = e.clientY - top; - DragManager.StartDocumentDrag([this._textTargetDiv!], dragData, e.clientX, e.clientY, { - handlers: { - dragComplete: action(emptyFunction), - }, - hideSource: false - }); - } - } - textBoxUp = (e: PointerEvent) => { - document.removeEventListener("pointermove", this.textBoxMove); - document.removeEventListener('pointerup', this.textBoxUp); - } - - @computed - get activeTextBox() { - 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="mainDiv-textInput" style={{ pointerEvents: "none", transform: `translate(${x}px, ${y}px) scale(${1 / s[0]},${1 / s[0]})`, width: "auto", height: "auto" }} > - <div className="mainDiv-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll} style={{ pointerEvents: "none", transform: `scale(${1}, ${1})`, width: `${w * s[0]}px`, height: `${h * s[0]}px` }}> - <FormattedTextBox fieldKey={this._textFieldKey!} isOverlay={true} Document={this._textDoc} isSelected={returnTrue} select={emptyFunction} isTopMost={true} - selectOnLoad={true} onActiveChanged={emptyFunction} active={returnTrue} ScreenToLocalTransform={() => this._textXf} focus={emptyDocFunction} /> - </div> - </ div>; - } - else return (null); + setTimeout(() => + col && col.GetTAsync<ListField<Document>>(KeyStore.Data, ListField, (f: Opt<ListField<Document>>) => + f && f.Data.length > 0 && CollectionDockingView.Instance.AddRightSplit(col)) + , 100) + ); } @computed get mainContent() { - return !this.mainContainer ? (null) : - <DocumentView Document={this.mainContainer} - addDocument={undefined} - removeDocument={undefined} - ScreenToLocalTransform={Transform.Identity} - ContentScaling={this.noScaling} - PanelWidth={this.pwidthFunc} - PanelHeight={this.pheightFunc} - isTopMost={true} - selectOnLoad={false} - focus={emptyDocFunction} - parentActive={returnTrue} - onActiveChanged={emptyFunction} - ContainingCollectionView={undefined} />; + let pwidthFunc = () => this.pwidth; + let pheightFunc = () => this.pheight; + let noScaling = () => 1; + let mainCont = this.mainContainer; + 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) : + <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> + } + </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"; @@ -360,6 +247,7 @@ 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, {})); return [ @@ -370,55 +258,50 @@ 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), 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} - <PreviewCursor /> - </div> - } - </Measure> - <ContextMenu /> - {this.nodesMenu} - {this.miscButtons} - {workspaceMenu} - <InkingControl /> - </div> - {this.activeTextBox} - </> + <div id="main-div"> + <DocumentDecorations /> + {this.mainContent} + <PreviewCursor /> + <ContextMenu /> + {this.nodesMenu} + {this.miscButtons} + {this.workspaceMenu} + <InkingControl /> + <MainOverlayTextBox /> + </div> ); } // --------------- Northstar hooks ------------- / + private _northstarSchemas: Document[] = []; - @action AddToNorthstarCatalog(ctlog: Catalog) { - CurrentUserUtils.NorthstarDBCatalog = CurrentUserUtils.NorthstarDBCatalog ? CurrentUserUtils.NorthstarDBCatalog : ctlog; + @action SetNorthstarCatalog(ctlog: Catalog) { + CurrentUserUtils.NorthstarDBCatalog = ctlog; if (ctlog && ctlog.schemas) { ctlog.schemas.map(schema => { - let promises: Promise<void>[] = []; let schemaDocuments: Document[] = []; - CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => { - let prom = Server.GetField(attr.displayName! + ".alias").then(action((field: Opt<Field>) => { + 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 { @@ -429,32 +312,17 @@ export class Main extends React.Component { new AttributeTransformationModel(atmod, AggregateFunction.Count)); schemaDocuments.push(Documents.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName! }, undefined, attr.displayName! + ".alias")); } - })); - promises.push(prom); - }); - Promise.all(promises).finally(() => { - let schemaDoc = Documents.TreeDocument(schemaDocuments, { width: 50, height: 100, title: schema.displayName! }); - this._northstarSchemas.push(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.AddToNorthstarCatalog(await Gateway.Instance.GetCatalog()); - // if (!CurrentUserUtils.GetNorthstarSchema("Book1")) - // this.AddToNorthstarCatalog(await Gateway.Instance.GetSchema("http://www.cs.brown.edu/~bcz/Book1.csv", "Book1")); - }); - + 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())); } } 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/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index b5eaab349..bff56e8c3 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -1,4 +1,4 @@ -import { action } from 'mobx'; +import { action, computed } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Document } from '../../../fields/Document'; @@ -80,12 +80,16 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { } 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); } diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index eb1cd1c09..4f7d4fc0d 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -172,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); } @@ -343,6 +343,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { <DocumentView key={this._document.Id} Document={this._document} addDocument={undefined} removeDocument={undefined} + opacity={1} ContentScaling={this._contentScaling} PanelWidth={this._nativeWidth} PanelHeight={this._nativeHeight} diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx index 6cbe59012..229bc4059 100644 --- a/src/client/views/collections/CollectionPDFView.tsx +++ b/src/client/views/collections/CollectionPDFView.tsx @@ -1,4 +1,4 @@ -import { action, computed, observable } from "mobx"; +import { action } from "mobx"; import { observer } from "mobx-react"; import { KeyStore } from "../../../fields/KeyStore"; import { ContextMenu } from "../ContextMenu"; @@ -42,7 +42,7 @@ export class CollectionPDFView extends React.Component<FieldViewProps> { let props = { ...this.props, ...renderProps }; return ( <> - <CollectionFreeFormView {...props} /> + <CollectionFreeFormView {...props} CollectionView={this} /> {this.props.isSelected() ? this.uIButtons : (null)} </> ); diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index fdb82690a..2c99c9c67 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -75,6 +75,7 @@ export class CollectionSchemaView extends CollectionSubView { let props: FieldViewProps = { Document: rowProps.value[0], fieldKey: rowProps.value[1], + ContainingCollectionView: this.props.CollectionView, isSelected: returnFalse, select: emptyFunction, isTopMost: false, @@ -307,12 +308,13 @@ export class CollectionSchemaView extends CollectionSubView { <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={undefined} + ContainingCollectionView={this.props.CollectionView} focus={emptyDocFunction} parentActive={this.props.active} onActiveChanged={this.props.onActiveChanged} /> : null} diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index d91db68bb..d3d69b1af 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -16,6 +16,9 @@ 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 extends FieldViewProps { addDocument: (document: Document, allowDuplicates?: boolean) => boolean; @@ -24,6 +27,7 @@ export interface CollectionViewProps extends FieldViewProps { } export interface SubCollectionViewProps extends CollectionViewProps { + CollectionView: CollectionView | CollectionPDFView | CollectionVideoView; } export type CursorEntry = TupleField<[string, string], [number, number]>; @@ -193,7 +197,7 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> { }).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: 300, width: 300, title: dropFileName }); + let docPromise = this.getDocumentFromType(type, path, { ...options, nativeWidth: 600, width: 300, title: dropFileName }); docPromise.then(action((doc?: Document) => { let docs = this.props.Document.GetT(KeyStore.Data, ListField); diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx index 6c9780adb..29fb342c6 100644 --- a/src/client/views/collections/CollectionVideoView.tsx +++ b/src/client/views/collections/CollectionVideoView.tsx @@ -109,7 +109,7 @@ export class CollectionVideoView extends React.Component<FieldViewProps> { let props = { ...this.props, ...renderProps }; return ( <> - <CollectionFreeFormView {...props} /> + <CollectionFreeFormView {...props} CollectionView={this} /> {this.props.isSelected() ? this.uIButtons : (null)} </> ); diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 8abd0a02d..1f69a69e8 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -18,12 +18,12 @@ export class CollectionView extends React.Component<FieldViewProps> { private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => { let props = { ...this.props, ...renderProps }; switch (type) { - case CollectionViewType.Schema: return (<CollectionSchemaView {...props} />); - case CollectionViewType.Docking: return (<CollectionDockingView {...props} />); - case CollectionViewType.Tree: return (<CollectionTreeView {...props} />); + 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} />); + return (<CollectionFreeFormView {...props} CollectionView={this} />); } return (null); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index 647c83d4d..2f684a54e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -1,7 +1,6 @@ -import { computed, reaction, trace, IReactionDisposer } 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"; @@ -85,19 +84,17 @@ 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) => { + possiblePairs.map(possiblePair => + 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); - } + 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] }); - } - }); + }, 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/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index e19dc98fa..cb4e8fb2e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,10 +1,9 @@ -import { action, computed, observable, runInAction } 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 { NumberField } from "../../../../fields/NumberField"; import { TextField } from "../../../../fields/TextField"; import { emptyFunction, returnFalse } from "../../../../Utils"; import { DocumentManager } from "../../../util/DocumentManager"; @@ -18,13 +17,14 @@ import { Main } from "../../Main"; import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; import { DocumentContentsView } from "../../nodes/DocumentContentsView"; import { DocumentViewProps } from "../../nodes/DocumentView"; -import { CollectionSubView } from "../CollectionSubView"; +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 { MainOverlayTextBox } from "../../MainOverlayTextBox"; @observer export class CollectionFreeFormView extends CollectionSubView { @@ -38,19 +38,16 @@ export class CollectionFreeFormView extends CollectionSubView { } 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[]) => { SelectionManager.DeselectAll; - docs.map(doc => { - const dv = DocumentManager.Instance.getDocumentView(doc); - if (dv) { - SelectionManager.SelectDoc(dv, true); - } - }); + docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).filter(dv => dv).map(dv => + SelectionManager.SelectDoc(dv!, true)); } public getActiveDocuments = () => { @@ -84,41 +81,28 @@ export class CollectionFreeFormView extends CollectionSubView { @undoBatch @action drop = (e: Event, de: DragManager.DropEvent) => { - if (super.drop(e, de)) { - if (de.data instanceof DragManager.DocumentDragData) { - let droppedDocs = de.data.droppedDocuments; - let xoff = de.data.xOffset as number || 0; - let yoff = de.data.yOffset as number || 0; - if (droppedDocs.length) { - let screenX = de.x - xoff; - let screenY = de.y - yoff; - const [x, y] = this.getTransform().transformPoint(screenX, screenY); - let dragDoc = droppedDocs[0]; - let dragX = dragDoc.GetNumber(KeyStore.X, 0); - let dragY = dragDoc.GetNumber(KeyStore.Y, 0); - droppedDocs.map(async 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)); - let docW = await d.GetTAsync(KeyStore.Width, NumberField); - let docH = await d.GetTAsync(KeyStore.Height, NumberField); - if (!docW) { - d.SetNumber(KeyStore.Width, 300); - } - if (!docH) { - d.SetNumber(KeyStore.Height, 300); - } - this.bringToFront(d); - }); - } + 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); @@ -150,7 +134,28 @@ export class CollectionFreeFormView extends CollectionSubView { 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; @@ -162,7 +167,9 @@ export class CollectionFreeFormView extends CollectionSubView { @action onPointerWheel = (e: React.WheelEvent): void => { - this.props.select(false); + // if (!this.props.active()) { + // return; + // } e.stopPropagation(); let coefficient = 1000; @@ -192,12 +199,14 @@ export class CollectionFreeFormView extends CollectionSubView { 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) { - Main.Instance.SetTextDoc(); + 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)); @@ -227,9 +236,9 @@ export class CollectionFreeFormView extends CollectionSubView { 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 { @@ -252,9 +261,10 @@ export class CollectionFreeFormView extends CollectionSubView { this.props.focus(this.props.Document); } - getDocumentViewProps(document: Document): DocumentViewProps { + getDocumentViewProps(document: Document, opacity: number): DocumentViewProps { return { Document: document, + opacity: opacity, addDocument: this.props.addDocument, removeDocument: this.props.removeDocument, moveDocument: this.props.moveDocument, @@ -264,7 +274,7 @@ export class CollectionFreeFormView extends CollectionSubView { PanelWidth: document.Width, PanelHeight: document.Height, ContentScaling: this.noScaling, - ContainingCollectionView: undefined, + ContainingCollectionView: this.props.CollectionView, focus: this.focusDocument, parentActive: this.props.active, onActiveChanged: this.props.active, @@ -274,25 +284,34 @@ export class CollectionFreeFormView extends CollectionSubView { @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[]); + + 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)} + (<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)} + (<DocumentContentsView {...this.getDocumentViewProps(this.props.Document, 1)} layoutKey={KeyStore.OverlayLayout} isTopMost={this.props.isTopMost} isSelected={returnFalse} select={emptyFunction} />); } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss index e5ffcec76..ae0a9fd48 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss @@ -13,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 a4722a6f8..dbab790d4 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -209,10 +209,11 @@ 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); + 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); } @@ -224,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/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 77f41105f..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(); } @@ -57,7 +51,7 @@ export class CollectionFreeFormDocumentView extends React.Component<DocumentView 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.contentScaling()).scale(1 / this.zoom) @computed get docView() { @@ -74,6 +68,7 @@ export class CollectionFreeFormDocumentView extends React.Component<DocumentView 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 5836da396..88900c4b1 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -44,19 +44,19 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { CreateBindings(): JsxBindings { - let - { - Document, - isSelected, - select, - isTopMost, - selectOnLoad, - ScreenToLocalTransform, - addDocument, - removeDocument, - onActiveChanged, - parentActive: active, - } = this.props; + let { + Document, + isSelected, + select, + isTopMost, + selectOnLoad, + ScreenToLocalTransform, + ContainingCollectionView, + addDocument, + removeDocument, + onActiveChanged, + parentActive: active, + } = this.props; let bindings: JsxBindings = { props: { Document, @@ -65,6 +65,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { isTopMost, selectOnLoad, ScreenToLocalTransform, + ContainingCollectionView, active, onActiveChanged, addDocument, diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 4eef07a2f..e74b609bb 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,31 +1,34 @@ -import { action, computed, IReactionDisposer, reaction, runInAction, trace } 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, FieldWaiting, Opt } from "../../../fields/Field"; import { Key } from "../../../fields/Key"; import { KeyStore } from "../../../fields/KeyStore"; import { ListField } from "../../../fields/ListField"; -import { BooleanField } from "../../../fields/BooleanField"; import { TextField } from "../../../fields/TextField"; import { ServerUtils } from "../../../server/ServerUtil"; -import { Utils, emptyFunction } 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 { CollectionPDFView } from "../collections/CollectionPDFView"; +import { CollectionVideoView } from "../collections/CollectionVideoView"; import { CollectionView } from "../collections/CollectionView"; import { ContextMenu } from "../ContextMenu"; import { DocumentContentsView } from "./DocumentContentsView"; import "./DocumentView.scss"; import React = require("react"); -import { undoBatch, UndoManager } from "../../util/UndoManager"; export interface DocumentViewProps { - ContainingCollectionView: Opt<CollectionView>; + 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; diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 0037d7b28..562de4827 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -1,7 +1,7 @@ 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"; @@ -20,6 +20,9 @@ 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"; // @@ -29,6 +32,7 @@ import { returnFalse, emptyDocFunction } from "../../../Utils"; // export interface FieldViewProps { fieldKey: Key; + ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>; Document: Document; isSelected: () => boolean; select: (isCtrlPressed: boolean) => void; @@ -79,6 +83,7 @@ export class FieldView extends React.Component<FieldViewProps> { <DocumentContentsView Document={field} addDocument={undefined} removeDocument={undefined} + opacity={1} ScreenToLocalTransform={Transform.Identity} ContentScaling={() => 1} PanelWidth={() => 100} @@ -89,7 +94,7 @@ export class FieldView extends React.Component<FieldViewProps> { isSelected={returnFalse} select={returnFalse} layoutKey={KeyStore.Layout} - ContainingCollectionView={undefined} + ContainingCollectionView={this.props.ContainingCollectionView} parentActive={this.props.active} onActiveChanged={this.props.onActiveChanged} /> ); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 8ea747b1c..87c1bcb1e 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -14,6 +14,9 @@ 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"); @@ -55,15 +58,20 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte 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); + 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); } } @@ -85,7 +93,7 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte }; if (this.props.isOverlay) { - this._inputReactionDisposer = reaction(() => Main.Instance._textDoc && Main.Instance._textDoc.Id, + this._inputReactionDisposer = reaction(() => MainOverlayTextBox.Instance.TextDoc && MainOverlayTextBox.Instance.TextDoc.Id, () => { if (this._editorView) { this._editorView.destroy(); @@ -96,7 +104,7 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte ); } else { this._proxyReactionDisposer = reaction(() => this.props.isSelected(), - () => this.props.isSelected() && Main.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform())); + () => this.props.isSelected() && MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform())); } this._reactionDisposer = reaction( @@ -105,7 +113,7 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte return field && field !== FieldWaiting ? field.Data : undefined; }, field => { - if (field && this._editorView) { + if (field && this._editorView && !this._applyingChange) { this._editorView.updateState( EditorState.fromJSON(config, JSON.parse(field)) ); @@ -177,10 +185,10 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte onFocused = (e: React.FocusEvent): void => { if (!this.props.isOverlay) { - Main.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform()); + MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform()); } else { if (this._ref.current) { - this._ref.current.scrollTop = Main.Instance._textScroll; + this._ref.current.scrollTop = MainOverlayTextBox.Instance.TextScroll; } } } @@ -209,7 +217,9 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte } onPointerWheel = (e: React.WheelEvent): void => { - e.stopPropagation(); + if (this.props.isSelected()) { + e.stopPropagation(); + } } tooltipMenuPlugin() { @@ -229,6 +239,7 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte render() { return ( <div + style={{ overflowY: this.props.isSelected() || this.props.isOverlay ? "scroll" : "hidden" }} className={`formattedTextBox-cont`} onKeyDown={this.onKeyPress} onKeyPress={this.onKeyPress} diff --git a/src/client/views/nodes/KeyValuePair.scss b/src/client/views/nodes/KeyValuePair.scss index 04d002c7b..01701e02c 100644 --- a/src/client/views/nodes/KeyValuePair.scss +++ b/src/client/views/nodes/KeyValuePair.scss @@ -1,30 +1,28 @@ @import "../globalCssVariables"; -.container{ - width:100%; - height:100%; - display: flex; - flex-direction: row; - flex-wrap: nowrap; - justify-content: space-between; -} .keyValuePair-td-key { display:inline-block; - width: 50%; + .keyValuePair-td-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; - width: 50%; -} -.keyValuePair-keyField { - width:100%; - text-align: center; - position: relative; - overflow: auto; -} -.delete{ - position: relative; - background-color: transparent; - color:red; }
\ No newline at end of file diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 3e0b61c3d..5d69f23b2 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -1,18 +1,18 @@ -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 { action, observable } from 'mobx'; import { observer } from "mobx-react"; -import { observable, action } from 'mobx'; +import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app import { Document } from '../../../fields/Document'; +import { Field, Opt } from '../../../fields/Field'; import { Key } from '../../../fields/Key'; +import { emptyDocFunction, emptyFunction, returnFalse } from '../../../Utils'; import { Server } from "../../Server"; -import { EditableView } from "../EditableView"; import { CompileScript, ToField } from "../../util/Scripting"; import { Transform } from '../../util/Transform'; -import { returnFalse, emptyFunction, emptyDocFunction } from '../../../Utils'; +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 @@ -25,28 +25,23 @@ export interface KeyValuePairProps { @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 = { Document: this.props.doc, + ContainingCollectionView: undefined, fieldKey: this.key, isSelected: returnFalse, select: emptyFunction, @@ -57,19 +52,17 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { ScreenToLocalTransform: Transform.Identity, focus: emptyDocFunction, }; - let contents = ( - <FieldView {...props} /> - ); + let contents = <FieldView {...props} />; return ( <tr className={this.props.rowStyle}> <td className="keyValuePair-td-key" style={{ width: `${this.props.keyWidth}%` }}> - <div className="container"> - <button className="delete" onClick={() => { + <div className="keyValuePair-td-key-container"> + <button className="keyValuePair-td-key-delete" onClick={() => { let field = props.Document.Get(props.fieldKey); - if (field && field instanceof Field) { - props.Document.Set(props.fieldKey, undefined); - } - }}>X</button> + field && field instanceof Field && props.Document.Set(props.fieldKey, undefined); + }}> + X + </button> <div className="keyValuePair-keyField">{this.key.Name}</div> </div> </td> diff --git a/src/fields/AudioField.ts b/src/fields/AudioField.ts index 996d2556d..87e47a715 100644 --- a/src/fields/AudioField.ts +++ b/src/fields/AudioField.ts @@ -20,11 +20,11 @@ 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 }; } diff --git a/src/fields/BooleanField.ts b/src/fields/BooleanField.ts index d319b4021..d49bfe82b 100644 --- a/src/fields/BooleanField.ts +++ b/src/fields/BooleanField.ts @@ -15,11 +15,11 @@ export class BooleanField extends BasicField<boolean> { return new BooleanField(this.Data); } - ToJson(): { type: Types; data: boolean; _id: string } { + ToJson() { return { type: Types.Boolean, data: this.Data, - _id: this.Id + id: this.Id }; } } diff --git a/src/fields/Document.ts b/src/fields/Document.ts index 628fe684c..7cf784f0e 100644 --- a/src/fields/Document.ts +++ b/src/fields/Document.ts @@ -26,6 +26,12 @@ export class Document extends Field { 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) { @@ -410,18 +416,15 @@ export class Document extends Field { return copy; } - ToJson(): { type: Types; data: [string, string][]; _id: string } { + ToJson() { let fields: [string, string][] = []; - this._proxies.forEach((field, key) => { - if (field) { - fields.push([key, field]); - } - }); + this._proxies.forEach((field, key) => + field && fields.push([key, field])); return { type: Types.Document, data: fields, - _id: this.Id + id: this.Id }; } } diff --git a/src/fields/DocumentReference.ts b/src/fields/DocumentReference.ts index 6c0c1ef82..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 d9db23b9e..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> { @@ -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 65665cf7a..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 dd843026f..bce20f242 100644 --- a/src/fields/ImageField.ts +++ b/src/fields/ImageField.ts @@ -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 ab706ee30..2eacd7d0c 100644 --- a/src/fields/InkField.ts +++ b/src/fields/InkField.ts @@ -31,11 +31,11 @@ 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, }; } diff --git a/src/fields/Key.ts b/src/fields/Key.ts index c7f806b88..57e2dadf0 100644 --- a/src/fields/Key.ts +++ b/src/fields/Key.ts @@ -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 da2d7268f..16a909eb8 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"); @@ -48,7 +49,7 @@ export namespace KeyStore { export const CopyDraggedItems = new Key("CopyDraggedItems"); export const KeyList: Key[] = [Prototype, X, Y, Page, Title, Author, PanX, PanY, Scale, NativeWidth, NativeHeight, - Width, Height, ZIndex, Data, Annotations, ViewType, Layout, BackgroundColor, BackgroundLayout, OverlayLayout, LayoutKeys, + 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 diff --git a/src/fields/ListField.ts b/src/fields/ListField.ts index 8311e737b..e24099126 100644 --- a/src/fields/ListField.ts +++ b/src/fields/ListField.ts @@ -176,14 +176,14 @@ export class ListField<T extends Field> extends BasicField<T[]> { this._proxies = data.fields; this._scriptIds = data.scripts; } - ToJson(): { type: Types, data: { fields: string[], scripts: string[] }, _id: string } { + ToJson() { return { type: Types.List, data: { fields: this._proxies, scripts: this._scriptIds, }, - _id: this.Id + id: this.Id }; } diff --git a/src/fields/NumberField.ts b/src/fields/NumberField.ts index 45b920e31..7eea360c0 100644 --- a/src/fields/NumberField.ts +++ b/src/fields/NumberField.ts @@ -15,9 +15,9 @@ export class NumberField extends BasicField<number> { 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 }; diff --git a/src/fields/PDFField.ts b/src/fields/PDFField.ts index 65e179894..718a1a4c0 100644 --- a/src/fields/PDFField.ts +++ b/src/fields/PDFField.ts @@ -22,11 +22,11 @@ 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 }; } diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts index 6f7b3074a..f53f48ca6 100644 --- a/src/fields/RichTextField.ts +++ b/src/fields/RichTextField.ts @@ -15,11 +15,11 @@ 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 }; } diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index 34c8a7544..7f87be45d 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -73,7 +73,7 @@ export class ScriptField extends Field { }); } - ToJson(): { _id: string, type: Types, data: ScriptData } { + ToJson() { const { options, originalScript } = this.script; let capturedIds: { [id: string]: string } = {}; for (const capt in options.capturedVariables) { @@ -85,7 +85,7 @@ export class ScriptField extends Field { }; delete (opts as any).capturedVariables; return { - _id: this.Id, + id: this.Id, type: Types.Script, data: { script: originalScript, diff --git a/src/fields/TextField.ts b/src/fields/TextField.ts index 69d26f42f..ddedec9b1 100644 --- a/src/fields/TextField.ts +++ b/src/fields/TextField.ts @@ -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 ad0f6f350..347f1fa05 100644 --- a/src/fields/TupleField.ts +++ b/src/fields/TupleField.ts @@ -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 d7cd7e968..838b811b1 100644 --- a/src/fields/VideoField.ts +++ b/src/fields/VideoField.ts @@ -19,11 +19,11 @@ 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 }; } diff --git a/src/fields/WebField.ts b/src/fields/WebField.ts index 6023e9e6b..8b276a552 100644 --- a/src/fields/WebField.ts +++ b/src/fields/WebField.ts @@ -19,11 +19,11 @@ 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 }; } diff --git a/src/server/Client.ts b/src/server/Client.ts index b4c419438..e6f953712 100644 --- a/src/server/Client.ts +++ b/src/server/Client.ts @@ -8,5 +8,4 @@ export class Client { } @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 d22e5c17c..bbe4ffcad 100644 --- a/src/server/Message.ts +++ b/src/server/Message.ts @@ -1,146 +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); - } - - 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; + this._name = name; + this._guid = Utils.GenerateDeterministicGuid(name); } -} -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, - Boolean, - Script, -} - -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 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"); } diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts index 0973f82b1..818230c1a 100644 --- a/src/server/ServerUtil.ts +++ b/src/server/ServerUtil.ts @@ -1,83 +1,56 @@ -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 { HistogramField } from "../client/northstar/dash-fields/HistogramField"; import { AudioField } from "../fields/AudioField"; -import { VideoField } from "../fields/VideoField"; +import { BooleanField } from "../fields/BooleanField"; +import { HtmlField } from "../fields/HtmlField"; import { InkField } from "../fields/InkField"; import { PDFField } from "../fields/PDFField"; -import { TupleField } from "../fields/TupleField"; -import { BooleanField } from "../fields/BooleanField"; -import { HistogramField } from "../client/northstar/dash-fields/HistogramField"; 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 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)) { + 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.Boolean: - return new BooleanField(data, id, false); - 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.Script: - return ScriptField.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" diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 13eddafbf..5d4479c88 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -1,81 +1,37 @@ -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" })); 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; @@ -86,17 +42,34 @@ export class CurrentUserUtils { }); 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?"); } }); 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 1c6926517..d5c84c311 100644 --- a/src/server/authentication/models/user_model.ts +++ b/src/server/authentication/models/user_model.ts @@ -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 e08385d98..7914febf8 100644 --- a/src/server/database.ts +++ b/src/server/database.ts @@ -1,23 +1,25 @@ import * as mongodb from 'mongodb'; +import { Transferable } from './Message'; export class Database { public static DocumentsCollection = 'documents'; public static Instance = new Database(); private MongoClient = mongodb.MongoClient; private url = 'mongodb://localhost:27017/Dash'; - private currentWrites: { [_id: string]: Promise<void> } = {}; + private currentWrites: { [id: string]: Promise<void> } = {}; private db?: mongodb.Db; constructor() { this.MongoClient.connect(this.url, (err, client) => this.db = client.db()); } - public update(id: string, value: any, callback: () => void, collectionName = Database.DocumentsCollection) { + public update(id: string, value: any, callback: () => void) { if (this.db) { - let collection = this.db.collection(collectionName); + let collection = this.db.collection('documents'); const prom = this.currentWrites[id]; + let newProm: Promise<void>; const run = (): Promise<void> => { - let newProm = new Promise<void>(resolve => { + return new Promise<void>(resolve => { collection.updateOne({ _id: id }, { $set: value }, { upsert: true } , (err, res) => { if (err) { @@ -34,14 +36,14 @@ export class Database { callback(); }); }); - return newProm; }; - this.currentWrites[id] = prom ? prom.then(run) : run(); + newProm = prom ? prom.then(run) : run(); + this.currentWrites[id] = newProm; } } public delete(id: string, collectionName = Database.DocumentsCollection) { - this.db && this.db.collection(collectionName).remove({ _id: id }); + this.db && this.db.collection(collectionName).remove({ id: id }); } public deleteAll(collectionName = Database.DocumentsCollection): Promise<any> { @@ -55,18 +57,18 @@ export class Database { ); } - public getDocument(id: string, fn: (res: any) => void, collectionName = Database.DocumentsCollection) { - this.db && this.db.collection(collectionName).findOne({ _id: id }, (err, result) => - fn(result ? result : undefined)); + public 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, collectionName = Database.DocumentsCollection) { - this.db && this.db.collection(collectionName).find({ _id: { "$in": ids } }).toArray((err, 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); + fn(docs.map(doc => ({ id: doc._id, type: doc.type, data: doc.data }))); }); } diff --git a/src/server/index.ts b/src/server/index.ts index a6fe6fa2c..3cbe1ca76 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -87,9 +87,11 @@ 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) { @@ -246,17 +248,17 @@ 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) => +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, () => + Database.Instance.update(newValue.id, newValue, () => socket.broadcast.emit(MessageStore.SetField.Message, newValue)); } |