From 1cf618563838f4ce7d8a98c8a0c8d94670bc4e18 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 21 Mar 2019 21:03:00 -0400 Subject: changes to allow tabs to be dragged into other documents --- src/client/views/collections/CollectionDockingView.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index fd0810242..950df7261 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -17,6 +17,7 @@ import { COLLECTION_BORDER_WIDTH } from "./CollectionView"; import React = require("react"); import { SubCollectionViewProps } from "./CollectionViewBase"; import { ServerUtils } from "../../../server/ServerUtil"; +import { DragManager } from "../../util/DragManager"; @observer export class CollectionDockingView extends React.Component { @@ -190,6 +191,21 @@ export class CollectionDockingView extends React.Component { var className = (e.target as any).className; + if ((className == "lm_title" || className == "lm_tab lm_active") && e.ctrlKey) { + e.stopPropagation(); + e.preventDefault(); + let docid = (e.target as any).DashDocId; + let tab = (e.target as any).parentElement as HTMLElement; + Server.GetField(docid, action((f: Opt) => + DragManager.StartDocumentDrag(tab, new DragManager.DocumentDragData(f as Document), + { + handlers: { + dragComplete: action(() => { }), + }, + hideSource: true + })) + ); + } if (className == "lm_drag_handle" || className == "lm_close" || className == "lm_maximise" || className == "lm_minimise" || className == "lm_close_tab") { this._flush = true; } @@ -208,6 +224,7 @@ export class CollectionDockingView extends React.Component { + tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId; tab.closeElement.off('click') //unbind the current click handler .click(function () { tab.contentItem.remove(); -- cgit v1.2.3-70-g09d2 From bc59ea805f32568f0835bd55d39575236c24a066 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 21 Mar 2019 22:22:25 -0400 Subject: added very basic cycle detection when adding to collections --- src/client/util/DragManager.ts | 4 +--- .../views/collections/CollectionDockingView.tsx | 2 +- .../views/collections/CollectionFreeFormView.tsx | 21 ++++++++++++--------- src/client/views/collections/CollectionPDFView.tsx | 2 +- .../views/collections/CollectionVideoView.tsx | 2 +- src/client/views/collections/CollectionView.tsx | 22 ++++++++++++++++++---- .../views/collections/CollectionViewBase.tsx | 12 ++++++++---- src/client/views/collections/MarqueeView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 2 +- 9 files changed, 44 insertions(+), 25 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 753115f76..661fa4dc8 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -197,9 +197,7 @@ export namespace DragManager { document.removeEventListener("pointermove", moveHandler, true); document.removeEventListener("pointerup", upHandler); dragDiv.removeChild(dragElement); - if (hideSource && !wasHidden) { - ele.hidden = false; - } + ele.hidden = false; } const upHandler = (e: PointerEvent) => { abortDrag(); diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 950df7261..39b284d8e 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -202,7 +202,7 @@ export class CollectionDockingView extends React.Component { }), }, - hideSource: true + hideSource: false })) ); } diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx index da9f7b392..8b9d178c9 100644 --- a/src/client/views/collections/CollectionFreeFormView.tsx +++ b/src/client/views/collections/CollectionFreeFormView.tsx @@ -75,16 +75,19 @@ export class CollectionFreeFormView extends CollectionViewBase { @undoBatch @action - drop = (e: Event, de: DragManager.DropEvent) => { - super.drop(e, de); - if (de.data instanceof DragManager.DocumentDragData) { - let screenX = de.x - (de.data.xOffset as number || 0); - let screenY = de.y - (de.data.yOffset as number || 0); - const [x, y] = this.getTransform().transformPoint(screenX, screenY); - de.data.droppedDocument.SetNumber(KeyStore.X, x); - de.data.droppedDocument.SetNumber(KeyStore.Y, y); - this.bringToFront(de.data.droppedDocument); + drop = (e: Event, de: DragManager.DropEvent): boolean => { + if (super.drop(e, de)) { + if (de.data instanceof DragManager.DocumentDragData) { + let screenX = de.x - (de.data.xOffset as number || 0); + let screenY = de.y - (de.data.yOffset as number || 0); + const [x, y] = this.getTransform().transformPoint(screenX, screenY); + de.data.droppedDocument.SetNumber(KeyStore.X, x); + de.data.droppedDocument.SetNumber(KeyStore.Y, y); + this.bringToFront(de.data.droppedDocument); + } + return true; } + return false; } diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx index e64b4c945..4d2daf149 100644 --- a/src/client/views/collections/CollectionPDFView.tsx +++ b/src/client/views/collections/CollectionPDFView.tsx @@ -38,7 +38,7 @@ export class CollectionPDFView extends React.Component { public SelectedDocs: FieldId[] = [] public active: () => boolean = () => CollectionView.Active(this); - addDocument = (doc: Document, allowDuplicates: boolean): void => { CollectionView.AddDocument(this.props, doc, allowDuplicates); } + addDocument = (doc: Document, allowDuplicates: boolean): boolean => { return CollectionView.AddDocument(this.props, doc, allowDuplicates); } removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); } specificContextMenu = (e: React.MouseEvent): void => { diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx index 05f759967..a3921696f 100644 --- a/src/client/views/collections/CollectionVideoView.tsx +++ b/src/client/views/collections/CollectionVideoView.tsx @@ -44,7 +44,7 @@ export class CollectionVideoView extends React.Component { public SelectedDocs: FieldId[] = [] public active: () => boolean = () => CollectionView.Active(this); - addDocument = (doc: Document, allowDuplicates: boolean): void => { CollectionView.AddDocument(this.props, doc, allowDuplicates); } + addDocument = (doc: Document, allowDuplicates: boolean): boolean => { return CollectionView.AddDocument(this.props, doc, allowDuplicates); } removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); } specificContextMenu = (e: React.MouseEvent): void => { diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 7e1d31018..2e93e6bb8 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -37,7 +37,7 @@ export class CollectionView extends React.Component { @observable public SelectedDocs: FieldId[] = []; public active: () => boolean = () => CollectionView.Active(this); - addDocument = (doc: Document, allowDuplicates: boolean): void => { CollectionView.AddDocument(this.props, doc, allowDuplicates); } + addDocument = (doc: Document, allowDuplicates: boolean): boolean => { return CollectionView.AddDocument(this.props, doc, allowDuplicates); } removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); } get subView() { return CollectionView.SubView(this); } @@ -48,17 +48,31 @@ export class CollectionView extends React.Component { return isSelected || childSelected || topMost; } + static createsCycle(doc: Document, props: CollectionViewProps): boolean { + let ldata = doc.GetList(KeyStore.Data, []); + for (let i = 0; i < ldata.length; i++) { + if (CollectionView.createsCycle(ldata[i], props)) + return true; + } + return doc.Id == props.Document.Id; + } + @action - public static AddDocument(props: CollectionViewProps, doc: Document, allowDuplicates: boolean) { + public static AddDocument(props: CollectionViewProps, doc: Document, allowDuplicates: boolean): boolean { doc.SetNumber(KeyStore.Page, props.Document.GetNumber(KeyStore.CurPage, -1)); if (props.Document.Get(props.fieldKey) instanceof Field) { //TODO This won't create the field if it doesn't already exist const value = props.Document.GetData(props.fieldKey, ListField, new Array()) - if (!value.some(v => v.Id == doc.Id) || allowDuplicates) - value.push(doc); + if (!CollectionView.createsCycle(doc, props)) { + if (!value.some(v => v.Id == doc.Id) || allowDuplicates) + value.push(doc); + } + else + return false; } else { props.Document.SetOnPrototype(props.fieldKey, new ListField([doc])); } + return true; } @action diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx index f33007196..535a8a8d2 100644 --- a/src/client/views/collections/CollectionViewBase.tsx +++ b/src/client/views/collections/CollectionViewBase.tsx @@ -17,6 +17,7 @@ import { NumberField } from "../../../fields/NumberField"; import { DocumentManager } from "../../util/DocumentManager"; import request = require("request"); import { ServerUtils } from "../../../server/ServerUtil"; +import { addHiddenFinalProp } from "mobx/lib/internal"; export interface CollectionViewProps { fieldKey: Key; @@ -33,7 +34,7 @@ export interface CollectionViewProps { export interface SubCollectionViewProps extends CollectionViewProps { active: () => boolean; - addDocument: (doc: Document, allowDuplicates: boolean) => void; + addDocument: (doc: Document, allowDuplicates: boolean) => boolean; removeDocument: (doc: Document) => boolean; CollectionView: CollectionView; } @@ -83,17 +84,20 @@ export class CollectionViewBase extends React.Component @undoBatch @action - protected drop(e: Event, de: DragManager.DropEvent) { + protected drop(e: Event, de: DragManager.DropEvent): boolean { if (de.data instanceof DragManager.DocumentDragData) { if (de.data.aliasOnDrop) { [KeyStore.Width, KeyStore.Height, KeyStore.CurPage].map(key => de.data.draggedDocument.GetTAsync(key, NumberField, (f: Opt) => f ? de.data.droppedDocument.SetNumber(key, f.Data) : null)); - } else if (de.data.removeDocument) { + } + let added = this.props.addDocument(de.data.droppedDocument, false); + if (added && de.data.removeDocument && !de.data.aliasOnDrop) { de.data.removeDocument(this.props.CollectionView); } - this.props.addDocument(de.data.droppedDocument, false); e.stopPropagation(); + return added; } + return false; } protected getDocumentFromType(type: string, path: string, options: DocumentOptions): Opt { diff --git a/src/client/views/collections/MarqueeView.tsx b/src/client/views/collections/MarqueeView.tsx index 8c2f3443c..b48ad9c64 100644 --- a/src/client/views/collections/MarqueeView.tsx +++ b/src/client/views/collections/MarqueeView.tsx @@ -17,7 +17,7 @@ interface MarqueeViewProps { getMarqueeTransform: () => Transform; getTransform: () => Transform; container: CollectionFreeFormView; - addDocument: (doc: Document, allowDuplicates: false) => void; + addDocument: (doc: Document, allowDuplicates: false) => boolean; activeDocuments: () => Document[]; selectDocuments: (docs: Document[]) => void; removeDocument: (doc: Document) => boolean; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index fec451b09..383341e02 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -23,7 +23,7 @@ import React = require("react"); export interface DocumentViewProps { ContainingCollectionView: Opt; Document: Document; - AddDocument?: (doc: Document, allowDuplicates: boolean) => void; + AddDocument?: (doc: Document, allowDuplicates: boolean) => boolean; RemoveDocument?: (doc: Document) => boolean; ScreenToLocalTransform: () => Transform; isTopMost: boolean; -- cgit v1.2.3-70-g09d2 From eb27dd447502ee1f55296b9e496aee998b0259f2 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 21 Mar 2019 23:10:42 -0400 Subject: avoided another cycle --- src/client/views/collections/CollectionView.tsx | 29 +++++++++++++++++++------ 1 file changed, 22 insertions(+), 7 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 2e93e6bb8..be757e22e 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -14,6 +14,7 @@ import { CollectionViewProps } from "./CollectionViewBase"; import { CollectionTreeView } from "./CollectionTreeView"; import { Field, FieldId, FieldWaiting } from "../../../fields/Field"; import { Main } from "../Main"; +import { dropPoint } from "prosemirror-transform"; export enum CollectionViewType { Invalid, @@ -48,13 +49,22 @@ export class CollectionView extends React.Component { return isSelected || childSelected || topMost; } - static createsCycle(doc: Document, props: CollectionViewProps): boolean { - let ldata = doc.GetList(KeyStore.Data, []); - for (let i = 0; i < ldata.length; i++) { - if (CollectionView.createsCycle(ldata[i], props)) + static createsCycle(documentToAdd: Document, containerDocument: Document): boolean { + let data = documentToAdd.GetList(KeyStore.Data, []); + for (let i = 0; i < data.length; i++) { + if (CollectionView.createsCycle(data[i], containerDocument)) return true; } - return doc.Id == props.Document.Id; + let annots = documentToAdd.GetList(KeyStore.Annotations, []); + for (let i = 0; i < annots.length; i++) { + if (CollectionView.createsCycle(annots[i], containerDocument)) + return true; + } + for (let containerProto: any = containerDocument; containerProto && containerProto != FieldWaiting; containerProto = containerProto.GetPrototype()) { + if (containerProto.Id == documentToAdd.Id) + return true; + } + return false; } @action @@ -63,14 +73,19 @@ export class CollectionView extends React.Component { if (props.Document.Get(props.fieldKey) instanceof Field) { //TODO This won't create the field if it doesn't already exist const value = props.Document.GetData(props.fieldKey, ListField, new Array()) - if (!CollectionView.createsCycle(doc, props)) { + if (!CollectionView.createsCycle(doc, props.Document)) { if (!value.some(v => v.Id == doc.Id) || allowDuplicates) value.push(doc); } else return false; } else { - props.Document.SetOnPrototype(props.fieldKey, new ListField([doc])); + let proto = props.Document.GetPrototype(); + if (!proto || proto == FieldWaiting || !CollectionView.createsCycle(proto, doc)) { + props.Document.SetOnPrototype(props.fieldKey, new ListField([doc])); + } + else + return false; } return true; } -- cgit v1.2.3-70-g09d2 From 3eefc8c7e901242ac6b7614bf1163858568d53b0 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Fri, 22 Mar 2019 05:19:37 -0400 Subject: Moved main doc id --- src/client/views/Main.tsx | 14 ++++++-------- src/client/views/collections/CollectionView.tsx | 4 ++-- src/server/authentication/models/current_user_utils.ts | 16 +++++++++++++--- 3 files changed, 21 insertions(+), 13 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index fdbad6ce1..cb49bc4e6 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -73,8 +73,6 @@ export class Main extends React.Component { return CurrentUserUtils.UserDocument; } - public mainDocId: string | undefined; - private currentUser?: DashUserModel; public static Instance: Main; constructor(props: Readonly<{}>) { @@ -85,7 +83,7 @@ export class Main extends React.Component { if (window.location.pathname !== RouteStore.home) { let pathname = window.location.pathname.split("/"); if (pathname.length > 1 && pathname[pathname.length - 2] == 'doc') { - this.mainDocId = pathname[pathname.length - 1]; + CurrentUserUtils.MainDocId = pathname[pathname.length - 1]; } }; @@ -117,8 +115,8 @@ export class Main extends React.Component { onHistory = () => { if (window.location.pathname !== RouteStore.home) { let pathname = window.location.pathname.split("/"); - this.mainDocId = pathname[pathname.length - 1]; - Server.GetField(this.mainDocId, action((field: Opt) => { + CurrentUserUtils.MainDocId = pathname[pathname.length - 1]; + Server.GetField(CurrentUserUtils.MainDocId, action((field: Opt) => { if (field instanceof Document) { this.openWorkspace(field, true); } @@ -148,7 +146,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 (!this.mainDocId) { + if (!CurrentUserUtils.MainDocId) { this.userDocument.GetTAsync(KeyStore.ActiveWorkspace, Document).then(doc => { if (doc) { this.openWorkspace(doc); @@ -157,11 +155,11 @@ export class Main extends React.Component { } }) } else { - Server.GetField(this.mainDocId).then(field => { + Server.GetField(CurrentUserUtils.MainDocId).then(field => { if (field instanceof Document) { this.openWorkspace(field) } else { - this.createNewWorkspace(this.mainDocId); + this.createNewWorkspace(CurrentUserUtils.MainDocId); } }) } diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 7e1d31018..c72633175 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -13,7 +13,7 @@ import { CollectionSchemaView } from "./CollectionSchemaView"; import { CollectionViewProps } from "./CollectionViewBase"; import { CollectionTreeView } from "./CollectionTreeView"; import { Field, FieldId, FieldWaiting } from "../../../fields/Field"; -import { Main } from "../Main"; +import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; export enum CollectionViewType { Invalid, @@ -96,7 +96,7 @@ export class CollectionView extends React.Component { } specificContextMenu = (e: React.MouseEvent): void => { - if (!e.isPropagationStopped() && this.props.Document.Id != Main.Instance.mainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 + if (!e.isPropagationStopped() && this.props.Document.Id != CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 ContextMenu.Instance.addItem({ description: "Freeform", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Freeform) }) ContextMenu.Instance.addItem({ description: "Schema", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Schema) }) ContextMenu.Instance.addItem({ description: "Treeview", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Tree) }) diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 17a6d493b..3291c671c 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -12,17 +12,27 @@ export class CurrentUserUtils { private static curr_email: string; private static curr_id: string; private static user_document: Document; + //TODO tfs: this should be temporary... + private static mainDocId: string | undefined; public static get email(): string { - return CurrentUserUtils.curr_email; + return this.curr_email; } public static get id(): string { - return CurrentUserUtils.curr_id; + return this.curr_id; } public static get UserDocument(): Document { - return CurrentUserUtils.user_document; + return this.user_document; + } + + public static get MainDocId(): string | undefined { + return this.mainDocId; + } + + public static set MainDocId(id: string | undefined) { + this.mainDocId = id; } private static createUserDocument(id: string): Document { -- cgit v1.2.3-70-g09d2 From 619c01891713b0ceb889ab45659b35f4b9fbc7e3 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Fri, 22 Mar 2019 07:15:22 -0400 Subject: Added basic fill down to schema view --- src/client/util/Scripting.ts | 2 +- src/client/util/type_decls.d | 7 ++++ src/client/views/EditableView.tsx | 11 +++++- .../views/collections/CollectionSchemaView.tsx | 43 ++++++++++++++-------- src/fields/Document.ts | 8 +++- 5 files changed, 51 insertions(+), 20 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index 46bd1a206..4cf98472f 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -110,7 +110,7 @@ export function CompileScript(script: string, scope?: { [name: string]: any }, a let host = new ScriptingCompilerHost; let funcScript = `(function() { ${addReturn ? `return ${script};` : script} - })()` + }).apply(this)` host.writeFile("file.ts", funcScript); host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib); let program = ts.createProgram(["file.ts"], {}, host); diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d index 679f73f42..dcf79285d 100644 --- a/src/client/util/type_decls.d +++ b/src/client/util/type_decls.d @@ -213,3 +213,10 @@ declare class Document extends Field { GetAllPrototypes(): Document[]; MakeDelegate(): Document; } + +declare const KeyStore: { + [name: string]: Key; +} + +// @ts-ignore +declare const console: any; diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index 579d6e6ad..29bf6add7 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -16,6 +16,8 @@ export interface EditableProps { * */ SetValue(value: string): boolean; + OnFillDown?(value: string): void; + /** * The contents to render when not editing */ @@ -36,8 +38,13 @@ export class EditableView extends React.Component { @action onKeyDown = (e: React.KeyboardEvent) => { - if (e.key == "Enter" && !e.ctrlKey) { - if (this.props.SetValue(e.currentTarget.value)) { + if (e.key == "Enter") { + if (!e.ctrlKey) { + if (this.props.SetValue(e.currentTarget.value)) { + this.editing = false; + } + } else if (this.props.OnFillDown) { + this.props.OnFillDown(e.currentTarget.value); this.editing = false; } } else if (e.key == "Escape") { diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 34b019244..8e0a38f05 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -85,12 +85,31 @@ export class CollectionSchemaView extends CollectionViewBase { ) let reference = React.createRef(); let onItemDown = setupDrag(reference, () => props.doc, (containingCollection: CollectionView) => this.props.removeDocument(props.doc)); + let applyToDoc = (doc: Document, value: string) => { + let script = CompileScript(value, { this: doc }, true); + if (!script.compiled) { + return false; + } + let field = script(); + if (field instanceof Field) { + doc.Set(props.fieldKey, field); + return true; + } else { + let dataField = ToField(field); + if (dataField) { + doc.Set(props.fieldKey, dataField); + return true; + } + } + return false; + } return (
{ + height={36} + GetValue={() => { let field = props.doc.Get(props.fieldKey); if (field && field instanceof Field) { return field.ToScriptString(); @@ -98,22 +117,14 @@ export class CollectionSchemaView extends CollectionViewBase { return field || ""; }} SetValue={(value: string) => { - let script = CompileScript(value); - if (!script.compiled) { - return false; - } - let field = script(); - if (field instanceof Field) { - props.doc.Set(props.fieldKey, field); - return true; - } else { - let dataField = ToField(field); - if (dataField) { - props.doc.Set(props.fieldKey, dataField); - return true; + return applyToDoc(props.doc, value); + }} + OnFillDown={(value: string) => { + this.props.Document.GetTAsync>(this.props.fieldKey, ListField).then((val) => { + if (val) { + val.Data.forEach(doc => applyToDoc(doc, value)); } - } - return false; + }) }}>
diff --git a/src/fields/Document.ts b/src/fields/Document.ts index fc4906d17..d098cc96d 100644 --- a/src/fields/Document.ts +++ b/src/fields/Document.ts @@ -132,7 +132,13 @@ export class Document extends Field { } else if (this._proxies.has(key.Id)) { Server.GetDocumentField(this, key, callback); } else { - callback(undefined); + this.GetTAsync(KeyStore.Prototype, Document, proto => { + if (proto) { + proto.GetAsync(key, callback); + } else { + callback(undefined); + } + }) } } -- cgit v1.2.3-70-g09d2 From 1238c172a2ac9fb7dfdee2588f141f2ae0c22b8e Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 22 Mar 2019 08:54:37 -0400 Subject: fixed schema scrolling --- src/client/views/collections/CollectionSchemaView.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 34b019244..696527049 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -238,6 +238,11 @@ export class CollectionSchemaView extends CollectionViewBase { } } + onWheel = (e: React.WheelEvent): void => { + if (this.props.active()) + e.stopPropagation(); + } + @observable _optionsActivated: number = 0; @action OptionsMenuDown = (e: React.PointerEvent) => { @@ -293,7 +298,7 @@ export class CollectionSchemaView extends CollectionViewBase { ); return ( -
+
this.onDrop(e, {})} ref={this.createDropTarget}> {({ measureRef }) => -- cgit v1.2.3-70-g09d2 From 356991c6100a44ef45b4574b43c815383d9be751 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Sat, 23 Mar 2019 00:22:52 -0400 Subject: Various fixes for the demo --- src/client/documents/Documents.ts | 17 ++++++--- src/client/util/Scripting.ts | 8 +++-- src/client/util/type_decls.d | 3 ++ .../views/collections/CollectionFreeFormView.tsx | 5 +++ .../views/collections/CollectionSchemaView.tsx | 42 ++++++++++++++++++---- .../views/collections/CollectionViewBase.tsx | 39 ++++++++++++++------ src/client/views/nodes/DocumentView.tsx | 7 ++++ src/client/views/nodes/FieldView.tsx | 18 +++++++++- src/client/views/nodes/FormattedTextBox.tsx | 2 ++ 9 files changed, 117 insertions(+), 24 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 96a7332aa..a2444db06 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1,6 +1,6 @@ import { AudioField } from "../../fields/AudioField"; import { Document } from "../../fields/Document"; -import { Field } from "../../fields/Field"; +import { Field, FieldWaiting } from "../../fields/Field"; import { HtmlField } from "../../fields/HtmlField"; import { ImageField } from "../../fields/ImageField"; import { InkField, StrokeData } from "../../fields/InkField"; @@ -23,6 +23,7 @@ import { PDFBox } from "../views/nodes/PDFBox"; import { VideoBox } from "../views/nodes/VideoBox"; import { WebBox } from "../views/nodes/WebBox"; import { HistogramBox } from "../views/nodes/HistogramBox"; +import { FieldView } from "../views/nodes/FieldView"; export interface DocumentOptions { x?: number; @@ -112,7 +113,7 @@ export namespace Documents { function GetImagePrototype(): Document { if (!imageProto) { imageProto = setupPrototypeOptions(imageProtoId, "IMAGE_PROTO", CollectionView.LayoutString("AnnotationsKey"), - { x: 0, y: 0, nativeWidth: 300, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations] }); + { x: 0, y: 0, nativeWidth: 300, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] }); imageProto.SetText(KeyStore.BackgroundLayout, ImageBox.LayoutString()); } return imageProto; @@ -155,7 +156,7 @@ export namespace Documents { function GetVideoPrototype(): Document { if (!videoProto) { videoProto = setupPrototypeOptions(videoProtoId, "VIDEO_PROTO", CollectionVideoView.LayoutString("AnnotationsKey"), - { x: 0, y: 0, nativeWidth: 600, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations] }); + { x: 0, y: 0, nativeWidth: 600, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] }); videoProto.SetNumber(KeyStore.CurPage, 0); videoProto.SetText(KeyStore.BackgroundLayout, VideoBox.LayoutString()); } @@ -218,6 +219,14 @@ export namespace Documents { return assignToDelegate(SetInstanceOptions(GetCollectionPrototype(), { ...options, viewType: CollectionViewType.Docking }, [config, TextField], id), options) } + export function CaptionDocument(doc: Document) { + const captionDoc = doc.CreateAlias(); + captionDoc.SetText(KeyStore.OverlayLayout, FixedCaption()); + captionDoc.SetNumber(KeyStore.Width, doc.GetNumber(KeyStore.Width, 0)); + captionDoc.SetNumber(KeyStore.Height, doc.GetNumber(KeyStore.Height, 0)); + return captionDoc; + } + // example of custom display string for an image that shows a caption. function EmbeddedCaption() { return `
@@ -228,7 +237,7 @@ export namespace Documents { + FormattedTextBox.LayoutString("CaptionKey") + `
` }; - function FixedCaption(fieldName: string = "Caption") { + export function FixedCaption(fieldName: string = "Caption") { return `
` + FormattedTextBox.LayoutString(fieldName + "Key") + diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index 4cf98472f..4e97b9401 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -15,6 +15,8 @@ import { ListField } from "../../fields/ListField"; // @ts-ignore import * as typescriptlib from '!!raw-loader!./type_decls.d' +import { Documents } from "../documents/Documents"; +import { Key } from "../../fields/Key"; export interface ExecutableScript { @@ -28,9 +30,9 @@ function Compile(script: string | undefined, diagnostics: Opt, scope: { [ let func: () => Opt; if (compiled && script) { - let fieldTypes = [Document, NumberField, TextField, ImageField, RichTextField, ListField]; - let paramNames = ["KeyStore", ...fieldTypes.map(fn => fn.name)]; - let params: any[] = [KeyStore, ...fieldTypes] + let fieldTypes = [Document, NumberField, TextField, ImageField, RichTextField, ListField, Key]; + let paramNames = ["KeyStore", "Documents", ...fieldTypes.map(fn => fn.name)]; + let params: any[] = [KeyStore, Documents, ...fieldTypes] for (let prop in scope) { if (prop === "this") { continue; diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d index dcf79285d..4f69053b1 100644 --- a/src/client/util/type_decls.d +++ b/src/client/util/type_decls.d @@ -174,6 +174,7 @@ declare class ListField extends BasicField{ Copy(): Field; } declare class Key extends Field { + constructor(name:string); Name: string; TrySetValue(value: any): boolean; GetValue(): any; @@ -220,3 +221,5 @@ declare const KeyStore: { // @ts-ignore declare const console: any; + +declare const Documents: any; diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx index da9f7b392..8f7b4cfe7 100644 --- a/src/client/views/collections/CollectionFreeFormView.tsx +++ b/src/client/views/collections/CollectionFreeFormView.tsx @@ -83,6 +83,10 @@ export class CollectionFreeFormView extends CollectionViewBase { const [x, y] = this.getTransform().transformPoint(screenX, screenY); de.data.droppedDocument.SetNumber(KeyStore.X, x); de.data.droppedDocument.SetNumber(KeyStore.Y, y); + if (!de.data.droppedDocument.GetNumber(KeyStore.Width, 0)) { + de.data.droppedDocument.SetNumber(KeyStore.Width, 300); + de.data.droppedDocument.SetNumber(KeyStore.Height, 300); + } this.bringToFront(de.data.droppedDocument); } } @@ -259,6 +263,7 @@ export class CollectionFreeFormView extends CollectionViewBase { const lvalue = this.props.Document.GetT>(this.props.fieldKey, ListField); if (lvalue && lvalue != FieldWaiting) { return lvalue.Data.map(doc => { + if (!doc) return null; var page = doc.GetNumber(KeyStore.Page, 0); return (page != curPage && page != 0) ? (null) : (); diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 8e0a38f05..7dd364449 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -1,6 +1,6 @@ import React = require("react") import { library } from '@fortawesome/fontawesome-svg-core'; -import { faCog } from '@fortawesome/free-solid-svg-icons'; +import { faCog, faPlus } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable, trace, untracked } from "mobx"; import { observer } from "mobx-react"; @@ -8,7 +8,7 @@ import Measure from "react-measure"; import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults } from "react-table"; import "react-table/react-table.css"; import { Document } from "../../../fields/Document"; -import { Field, Opt } from "../../../fields/Field"; +import { Field, Opt, FieldWaiting } from "../../../fields/Field"; import { Key } from "../../../fields/Key"; import { KeyStore } from "../../../fields/KeyStore"; import { ListField } from "../../../fields/ListField"; @@ -24,6 +24,7 @@ import { FieldView, FieldViewProps } from "../nodes/FieldView"; import "./CollectionSchemaView.scss"; import { CollectionView, COLLECTION_BORDER_WIDTH } from "./CollectionView"; import { CollectionViewBase } from "./CollectionViewBase"; +import { TextField } from "../../../fields/TextField"; // bcz: need to add drag and drop of rows and columns. This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657 @@ -104,11 +105,11 @@ export class CollectionSchemaView extends CollectionViewBase { return false; } return ( -
+
{ let field = props.doc.Get(props.fieldKey); if (field && field instanceof Field) { @@ -249,23 +250,48 @@ export class CollectionSchemaView extends CollectionViewBase { } } + @action + addColumn = () => { + this.columns.push(new Key(this.newKeyName)); + this.newKeyName = ""; + } + + @observable + newKeyName: string = ""; + + @action + newKeyChange = (e: React.ChangeEvent) => { + this.newKeyName = e.currentTarget.value; + } + @observable _optionsActivated: number = 0; @action OptionsMenuDown = (e: React.PointerEvent) => { this._optionsActivated++; } + + @observable previewScript: string = "this"; + @action + onPreviewScriptChange = (e: React.ChangeEvent) => { + this.previewScript = e.currentTarget.value; + } + render() { library.add(faCog); + library.add(faPlus); const columns = this.columns; const children = this.props.Document.GetList(this.props.fieldKey, []); const selected = children.length > this._selectedIndex ? children[this._selectedIndex] : undefined; //all the keys/columns that will be displayed in the schema const allKeys = this.findAllDocumentKeys; + let doc: any = selected ? selected.Get(new Key(this.previewScript)) : undefined; + + // let doc = CompileScript(this.previewScript, { this: selected }, true)(); let content = this._selectedIndex == -1 || !selected ? (null) : ( {({ measureRef }) =>
- + /> : null} +
}
@@ -297,6 +325,8 @@ export class CollectionSchemaView extends CollectionViewBase { return () })} + +
}> diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx index f33007196..7d903899d 100644 --- a/src/client/views/collections/CollectionViewBase.tsx +++ b/src/client/views/collections/CollectionViewBase.tsx @@ -17,6 +17,7 @@ import { NumberField } from "../../../fields/NumberField"; import { DocumentManager } from "../../util/DocumentManager"; import request = require("request"); import { ServerUtils } from "../../../server/ServerUtil"; +import { Server } from "../../Server"; export interface CollectionViewProps { fieldKey: Key; @@ -59,17 +60,20 @@ export class CollectionViewBase extends React.Component let email = CurrentUserUtils.email; if (id && email) { let textInfo: [string, string] = [id, email]; - doc.GetOrCreateAsync>(KeyStore.Cursors, ListField, field => { - let cursors = field.Data; - if (cursors.length > 0 && (ind = cursors.findIndex(entry => entry.Data[0][0] === id)) > -1) { - cursors[ind].Data[1] = position; - } else { - let entry = new TupleField<[string, string], [number, number]>([textInfo, position]); - cursors.push(entry); + doc.GetTAsync(KeyStore.Prototype, Document).then(proto => { + if (!proto) { + return; } + proto.GetOrCreateAsync>(KeyStore.Cursors, ListField, action((field: ListField) => { + let cursors = field.Data; + if (cursors.length > 0 && (ind = cursors.findIndex(entry => entry.Data[0][0] === id)) > -1) { + cursors[ind].Data[1] = position; + } else { + let entry = new TupleField<[string, string], [number, number]>([textInfo, position]); + cursors.push(entry); + } + })) }) - - } } @@ -111,6 +115,21 @@ export class CollectionViewBase extends React.Component ctor = Documents.PdfDocument; } if (type.indexOf("html") !== -1) { + if (path.includes('localhost')) { + let s = path.split('/'); + let id = s[s.length - 1]; + Server.GetField(id).then(field => { + if (field instanceof Document) { + let alias = field.CreateAlias(); + alias.SetNumber(KeyStore.X, options.x || 0); + alias.SetNumber(KeyStore.Y, options.y || 0); + alias.SetNumber(KeyStore.Width, options.width || 300); + alias.SetNumber(KeyStore.Height, options.height || options.width || 300); + this.props.addDocument(alias, false); + } + }) + return undefined; + } ctor = Documents.WebDocument; options = { height: options.width, ...options, }; } @@ -130,7 +149,7 @@ export class CollectionViewBase extends React.Component e.stopPropagation() e.preventDefault() - if (html && html.indexOf(" { ContextMenu.Instance.addItem({ description: "Fields", event: this.fieldsClicked }) ContextMenu.Instance.addItem({ description: "Center", event: () => this.props.focus(this.props.Document) }) ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document) }) + ContextMenu.Instance.addItem({ + description: "Copy URL", + event: () => { + Utils.CopyText(ServerUtils.prepend("/doc/" + this.props.Document.Id)); + } + }); ContextMenu.Instance.addItem({ description: "Copy ID", event: () => { diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index f6343c631..4e83ec7b9 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -16,6 +16,9 @@ import { VideoBox } from "./VideoBox"; import { AudioBox } from "./AudioBox"; import { AudioField } from "../../../fields/AudioField"; import { ListField } from "../../../fields/ListField"; +import { DocumentContentsView } from "./DocumentContentsView"; +import { Transform } from "../../util/Transform"; +import { KeyStore } from "../../../fields/KeyStore"; // @@ -65,7 +68,20 @@ export class FieldView extends React.Component { return } else if (field instanceof Document) { - return
{field.Title}
+ return ( Transform.Identity} + ContentScaling={() => 1} + PanelWidth={() => 100} + PanelHeight={() => 100} + isTopMost={true} + SelectOnLoad={false} + focus={() => { }} + isSelected={() => false} + select={() => false} + layoutKey={KeyStore.Layout} + ContainingCollectionView={undefined} />) } else if (field instanceof ListField) { return (
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index ba9bd9566..30fa1342e 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -167,6 +167,8 @@ export class FormattedTextBox extends React.Component { onKeyPress={this.onKeyPress} onPointerDown={this.onPointerDown} onContextMenu={this.specificContextMenu} + // tfs: do we need this event handler + onKeyDown={this.onKeyPress} onWheel={this.onPointerWheel} ref={this._ref} />) } -- cgit v1.2.3-70-g09d2 From 2264fb874a09ee01540141986325c76650f32c21 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 25 Mar 2019 23:20:22 -0400 Subject: added 1-level navigation to region annotations --- .../views/collections/CollectionDockingView.tsx | 2 +- .../views/collections/CollectionVideoView.tsx | 108 ++++++++++++--------- src/client/views/collections/CollectionView.tsx | 6 +- src/client/views/nodes/LinkBox.tsx | 21 +++- src/client/views/nodes/PDFBox.tsx | 21 ++-- src/client/views/nodes/VideoBox.tsx | 29 ++++-- src/fields/KeyStore.ts | 2 + 7 files changed, 120 insertions(+), 69 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 39b284d8e..d4f510f5d 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -191,7 +191,7 @@ export class CollectionDockingView extends React.Component { var className = (e.target as any).className; - if ((className == "lm_title" || className == "lm_tab lm_active") && e.ctrlKey) { + if ((className == "lm_title" || className == "lm_tab lm_active") && (e.ctrlKey || e.altKey)) { e.stopPropagation(); e.preventDefault(); let docid = (e.target as any).DashDocId; diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx index a3921696f..470a853e3 100644 --- a/src/client/views/collections/CollectionVideoView.tsx +++ b/src/client/views/collections/CollectionVideoView.tsx @@ -1,4 +1,4 @@ -import { action, computed, observable } from "mobx"; +import { action, computed, observable, trace } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../fields/Document"; import { KeyStore } from "../../../fields/KeyStore"; @@ -12,24 +12,26 @@ import "./CollectionVideoView.scss" @observer export class CollectionVideoView extends React.Component { + private _intervalTimer: any = undefined; + private _player: HTMLVideoElement | undefined = undefined; + + @observable _currentTimecode: number = 0; + @observable _isPlaying: boolean = false; public static LayoutString(fieldKey: string = "DataKey") { return `<${CollectionVideoView.name} Document={Document} ScreenToLocalTransform={ScreenToLocalTransform} fieldKey={${fieldKey}} panelWidth={PanelWidth} panelHeight={PanelHeight} isSelected={isSelected} select={select} bindings={bindings} isTopMost={isTopMost} SelectOnLoad={selectOnLoad} BackgroundView={BackgroundView} focus={focus}/>`; } - - private _mainCont = React.createRef(); - private get uIButtons() { let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().transformDirection(1, 1)[0]); return ([
- {"" + Math.round(this.ctime)} - {" " + Math.round((this.ctime - Math.trunc(this.ctime)) * 100)} + {"" + Math.round(this._currentTimecode)} + {" " + Math.round((this._currentTimecode - Math.trunc(this._currentTimecode)) * 100)}
,
- {this.playing ? "\"" : ">"} + {this._isPlaying ? "\"" : ">"}
,
F @@ -37,64 +39,54 @@ export class CollectionVideoView extends React.Component { ]); } - - // "inherited" CollectionView API starts here... - - @observable - public SelectedDocs: FieldId[] = [] - public active: () => boolean = () => CollectionView.Active(this); - - addDocument = (doc: Document, allowDuplicates: boolean): boolean => { return CollectionView.AddDocument(this.props, doc, allowDuplicates); } - removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); } - - specificContextMenu = (e: React.MouseEvent): void => { - if (!e.isPropagationStopped() && this.props.Document.Id != "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 - ContextMenu.Instance.addItem({ description: "VideoOptions", event: () => { } }); + @action + mainCont = (ele: HTMLDivElement | null) => { + if (ele) { + this._player = ele!.getElementsByTagName("video")[0]; + if (this.props.Document.GetNumber(KeyStore.CurPage, -1) >= 0) { + this._currentTimecode = this.props.Document.GetNumber(KeyStore.CurPage, -1); + } } } - get collectionViewType(): CollectionViewType { return CollectionViewType.Freeform; } - get subView(): any { return CollectionView.SubView(this); } - componentDidMount() { - this.updateTimecode(); + this._intervalTimer = setInterval(this.updateTimecode, 1000); } - get player(): HTMLVideoElement | undefined { - return this._mainCont.current ? this._mainCont.current.getElementsByTagName("video")[0] : undefined; + componentWillUnmount() { + clearInterval(this._intervalTimer); } @action updateTimecode = () => { - if (this.player) { - this.ctime = this.player.currentTime; - this.props.Document.SetNumber(KeyStore.CurPage, Math.round(this.ctime)); + if (this._player) { + if ((this._player as any).AHackBecauseSomethingResetsTheVideoToZero != -1) { + this._player.currentTime = (this._player as any).AHackBecauseSomethingResetsTheVideoToZero; + (this._player as any).AHackBecauseSomethingResetsTheVideoToZero = -1; + } else { + this._currentTimecode = this._player.currentTime; + this.props.Document.SetNumber(KeyStore.CurPage, Math.round(this._currentTimecode)); + } } - setTimeout(() => this.updateTimecode(), 100) } - - @observable - ctime: number = 0 - @observable - playing: boolean = false; - @action onPlayDown = () => { - if (this.player) { - if (this.player.paused) { - this.player.play(); - this.playing = true; + if (this._player) { + if (this._player.paused) { + this._player.play(); + this._isPlaying = true; } else { - this.player.pause(); - this.playing = false; + this._player.pause(); + this._isPlaying = false; } } } + @action onFullDown = (e: React.PointerEvent) => { - if (this.player) { - this.player.requestFullscreen(); + if (this._player) { + this._player.requestFullscreen(); e.stopPropagation(); e.preventDefault(); } @@ -102,15 +94,35 @@ export class CollectionVideoView extends React.Component { @action onResetDown = () => { - if (this.player) { - this.player.pause(); - this.player.currentTime = 0; + if (this._player) { + this._player.pause(); + this._player.currentTime = 0; } } + // "inherited" CollectionView API starts here... + + @observable + public SelectedDocs: FieldId[] = [] + public active: () => boolean = () => CollectionView.Active(this); + + addDocument = (doc: Document, allowDuplicates: boolean): boolean => { return CollectionView.AddDocument(this.props, doc, allowDuplicates); } + removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); } + + specificContextMenu = (e: React.MouseEvent): void => { + if (!e.isPropagationStopped() && this.props.Document.Id != "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 + ContextMenu.Instance.addItem({ description: "VideoOptions", event: () => { } }); + } + } + + get collectionViewType(): CollectionViewType { return CollectionViewType.Freeform; } + get subView(): any { return CollectionView.SubView(this); } + + render() { - return (
+ trace(); + return (
{this.subView} {this.props.isSelected() ? this.uIButtons : (null)}
) diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index a740865ad..2fa2c9086 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -68,7 +68,11 @@ export class CollectionView extends React.Component { @action public static AddDocument(props: CollectionViewProps, doc: Document, allowDuplicates: boolean): boolean { - doc.SetNumber(KeyStore.Page, props.Document.GetNumber(KeyStore.CurPage, -1)); + var curPage = props.Document.GetNumber(KeyStore.CurPage, -1); + doc.SetNumber(KeyStore.Page, curPage); + if (curPage > 0) { + doc.Set(KeyStore.AnnotationOn, props.Document); + } if (props.Document.Get(props.fieldKey) instanceof Field) { //TODO This won't create the field if it doesn't already exist const value = props.Document.GetData(props.fieldKey, ListField, new Array()) diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index dd2f71b59..638d3b5a7 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -17,6 +17,8 @@ import { faEye } from '@fortawesome/free-solid-svg-icons'; import { faEdit } from '@fortawesome/free-solid-svg-icons'; import { faTimes } from '@fortawesome/free-solid-svg-icons'; import { undoBatch } from "../../util/UndoManager"; +import { FieldWaiting } from "../../../fields/Field"; +import { NumberField } from "../../../fields/NumberField"; library.add(faEye); @@ -41,7 +43,24 @@ export class LinkBox extends React.Component { if (docView) { docView.props.focus(this.props.pairedDoc); } else { - CollectionDockingView.Instance.AddRightSplit(this.props.pairedDoc) + this.props.pairedDoc.GetAsync(KeyStore.AnnotationOn, (contextDoc: any) => { + if (!contextDoc) { + CollectionDockingView.Instance.AddRightSplit(this.props.pairedDoc); + } else if (contextDoc instanceof Document) { + this.props.pairedDoc.GetTAsync(KeyStore.Page, NumberField).then((pfield: any) => { + contextDoc.GetTAsync(KeyStore.CurPage, NumberField).then((cfield: any) => { + if (pfield != cfield) + contextDoc.SetNumber(KeyStore.CurPage, pfield.Data); + let contextView = DocumentManager.Instance.getDocumentView(contextDoc); + if (contextView) { + contextView.props.focus(contextDoc); + } else { + CollectionDockingView.Instance.AddRightSplit(contextDoc); + } + }) + }); + } + }); } } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 3a0ef2d32..e273b0b4f 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -1,5 +1,5 @@ import * as htmlToImage from "html-to-image"; -import { action, computed, observable, reaction, IReactionDisposer, trace } from 'mobx'; +import { action, computed, observable, reaction, IReactionDisposer, trace, keys } from 'mobx'; import { observer } from "mobx-react"; import 'react-image-lightbox/style.css'; import Measure from "react-measure"; @@ -18,6 +18,7 @@ import "./PDFBox.scss"; import { Sticky } from './Sticky'; //you should look at sticky and annotation, because they are used here import React = require("react") import { RouteStore } from "../../../server/RouteStore"; +import { NumberField } from "../../../fields/NumberField"; /** ALSO LOOK AT: Annotation.tsx, Sticky.tsx * This method renders PDF and puts all kinds of functionalities such as annotation, highlighting, @@ -60,7 +61,6 @@ export class PDFBox extends React.Component { //very useful for keeping track of X and y position throughout the PDF Canvas private initX: number = 0; private initY: number = 0; - private initPage: boolean = false; //checks if tool is on private _toolOn: boolean = false; //checks if tool is on @@ -88,17 +88,15 @@ export class PDFBox extends React.Component { @observable private _loaded: boolean = false; @computed private get curPage() { return this.props.doc.GetNumber(KeyStore.CurPage, -1); } + @computed private get thumbnailPage() { return this.props.doc.GetNumber(KeyStore.ThumbnailPage, -1); } componentDidMount() { this._reactionDisposer = reaction( - () => this.curPage, + () => [this.curPage, this.thumbnailPage], () => { - if (this.curPage && this.initPage) { + if (this.curPage > 0 && this.thumbnailPage > 0 && this.curPage != this.thumbnailPage) { this.saveThumbnail(); this._interactive = true; - } else { - if (this.curPage > 0) - this.initPage = true; } }, { fireImmediately: true }); @@ -384,6 +382,7 @@ export class PDFBox extends React.Component { { width: me.props.doc.GetNumber(KeyStore.NativeWidth, 0), height: me.props.doc.GetNumber(KeyStore.NativeHeight, 0), quality: 0.5 }) .then(function (dataUrl: string) { me.props.doc.SetData(KeyStore.Thumbnail, new URL(dataUrl), ImageField); + me.props.doc.SetNumber(KeyStore.ThumbnailPage, me.props.doc.GetNumber(KeyStore.CurPage, -1)); }) .catch(function (error: any) { console.error('oops, something went wrong!', error); @@ -473,10 +472,10 @@ export class PDFBox extends React.Component { @computed get imageProxyRenderer() { - let field = this.props.doc.Get(KeyStore.Thumbnail); - if (field) { - let path = field == FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" : - field instanceof ImageField ? field.Data.href : "http://cs.brown.edu/people/bcz/prairie.jpg"; + let thumbField = this.props.doc.Get(KeyStore.Thumbnail); + if (thumbField) { + let path = thumbField == FieldWaiting || this.thumbnailPage != this.curPage ? "https://image.flaticon.com/icons/svg/66/66163.svg" : + thumbField instanceof ImageField ? thumbField.Data.href : "http://cs.brown.edu/people/bcz/prairie.jpg"; return ; } return (null); diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 09ae95183..7c0db83a8 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -1,23 +1,27 @@ import React = require("react") import { observer } from "mobx-react"; -import { FieldWaiting } from '../../../fields/Field'; +import { FieldWaiting, Opt } from '../../../fields/Field'; import { VideoField } from '../../../fields/VideoField'; import { FieldView, FieldViewProps } from './FieldView'; import "./VideoBox.scss"; import Measure from "react-measure"; -import { action, trace, observable } from "mobx"; +import { action, trace, observable, IReactionDisposer, computed, reaction } from "mobx"; import { KeyStore } from "../../../fields/KeyStore"; import { number } from "prop-types"; @observer export class VideoBox extends React.Component { + private _reactionDisposer: Opt; + private _videoRef = React.createRef() public static LayoutString() { return FieldView.LayoutString(VideoBox) } constructor(props: FieldViewProps) { super(props); } + @computed private get curPage() { return this.props.doc.GetNumber(KeyStore.CurPage, -1); } + _loaded: boolean = false; @@ -39,7 +43,17 @@ export class VideoBox extends React.Component { } } + get player(): HTMLVideoElement | undefined { + return this._videoRef.current ? this._videoRef.current.getElementsByTagName("video")[0] : undefined; + } + @action + setVideoRef = (vref: HTMLVideoElement | null) => { + if (this.curPage >= 0 && vref) { + vref!.currentTime = this.curPage; + (vref! as any).AHackBecauseSomethingResetsTheVideoToZero = this.curPage; + } + } render() { let field = this.props.doc.GetT(this.props.fieldKey, VideoField); @@ -47,15 +61,16 @@ export class VideoBox extends React.Component { return
Loading
} let path = field.Data.href; - - //setTimeout(action(() => this._loaded = true), 500); + trace(); return ( {({ measureRef }) => - ) diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts index 611e2951b..f9684b212 100644 --- a/src/fields/KeyStore.ts +++ b/src/fields/KeyStore.ts @@ -36,7 +36,9 @@ export namespace KeyStore { export const LinkDescription = new Key("LinkDescription"); export const LinkTags = new Key("LinkTag"); export const Thumbnail = new Key("Thumbnail"); + export const ThumbnailPage = new Key("ThumbnailPage"); export const CurPage = new Key("CurPage"); + export const AnnotationOn = new Key("AnnotationOn"); export const NumPages = new Key("NumPages"); export const Ink = new Key("Ink"); export const Cursors = new Key("Cursors"); -- cgit v1.2.3-70-g09d2 From 970ecdb7158340cec2ea9e9a381d86afa8a59430 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 26 Mar 2019 00:02:30 -0400 Subject: tried to fix keyboard event issue when editing a text note in an embedded free form collection. --- src/client/views/collections/PreviewCursor.tsx | 9 ++++++--- src/client/views/nodes/FormattedTextBox.tsx | 6 ++++-- 2 files changed, 10 insertions(+), 5 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/PreviewCursor.tsx b/src/client/views/collections/PreviewCursor.tsx index cbf36cf9e..5d621f321 100644 --- a/src/client/views/collections/PreviewCursor.tsx +++ b/src/client/views/collections/PreviewCursor.tsx @@ -37,13 +37,13 @@ export class PreviewCursor extends React.Component { @action cleanupInteractions = () => { - document.removeEventListener("keypress", this.onKeyPress, true); + document.removeEventListener("keypress", this.onKeyPress, false); } @action onCursorPlaced = (visible: boolean, downX: number, downY: number): void => { if (visible) { - document.addEventListener("keypress", this.onKeyPress, true); + document.addEventListener("keypress", this.onKeyPress, false); this._lastX = downX; this._lastY = downY; } else @@ -52,8 +52,11 @@ export class PreviewCursor extends React.Component { @action onKeyPress = (e: KeyboardEvent) => { + // Mixing events between React and Native is finicky. In FormattedTextBox, we set the + // DASHFormattedTextBoxHandled flag when a text box consumes a key press so that we can ignore + // the keyPress here. //if not these keys, make a textbox if preview cursor is active! - if (!e.ctrlKey && !e.altKey && !e.defaultPrevented) { + if (!e.ctrlKey && !e.altKey && !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) { //make textbox and add it to this collection let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY); let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "new" }); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 30fa1342e..512ad7d70 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -158,17 +158,19 @@ export class FormattedTextBox extends React.Component { } }) } - onKeyPress(e: React.KeyboardEvent) { e.stopPropagation(); + // stop propagation doesn't seem to stop propagation of native keyboard events. + // so we set a flag on the native event that marks that the event's been handled. + // (e.nativeEvent as any).DASHFormattedTextBoxHandled = true; } render() { return (
) } -- cgit v1.2.3-70-g09d2 From 9f585c4f0a8287951f142f99dcc54861cb348728 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 26 Mar 2019 16:09:27 -0400 Subject: treeview scrolling. --- src/client/views/collections/CollectionTreeView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 6cc14ebcb..70790af18 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -125,7 +125,7 @@ export class CollectionTreeView extends CollectionViewBase { ) return ( -
this.onDrop(e, {})} ref={this.createDropTarget} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}> +
e.stopPropagation()} onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}>
Date: Tue, 26 Mar 2019 16:44:53 -0400 Subject: fixed histos in docking pane and not selecting bars when dragging. --- src/client/views/collections/CollectionFreeFormView.tsx | 4 ++-- src/client/views/nodes/DocumentContentsView.tsx | 1 + src/client/views/nodes/HistogramBox.tsx | 4 +++- src/client/views/nodes/HistogramBoxPrimitives.tsx | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx index 53fe969fe..1eab3e475 100644 --- a/src/client/views/collections/CollectionFreeFormView.tsx +++ b/src/client/views/collections/CollectionFreeFormView.tsx @@ -279,13 +279,13 @@ export class CollectionFreeFormView extends CollectionViewBase { get backgroundView() { return !this.backgroundLayout ? (null) : ( false} select={() => { }} />); + layoutKey={KeyStore.BackgroundLayout} isTopMost={this.props.isTopMost} isSelected={() => false} select={() => { }} />); } @computed get overlayView() { return !this.overlayLayout ? (null) : ( false} select={() => { }} />); + layoutKey={KeyStore.OverlayLayout} isTopMost={this.props.isTopMost} isSelected={() => false} select={() => { }} />); } getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform()) diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 40990b76a..f48be2047 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -35,6 +35,7 @@ export class DocumentContentsView extends React.Component()); } @computed get layoutFields(): Key[] { return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array()); } + CreateBindings(): JsxBindings { let bindings: JsxBindings = { ...this.props, }; for (const key of this.layoutKeys) { diff --git a/src/client/views/nodes/HistogramBox.tsx b/src/client/views/nodes/HistogramBox.tsx index 20a022227..4d7922c1b 100644 --- a/src/client/views/nodes/HistogramBox.tsx +++ b/src/client/views/nodes/HistogramBox.tsx @@ -150,10 +150,12 @@ export class HistogramBox extends React.Component { let label = this.HistoOp && this.HistoOp.X ? this.HistoOp.X.AttributeModel.DisplayName : "<...>"; let xaxislines = this.xaxislines; let yaxislines = this.yaxislines; + var h = this.props.isTopMost ? this._panelHeight : this.props.doc.GetNumber(KeyStore.Height, 0); + var w = this.props.isTopMost ? this._panelWidth : this.props.doc.GetNumber(KeyStore.Width, 0); return ( runInAction(() => { this._panelWidth = r.entry.width; this._panelHeight = r.entry.height })}> {({ measureRef }) => -
+
{xaxislines} {yaxislines} diff --git a/src/client/views/nodes/HistogramBoxPrimitives.tsx b/src/client/views/nodes/HistogramBoxPrimitives.tsx index 2f4a553b7..a8ada99a4 100644 --- a/src/client/views/nodes/HistogramBoxPrimitives.tsx +++ b/src/client/views/nodes/HistogramBoxPrimitives.tsx @@ -79,7 +79,7 @@ export class HistogramBoxPrimitives extends React.Component void) { - return
{ if (e.button == 0) tapHandler() }} style={{ position: "absolute", transform: `translate(${r.x}px,${r.y}px)`, -- cgit v1.2.3-70-g09d2 From 4704f088af92a8c04a148861c547afa54a276761 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 27 Mar 2019 19:33:56 -0400 Subject: fixed linking to work with delegates. fixed full screen option and tab dragging bugs. --- src/client/util/DocumentManager.ts | 8 +++++++- .../views/collections/CollectionDockingView.tsx | 22 ++++++++++++---------- .../views/collections/CollectionViewBase.tsx | 2 +- src/client/views/collections/PreviewCursor.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 21 +++++++++++++-------- src/client/views/nodes/LinkBox.tsx | 2 +- src/fields/Document.ts | 14 ++++++++++++-- 7 files changed, 47 insertions(+), 24 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 5b99b4ef8..03df11ad7 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -3,6 +3,8 @@ import { observer } from 'mobx-react'; import { observable, action } from 'mobx'; import { Document } from "../../fields/Document" import { DocumentView } from '../views/nodes/DocumentView'; +import { KeyStore } from '../../fields/KeyStore'; +import { FieldWaiting } from '../../fields/Field'; export class DocumentManager { @@ -39,11 +41,15 @@ export class DocumentManager { // } // } + if (Object.is(doc, toFind)) { toReturn = view; return; } - + let docSrc = doc.GetT(KeyStore.Prototype, Document); + if (docSrc && docSrc != FieldWaiting && Object.is(docSrc, toFind)) { + toReturn = view; + } }) return (toReturn); diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index d4f510f5d..b77af8cd6 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -196,15 +196,16 @@ export class CollectionDockingView extends React.Component) => - DragManager.StartDocumentDrag(tab, new DragManager.DocumentDragData(f as Document), - { - handlers: { - dragComplete: action(() => { }), - }, - hideSource: false - })) - ); + Server.GetField(docid, action((f: Opt) => { + if (f instanceof Document) + DragManager.StartDocumentDrag(tab, new DragManager.DocumentDragData(f as Document), + { + handlers: { + dragComplete: action(() => { }), + }, + hideSource: false + }) + })); } if (className == "lm_drag_handle" || className == "lm_close" || className == "lm_maximise" || className == "lm_minimise" || className == "lm_close_tab") { this._flush = true; @@ -224,7 +225,8 @@ export class CollectionDockingView extends React.Component { - tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId; + if (tab.hasOwnProperty("contentItem") && tab.contentItem.config.type != "stack") + tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId; tab.closeElement.off('click') //unbind the current click handler .click(function () { tab.contentItem.remove(); diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx index adaf810ea..48adce4d8 100644 --- a/src/client/views/collections/CollectionViewBase.tsx +++ b/src/client/views/collections/CollectionViewBase.tsx @@ -133,7 +133,7 @@ export class CollectionViewBase extends React.Component return undefined; } ctor = Documents.WebDocument; - options = { height: options.width, ...options, }; + options = { height: options.width, ...options, title: path }; } return ctor ? ctor(path, options) : undefined; } diff --git a/src/client/views/collections/PreviewCursor.tsx b/src/client/views/collections/PreviewCursor.tsx index 5d621f321..8ae59a83a 100644 --- a/src/client/views/collections/PreviewCursor.tsx +++ b/src/client/views/collections/PreviewCursor.tsx @@ -59,7 +59,7 @@ export class PreviewCursor extends React.Component { if (!e.ctrlKey && !e.altKey && !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) { //make textbox and add it to this collection let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY); - let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "new" }); + let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "typed text" }); this.props.addLiveTextDocument(newBox); e.stopPropagation(); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 3873a6f89..c31e8b8c4 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -243,15 +243,20 @@ export class DocumentView extends React.Component { } let linkDoc: Document = new Document(); - linkDoc.Set(KeyStore.Title, new TextField("New Link")); - linkDoc.Set(KeyStore.LinkDescription, new TextField("")); - linkDoc.Set(KeyStore.LinkTags, new TextField("Default")); - - sourceDoc.GetOrCreateAsync(KeyStore.LinkedToDocs, ListField, field => { (field as ListField).Data.push(linkDoc) }); - linkDoc.Set(KeyStore.LinkedToDocs, destDoc); - destDoc.GetOrCreateAsync(KeyStore.LinkedFromDocs, ListField, field => { (field as ListField).Data.push(linkDoc) }); - linkDoc.Set(KeyStore.LinkedFromDocs, sourceDoc); + destDoc.GetTAsync(KeyStore.Prototype, Document).then((protoDest) => + sourceDoc.GetTAsync(KeyStore.Prototype, Document).then((protoSrc) => runInAction(() => { + let dstTarg = (protoDest ? protoDest : destDoc); + let srcTarg = (protoSrc ? protoSrc : sourceDoc); + linkDoc.Set(KeyStore.Title, new TextField("New Link")); + linkDoc.Set(KeyStore.LinkDescription, new TextField("")); + linkDoc.Set(KeyStore.LinkTags, new TextField("Default")); + linkDoc.Set(KeyStore.LinkedToDocs, dstTarg); + linkDoc.Set(KeyStore.LinkedFromDocs, srcTarg); + dstTarg.GetOrCreateAsync(KeyStore.LinkedFromDocs, ListField, field => { (field as ListField).Data.push(linkDoc) }) + srcTarg.GetOrCreateAsync(KeyStore.LinkedToDocs, ListField, field => { (field as ListField).Data.push(linkDoc) }) + })) + ) e.stopPropagation(); } } diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 638d3b5a7..457fecee3 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -41,7 +41,7 @@ export class LinkBox extends React.Component { e.stopPropagation(); let docView = DocumentManager.Instance.getDocumentView(this.props.pairedDoc); if (docView) { - docView.props.focus(this.props.pairedDoc); + docView.props.focus(docView.props.Document); } else { this.props.pairedDoc.GetAsync(KeyStore.AnnotationOn, (contextDoc: any) => { if (!contextDoc) { diff --git a/src/fields/Document.ts b/src/fields/Document.ts index cd393d676..e9192f267 100644 --- a/src/fields/Document.ts +++ b/src/fields/Document.ts @@ -35,8 +35,18 @@ export class Document extends Field { public Scale = () => { return this.GetNumber(KeyStore.Scale, 1) } @computed - public get Title() { - return this.GetText(KeyStore.Title, "-untitled-"); + public get Title(): string { + let title = this.Get(KeyStore.Title, true); + if (title) + if (title != FieldWaiting && title instanceof TextField) + return title.Data; + else return ""; + let parTitle = this.GetT(KeyStore.Title, TextField); + if (parTitle) + if (parTitle != FieldWaiting) + return parTitle.Data + ".alias"; + else return ".alias"; + return "-untitled-"; } @computed -- cgit v1.2.3-70-g09d2 From 5e53c5953d8550dcf4aa6ad87cd0f68fd835bd14 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 27 Mar 2019 19:52:49 -0400 Subject: fixed tab titles of docking view --- src/client/views/collections/CollectionDockingView.tsx | 16 +++++++++++++++- src/fields/Document.ts | 4 ++-- 2 files changed, 17 insertions(+), 3 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index b77af8cd6..f123149dd 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -18,6 +18,7 @@ import React = require("react"); import { SubCollectionViewProps } from "./CollectionViewBase"; import { ServerUtils } from "../../../server/ServerUtil"; import { DragManager } from "../../util/DragManager"; +import { TextField } from "../../../fields/TextField"; @observer export class CollectionDockingView extends React.Component { @@ -225,8 +226,21 @@ export class CollectionDockingView extends React.Component { - if (tab.hasOwnProperty("contentItem") && tab.contentItem.config.type != "stack") + if (tab.hasOwnProperty("contentItem") && tab.contentItem.config.type != "stack") { + if (tab.titleElement[0].textContent.indexOf("-waiting") != -1) { + Server.GetField(tab.contentItem.config.props.documentId, action((f: Opt) => { + if (f != undefined && f instanceof Document) { + f.GetTAsync(KeyStore.Title, TextField, (tfield) => { + if (tfield != undefined) { + tab.titleElement[0].textContent = f.Title; + } + }) + } + })); + tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId; + } tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId; + } tab.closeElement.off('click') //unbind the current click handler .click(function () { tab.contentItem.remove(); diff --git a/src/fields/Document.ts b/src/fields/Document.ts index e9192f267..85ff6ddcb 100644 --- a/src/fields/Document.ts +++ b/src/fields/Document.ts @@ -40,12 +40,12 @@ export class Document extends Field { if (title) if (title != FieldWaiting && title instanceof TextField) return title.Data; - else return ""; + else return "-waiting-"; let parTitle = this.GetT(KeyStore.Title, TextField); if (parTitle) if (parTitle != FieldWaiting) return parTitle.Data + ".alias"; - else return ".alias"; + else return "-waiting-.alias"; return "-untitled-"; } -- cgit v1.2.3-70-g09d2 From b2558d67608ae20f291c6a1fdbaf1ed09b8c91d2 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 28 Mar 2019 10:04:28 -0400 Subject: made links show up on collections --- src/client/util/DocumentManager.ts | 27 ++++++-- .../views/collections/CollectionFreeFormView.tsx | 72 ++++++++++++++++++++-- .../views/nodes/CollectionFreeFormDocumentView.tsx | 38 ------------ src/client/views/nodes/DocumentView.tsx | 2 - 4 files changed, 89 insertions(+), 50 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 03df11ad7..5bc16343e 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -35,12 +35,6 @@ export class DocumentManager { DocumentManager.Instance.DocumentViews.map(view => { let doc = view.props.Document; // if (view.props.ContainingCollectionView instanceof CollectionFreeFormView) { - // if (Object.is(doc, toFind)) { - // toReturn = view; - // return; - // } - // } - if (Object.is(doc, toFind)) { toReturn = view; @@ -52,6 +46,27 @@ export class DocumentManager { } }) + return (toReturn); + } + public getDocumentViews(toFind: Document): DocumentView[] { + + let toReturn: DocumentView[] = []; + + //gets document view that is in a freeform canvas collection + DocumentManager.Instance.DocumentViews.map(view => { + let doc = view.props.Document; + // if (view.props.ContainingCollectionView instanceof CollectionFreeFormView) { + + if (Object.is(doc, toFind)) { + toReturn.push(view); + } else { + let docSrc = doc.GetT(KeyStore.Prototype, Document); + if (docSrc && docSrc != FieldWaiting && Object.is(docSrc, toFind)) { + toReturn.push(view); + } + } + }) + return (toReturn); } } \ No newline at end of file diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx index 1eab3e475..d75c53b7e 100644 --- a/src/client/views/collections/CollectionFreeFormView.tsx +++ b/src/client/views/collections/CollectionFreeFormView.tsx @@ -1,7 +1,7 @@ -import { action, computed, observable } from "mobx"; +import { action, computed, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../fields/Document"; -import { FieldWaiting } from "../../../fields/Field"; +import { FieldWaiting, Field } from "../../../fields/Field"; import { KeyStore } from "../../../fields/KeyStore"; import { ListField } from "../../../fields/ListField"; import { TextField } from "../../../fields/TextField"; @@ -11,14 +11,16 @@ import { undoBatch } from "../../util/UndoManager"; import { InkingCanvas } from "../InkingCanvas"; import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; import { DocumentContentsView } from "../nodes/DocumentContentsView"; -import { DocumentViewProps } from "../nodes/DocumentView"; +import { DocumentViewProps, DocumentView } from "../nodes/DocumentView"; import "./CollectionFreeFormView.scss"; import { COLLECTION_BORDER_WIDTH } from "./CollectionView"; -import { CollectionViewBase } from "./CollectionViewBase"; +import { CollectionViewBase, CollectionViewProps } from "./CollectionViewBase"; import { MarqueeView } from "./MarqueeView"; import { PreviewCursor } from "./PreviewCursor"; import React = require("react"); import v5 = require("uuid/v5"); +import { DocumentManager } from "../../util/DocumentManager"; +import { Utils } from "../../../Utils"; @observer export class CollectionFreeFormView extends CollectionViewBase { @@ -360,6 +362,7 @@ export class CollectionFreeFormView extends CollectionViewBase { {this.views} + {super.getCursors().map(entry => { if (entry.Data.length > 0) { let id = entry.Data[0][0]; @@ -411,4 +414,65 @@ export class CollectionFreeFormView extends CollectionViewBase {
); } +} + +@observer +export class LinksView extends React.Component { + private _mainCont = React.createRef(); + + constructor(props: CollectionViewProps) { + super(props); + } + + @observable _pairs: { a: DocumentView, b: DocumentView }[] = []; + + findPairs() { + return DocumentManager.Instance.DocumentViews.filter(dv => dv.props.ContainingCollectionView && dv.props.ContainingCollectionView.props.Document === this.props.Document).reduce((pairs, dv) => { + let linksList = dv.props.Document.GetT(KeyStore.LinkedToDocs, ListField); + if (linksList && linksList != FieldWaiting && linksList.Data.length) { + pairs.push(...linksList.Data.reduce((pairs, link) => { + if (link instanceof Document) { + let linkToDoc = link.GetT(KeyStore.LinkedToDocs, Document); + if (linkToDoc && linkToDoc != FieldWaiting) { + DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => + pairs.push({ a: dv, b: docView1 }) + ) + } + } + return pairs; + }, [] as { a: DocumentView, b: DocumentView }[])); + } + return pairs; + }, [] as { a: DocumentView, b: DocumentView }[]); + } + componentDidMount() { + reaction(() => this.findPairs() + , (pairs) => runInAction(() => this._pairs = pairs)); + } + + render() { + if (!this._pairs.length) + return (null); + return
+ {this._pairs.map(pair => { + let doc1 = pair.a.props.Document; + let doc2 = pair.b.props.Document; + let x1 = doc1.GetNumber(KeyStore.X, 0) + doc1.GetNumber(KeyStore.Width, 0) / 2; + let y1 = doc1.GetNumber(KeyStore.Y, 0) + doc1.GetNumber(KeyStore.Height, 0) / 2; + let x2 = doc2.GetNumber(KeyStore.X, 0) + doc2.GetNumber(KeyStore.Width, 0) / 2; + let y2 = doc2.GetNumber(KeyStore.Y, 0) + doc2.GetNumber(KeyStore.Height, 0) / 2; + let lx = Math.min(x1, x2); + let ly = Math.min(y1, y2); + let w = Math.max(x1, x2) - lx; + let h = Math.max(y1, y2) - ly; + let unflipped = (x1 == lx && y1 == ly) || (x2 == lx && y2 == ly); + return ( +
+ + + +
); + })} +
+ } } \ No newline at end of file diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index ed3468400..7f951864e 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -69,46 +69,8 @@ export class CollectionFreeFormDocumentView extends React.Component } - @observable _docView1: DocumentView | null = null; - @observable _docView2: DocumentView | null = null; - - componentDidMount() { - reaction(() => { - let linkFrom = this.props.Document.GetT(KeyStore.LinkedFromDocs, Document); - let linkTo = this.props.Document.GetT(KeyStore.LinkedToDocs, Document); - let docView1: DocumentView | null = null; - let docView2: DocumentView | null = null; - if (linkFrom instanceof Document && linkTo instanceof Document) { - docView1 = DocumentManager.Instance.getDocumentView(linkFrom); - docView2 = DocumentManager.Instance.getDocumentView(linkTo); - } - return [docView1, docView2]; - }, (vals) => runInAction(() => { - this._docView1 = vals[0]; - this._docView2 = vals[1]; - }), { fireImmediately: true }); - } render() { - if (this._docView1 != null && this._docView2 != null) { - let doc1 = this._docView1.props.Document; - let doc2 = this._docView2.props.Document; - let x1 = doc1.GetNumber(KeyStore.X, 0) + doc1.GetNumber(KeyStore.Width, 0) / 2; - let y1 = doc1.GetNumber(KeyStore.Y, 0) + doc1.GetNumber(KeyStore.Height, 0) / 2; - let x2 = doc2.GetNumber(KeyStore.X, 0) + doc2.GetNumber(KeyStore.Width, 0) / 2; - let y2 = doc2.GetNumber(KeyStore.Y, 0) + doc2.GetNumber(KeyStore.Height, 0) / 2; - let lx = Math.min(x1, x2); - let ly = Math.min(y1, y2); - let w = Math.max(x1, x2) - lx; - let h = Math.max(y1, y2) - ly; - let unflipped = (x1 == lx && y1 == ly) || (x2 == lx && y2 == ly); - return ( -
- - - -
); - } return (
{ linkDoc.Set(KeyStore.LinkedFromDocs, srcTarg); dstTarg.GetOrCreateAsync(KeyStore.LinkedFromDocs, ListField, field => { (field as ListField).Data.push(linkDoc) }) srcTarg.GetOrCreateAsync(KeyStore.LinkedToDocs, ListField, field => { (field as ListField).Data.push(linkDoc) }) - if (this.props.AddDocument) - this.props.AddDocument(linkDoc, false); })) ) e.stopPropagation(); -- cgit v1.2.3-70-g09d2 From cbf88ae3466e17fef57c7d63cc72ee42b75ccce6 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 28 Mar 2019 10:36:03 -0400 Subject: cleaned up --- .../views/collections/CollectionFreeFormView.scss | 17 ++++++++++ .../views/collections/CollectionFreeFormView.tsx | 39 +++++++++------------- 2 files changed, 32 insertions(+), 24 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionFreeFormView.scss b/src/client/views/collections/CollectionFreeFormView.scss index bdc597a25..2dd345b6f 100644 --- a/src/client/views/collections/CollectionFreeFormView.scss +++ b/src/client/views/collections/CollectionFreeFormView.scss @@ -1,5 +1,22 @@ @import "../global_variables"; +.collectionfreeformview-linkLine { + stroke: black; + stroke-width: 3; + transform: translate(10000px,10000px) +} +.collectionfreeformview-svgContainer{ + transform: translate(-10000px,-10000px); + position: absolute; + width: 20000px; + height: 20000px; +} +.collectionfreeformview-svgCanvas{ + transform: translate(-10000px,-10000px); + position: absolute; + width: 20000px; + height: 20000px; +} .collectionfreeformview-container { .collectionfreeformview > .jsx-parser { position: absolute; diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx index d75c53b7e..cba1110a1 100644 --- a/src/client/views/collections/CollectionFreeFormView.tsx +++ b/src/client/views/collections/CollectionFreeFormView.tsx @@ -424,7 +424,7 @@ export class LinksView extends React.Component { super(props); } - @observable _pairs: { a: DocumentView, b: DocumentView }[] = []; + @observable _pairs: { a: Document, b: Document }[] = []; findPairs() { return DocumentManager.Instance.DocumentViews.filter(dv => dv.props.ContainingCollectionView && dv.props.ContainingCollectionView.props.Document === this.props.Document).reduce((pairs, dv) => { @@ -435,44 +435,35 @@ export class LinksView extends React.Component { let linkToDoc = link.GetT(KeyStore.LinkedToDocs, Document); if (linkToDoc && linkToDoc != FieldWaiting) { DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => - pairs.push({ a: dv, b: docView1 }) + pairs.push({ a: dv.props.Document, b: docView1.props.Document }) ) } } return pairs; - }, [] as { a: DocumentView, b: DocumentView }[])); + }, [] as { a: Document, b: Document }[])); } return pairs; - }, [] as { a: DocumentView, b: DocumentView }[]); + }, [] as { a: Document, b: Document }[]); } componentDidMount() { - reaction(() => this.findPairs() - , (pairs) => runInAction(() => this._pairs = pairs)); + reaction(() => this.findPairs(), (pairs) => runInAction(() => this._pairs = pairs)); } render() { if (!this._pairs.length) return (null); - return
+ return {this._pairs.map(pair => { - let doc1 = pair.a.props.Document; - let doc2 = pair.b.props.Document; - let x1 = doc1.GetNumber(KeyStore.X, 0) + doc1.GetNumber(KeyStore.Width, 0) / 2; - let y1 = doc1.GetNumber(KeyStore.Y, 0) + doc1.GetNumber(KeyStore.Height, 0) / 2; - let x2 = doc2.GetNumber(KeyStore.X, 0) + doc2.GetNumber(KeyStore.Width, 0) / 2; - let y2 = doc2.GetNumber(KeyStore.Y, 0) + doc2.GetNumber(KeyStore.Height, 0) / 2; - let lx = Math.min(x1, x2); - let ly = Math.min(y1, y2); - let w = Math.max(x1, x2) - lx; - let h = Math.max(y1, y2) - ly; - let unflipped = (x1 == lx && y1 == ly) || (x2 == lx && y2 == ly); + let x1 = pair.a.GetNumber(KeyStore.X, 0) + pair.a.GetNumber(KeyStore.Width, 0) / 2; + let y1 = pair.a.GetNumber(KeyStore.Y, 0) + pair.a.GetNumber(KeyStore.Height, 0) / 2; + let x2 = pair.b.GetNumber(KeyStore.X, 0) + pair.b.GetNumber(KeyStore.Width, 0) / 2; + let y2 = pair.b.GetNumber(KeyStore.Y, 0) + pair.b.GetNumber(KeyStore.Height, 0) / 2; return ( -
- - - -
); + + ) })} -
+ } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From fddd13da196a26affe61b30d93a3fe1b24f391a6 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 28 Mar 2019 12:59:35 -0400 Subject: works better with prototypes and links --- src/client/util/DocumentManager.ts | 5 ++ .../views/collections/CollectionFreeFormView.tsx | 53 +++++++++++++++++----- src/client/views/collections/CollectionView.tsx | 4 +- src/client/views/nodes/LinkBox.tsx | 2 +- 4 files changed, 50 insertions(+), 14 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 5bc16343e..28624dba8 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -26,6 +26,11 @@ export class DocumentManager { // this.DocumentViews = new Array(); } + 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; diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx index cba1110a1..0843d3670 100644 --- a/src/client/views/collections/CollectionFreeFormView.tsx +++ b/src/client/views/collections/CollectionFreeFormView.tsx @@ -1,7 +1,7 @@ import { action, computed, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../fields/Document"; -import { FieldWaiting, Field } from "../../../fields/Field"; +import { FieldWaiting, Field, Opt } from "../../../fields/Field"; import { KeyStore } from "../../../fields/KeyStore"; import { ListField } from "../../../fields/ListField"; import { TextField } from "../../../fields/TextField"; @@ -21,6 +21,8 @@ import React = require("react"); import v5 = require("uuid/v5"); import { DocumentManager } from "../../util/DocumentManager"; import { Utils } from "../../../Utils"; +import { Server } from "../../Server"; +import { AverageAggregateParameters } from "../../northstar/model/idea/idea"; @observer export class CollectionFreeFormView extends CollectionViewBase { @@ -424,42 +426,71 @@ export class LinksView extends React.Component { super(props); } - @observable _pairs: { a: Document, b: Document }[] = []; + @observable _triples: { a: Document, b: Document, l: Document }[] = []; findPairs() { return DocumentManager.Instance.DocumentViews.filter(dv => dv.props.ContainingCollectionView && dv.props.ContainingCollectionView.props.Document === this.props.Document).reduce((pairs, dv) => { + + return DocumentManager.Instance.DocumentViews.reduce((pairs, dv) => { + let srcViews = [dv]; + let srcAnnot = dv.props.Document.GetT(KeyStore.AnnotationOn, Document); + if (srcAnnot && srcAnnot != FieldWaiting && srcAnnot instanceof Document) { + srcViews = DocumentManager.Instance.getDocumentViews(srcAnnot.GetPrototype() as Document) + } + srcViews = srcViews.filter(sv => + sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document == self + ); let linksList = dv.props.Document.GetT(KeyStore.LinkedToDocs, ListField); if (linksList && linksList != FieldWaiting && linksList.Data.length) { pairs.push(...linksList.Data.reduce((pairs, link) => { if (link instanceof Document) { let linkToDoc = link.GetT(KeyStore.LinkedToDocs, Document); if (linkToDoc && linkToDoc != FieldWaiting) { - DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => - pairs.push({ a: dv.props.Document, b: docView1.props.Document }) - ) + DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => { + + let targetViews = [docView1]; + let docAnnot = docView1.props.Document.GetT(KeyStore.AnnotationOn, Document); + if (docAnnot && docAnnot != FieldWaiting && docAnnot instanceof Document) { + targetViews = DocumentManager.Instance.getDocumentViews(docAnnot.GetPrototype() as Document) + } + targetViews.filter(tv => + tv.props.ContainingCollectionView && tv.props.ContainingCollectionView.props.Document == self + ).map(tv => srcViews.map(sv => + pairs.push({ a: sv.props.Document, b: tv.props.Document, l: link }))) + }) } } return pairs; - }, [] as { a: Document, b: Document }[])); + }, [] as { a: Document, b: Document, l: Document }[])); } return pairs; - }, [] as { a: Document, b: Document }[]); + }, [] as { a: Document, b: Document, l: Document }[]); } componentDidMount() { - reaction(() => this.findPairs(), (pairs) => runInAction(() => this._pairs = pairs)); + reaction(() => this.findPairs(), (pairs) => runInAction(() => this._triples = pairs)); + } + + onPointerDown(e: React.PointerEvent) { + let line = (e.nativeEvent as any).path[0]; + line.style.stroke = "red"; + Server.GetField(line.id, action((f: Opt) => { + if (f instanceof Document) { + console.log(f.Title); + } + })); } render() { - if (!this._pairs.length) + if (!this._triples.length) return (null); return - {this._pairs.map(pair => { + {this._triples.map(pair => { let x1 = pair.a.GetNumber(KeyStore.X, 0) + pair.a.GetNumber(KeyStore.Width, 0) / 2; let y1 = pair.a.GetNumber(KeyStore.Y, 0) + pair.a.GetNumber(KeyStore.Height, 0) / 2; let x2 = pair.b.GetNumber(KeyStore.X, 0) + pair.b.GetNumber(KeyStore.Width, 0) / 2; let y2 = pair.b.GetNumber(KeyStore.Y, 0) + pair.b.GetNumber(KeyStore.Height, 0) / 2; return ( - ) diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 2fa2c9086..789f07e8c 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -69,9 +69,9 @@ export class CollectionView extends React.Component { @action public static AddDocument(props: CollectionViewProps, doc: Document, allowDuplicates: boolean): boolean { var curPage = props.Document.GetNumber(KeyStore.CurPage, -1); - doc.SetNumber(KeyStore.Page, curPage); + doc.SetOnPrototype(KeyStore.Page, new NumberField(curPage)); if (curPage > 0) { - doc.Set(KeyStore.AnnotationOn, props.Document); + doc.SetOnPrototype(KeyStore.AnnotationOn, props.Document); } if (props.Document.Get(props.fieldKey) instanceof Field) { //TODO This won't create the field if it doesn't already exist diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 457fecee3..e81f8fec7 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -45,7 +45,7 @@ export class LinkBox extends React.Component { } else { this.props.pairedDoc.GetAsync(KeyStore.AnnotationOn, (contextDoc: any) => { if (!contextDoc) { - CollectionDockingView.Instance.AddRightSplit(this.props.pairedDoc); + CollectionDockingView.Instance.AddRightSplit(this.props.pairedDoc.MakeDelegate()); } else if (contextDoc instanceof Document) { this.props.pairedDoc.GetTAsync(KeyStore.Page, NumberField).then((pfield: any) => { contextDoc.GetTAsync(KeyStore.CurPage, NumberField).then((cfield: any) => { -- cgit v1.2.3-70-g09d2 From e64ec4345b8403347e86ac8c472507343f6b7548 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 28 Mar 2019 13:00:03 -0400 Subject: from last --- src/client/views/collections/CollectionFreeFormView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx index 0843d3670..5ce42acc9 100644 --- a/src/client/views/collections/CollectionFreeFormView.tsx +++ b/src/client/views/collections/CollectionFreeFormView.tsx @@ -429,7 +429,7 @@ export class LinksView extends React.Component { @observable _triples: { a: Document, b: Document, l: Document }[] = []; findPairs() { - return DocumentManager.Instance.DocumentViews.filter(dv => dv.props.ContainingCollectionView && dv.props.ContainingCollectionView.props.Document === this.props.Document).reduce((pairs, dv) => { + let self = this.props.Document; return DocumentManager.Instance.DocumentViews.reduce((pairs, dv) => { let srcViews = [dv]; -- cgit v1.2.3-70-g09d2 From bdea88507ce2ba4da32b7f89c995021d0a058413 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 28 Mar 2019 15:24:57 -0400 Subject: fixed some things with linking. still some real inefficiencies. --- src/client/documents/Documents.ts | 1 + .../views/collections/CollectionFreeFormView.scss | 10 +-- .../views/collections/CollectionFreeFormView.tsx | 87 ++++++++++++++-------- src/client/views/collections/CollectionView.tsx | 2 +- 4 files changed, 59 insertions(+), 41 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 1d23b8c2c..837dfe815 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -119,6 +119,7 @@ export namespace Documents { imageProto = setupPrototypeOptions(imageProtoId, "IMAGE_PROTO", CollectionView.LayoutString("AnnotationsKey"), { x: 0, y: 0, nativeWidth: 300, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] }); imageProto.SetText(KeyStore.BackgroundLayout, ImageBox.LayoutString()); + imageProto.SetNumber(KeyStore.CurPage, 0); } return imageProto; } diff --git a/src/client/views/collections/CollectionFreeFormView.scss b/src/client/views/collections/CollectionFreeFormView.scss index 2dd345b6f..7012ce6b9 100644 --- a/src/client/views/collections/CollectionFreeFormView.scss +++ b/src/client/views/collections/CollectionFreeFormView.scss @@ -3,19 +3,15 @@ .collectionfreeformview-linkLine { stroke: black; stroke-width: 3; - transform: translate(10000px,10000px) -} -.collectionfreeformview-svgContainer{ - transform: translate(-10000px,-10000px); - position: absolute; - width: 20000px; - height: 20000px; + transform: translate(10000px,10000px); + pointer-events: all; } .collectionfreeformview-svgCanvas{ transform: translate(-10000px,-10000px); position: absolute; width: 20000px; height: 20000px; + pointer-events: none; } .collectionfreeformview-container { .collectionfreeformview > .jsx-parser { diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx index 5ce42acc9..91faf0907 100644 --- a/src/client/views/collections/CollectionFreeFormView.tsx +++ b/src/client/views/collections/CollectionFreeFormView.tsx @@ -1,4 +1,4 @@ -import { action, computed, observable, reaction, runInAction } from "mobx"; +import { action, computed, observable, reaction, runInAction, trace, untracked } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../fields/Document"; import { FieldWaiting, Field, Opt } from "../../../fields/Field"; @@ -424,6 +424,7 @@ export class LinksView extends React.Component { constructor(props: CollectionViewProps) { super(props); + console.log("Create links View"); } @observable _triples: { a: Document, b: Document, l: Document }[] = []; @@ -432,42 +433,47 @@ export class LinksView extends React.Component { let self = this.props.Document; return DocumentManager.Instance.DocumentViews.reduce((pairs, dv) => { - let srcViews = [dv]; - let srcAnnot = dv.props.Document.GetT(KeyStore.AnnotationOn, Document); - if (srcAnnot && srcAnnot != FieldWaiting && srcAnnot instanceof Document) { - srcViews = DocumentManager.Instance.getDocumentViews(srcAnnot.GetPrototype() as Document) - } - srcViews = srcViews.filter(sv => - sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document == self - ); - let linksList = dv.props.Document.GetT(KeyStore.LinkedToDocs, ListField); - if (linksList && linksList != FieldWaiting && linksList.Data.length) { - pairs.push(...linksList.Data.reduce((pairs, link) => { - if (link instanceof Document) { - let linkToDoc = link.GetT(KeyStore.LinkedToDocs, Document); - if (linkToDoc && linkToDoc != FieldWaiting) { - DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => { - - let targetViews = [docView1]; - let docAnnot = docView1.props.Document.GetT(KeyStore.AnnotationOn, Document); - if (docAnnot && docAnnot != FieldWaiting && docAnnot instanceof Document) { - targetViews = DocumentManager.Instance.getDocumentViews(docAnnot.GetPrototype() as Document) - } - targetViews.filter(tv => - tv.props.ContainingCollectionView && tv.props.ContainingCollectionView.props.Document == self - ).map(tv => srcViews.map(sv => - pairs.push({ a: sv.props.Document, b: tv.props.Document, l: link }))) - }) + untracked(() => { + let srcViews = [dv]; + let srcAnnot = dv.props.Document.GetT(KeyStore.AnnotationOn, Document); + if (srcAnnot && srcAnnot != FieldWaiting && srcAnnot instanceof Document) { + srcViews = DocumentManager.Instance.getDocumentViews(srcAnnot.GetPrototype() as Document) + } + srcViews = srcViews.filter(sv => + sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document == self + ); + let linksList = dv.props.Document.GetT(KeyStore.LinkedToDocs, ListField); + if (linksList && linksList != FieldWaiting && linksList.Data.length) { + pairs.push(...linksList.Data.reduce((pairs, link) => { + if (link instanceof Document) { + let linkToDoc = link.GetT(KeyStore.LinkedToDocs, Document); + if (linkToDoc && linkToDoc != FieldWaiting) { + DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => { + let targetViews = [docView1]; + let docAnnot = docView1.props.Document.GetT(KeyStore.AnnotationOn, Document); + if (docAnnot && docAnnot != FieldWaiting && docAnnot instanceof Document) { + targetViews = DocumentManager.Instance.getDocumentViews(docAnnot.GetPrototype() as Document) + } + targetViews.filter(tv => + tv.props.ContainingCollectionView && tv.props.ContainingCollectionView.props.Document == self + ).map(tv => srcViews.map(sv => + pairs.push({ a: sv.props.Document, b: tv.props.Document, l: link }))) + }) + } } - } - return pairs; - }, [] as { a: Document, b: Document, l: Document }[])); - } + return pairs; + }, [] as { a: Document, b: Document, l: Document }[])); + } + }) return pairs; }, [] as { a: Document, b: Document, l: Document }[]); } componentDidMount() { - reaction(() => this.findPairs(), (pairs) => runInAction(() => this._triples = pairs)); + reaction(() => DocumentManager.Instance.DocumentViews.map(dv => [ + dv.props.Document.Get(KeyStore.AnnotationOn, false), + dv.props.Document.Get(KeyStore.LinkedFromDocs, false), + dv.props.Document.Get(KeyStore.LinkedToDocs, false) + ]), () => runInAction(() => this._triples = this.findPairs())); } onPointerDown(e: React.PointerEvent) { @@ -483,14 +489,29 @@ export class LinksView extends React.Component { render() { if (!this._triples.length) return (null); + let y: { a: Document, b: Document, l: Document, n: number }[] = [] + this._triples.map(t => { + if (!y.reduce((found, yi) => { + if (yi.a == t.a && yi.b == t.b) { + if (yi.l != t.l) + yi.n++; + return true; + } + return found; + }, false)) { + y.push({ a: t.a, b: t.b, l: t.l, n: 1 }); + } + }) + console.log("RENDERING " + y.length) return - {this._triples.map(pair => { + {y.map(pair => { let x1 = pair.a.GetNumber(KeyStore.X, 0) + pair.a.GetNumber(KeyStore.Width, 0) / 2; let y1 = pair.a.GetNumber(KeyStore.Y, 0) + pair.a.GetNumber(KeyStore.Height, 0) / 2; let x2 = pair.b.GetNumber(KeyStore.X, 0) + pair.b.GetNumber(KeyStore.Width, 0) / 2; let y2 = pair.b.GetNumber(KeyStore.Y, 0) + pair.b.GetNumber(KeyStore.Height, 0) / 2; return ( ) diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 789f07e8c..c804085df 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -70,7 +70,7 @@ export class CollectionView extends React.Component { public static AddDocument(props: CollectionViewProps, doc: Document, allowDuplicates: boolean): boolean { var curPage = props.Document.GetNumber(KeyStore.CurPage, -1); doc.SetOnPrototype(KeyStore.Page, new NumberField(curPage)); - if (curPage > 0) { + if (curPage >= 0) { doc.SetOnPrototype(KeyStore.AnnotationOn, props.Document); } if (props.Document.Get(props.fieldKey) instanceof Field) { -- cgit v1.2.3-70-g09d2 From c5cd6c5716ca7a66db367f1e3859feb1c7302f6f Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 28 Mar 2019 18:26:35 -0400 Subject: reordered --- .../views/collections/CollectionFreeFormView.tsx | 74 ++++++++++++---------- 1 file changed, 39 insertions(+), 35 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx index 91faf0907..44f4eadaa 100644 --- a/src/client/views/collections/CollectionFreeFormView.tsx +++ b/src/client/views/collections/CollectionFreeFormView.tsx @@ -1,4 +1,4 @@ -import { action, computed, observable, reaction, runInAction, trace, untracked } from "mobx"; +import { action, computed, observable, reaction, runInAction, trace, untracked, IReactionDisposer } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../fields/Document"; import { FieldWaiting, Field, Opt } from "../../../fields/Field"; @@ -420,18 +420,31 @@ export class CollectionFreeFormView extends CollectionViewBase { @observer export class LinksView extends React.Component { - private _mainCont = React.createRef(); + @observable _connections: { a: Document, b: Document, l: Document }[] = []; - constructor(props: CollectionViewProps) { - super(props); - console.log("Create links View"); + private _reactionDisposer: Opt; + + componentDidMount() { + this._reactionDisposer = reaction(() => DocumentManager.Instance.DocumentViews.map(dv => [ + dv.props.Document.Get(KeyStore.AnnotationOn, false), + dv.props.Document.Get(KeyStore.LinkedFromDocs, false), + dv.props.Document.Get(KeyStore.LinkedToDocs, false) + ]), () => runInAction(() => this._connections = this.findPairs()), { fireImmediately: true }); + } + componentWillUnmount() { + if (this._reactionDisposer) { + this._reactionDisposer(); + } + this._reactionDisposer = undefined; } - @observable _triples: { a: Document, b: Document, l: Document }[] = []; + filtered(views: DocumentView[]) { + return views.filter(sv => + sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document == this.props.Document + ); + } findPairs() { - let self = this.props.Document; - return DocumentManager.Instance.DocumentViews.reduce((pairs, dv) => { untracked(() => { let srcViews = [dv]; @@ -439,9 +452,6 @@ export class LinksView extends React.Component { if (srcAnnot && srcAnnot != FieldWaiting && srcAnnot instanceof Document) { srcViews = DocumentManager.Instance.getDocumentViews(srcAnnot.GetPrototype() as Document) } - srcViews = srcViews.filter(sv => - sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document == self - ); let linksList = dv.props.Document.GetT(KeyStore.LinkedToDocs, ListField); if (linksList && linksList != FieldWaiting && linksList.Data.length) { pairs.push(...linksList.Data.reduce((pairs, link) => { @@ -454,9 +464,7 @@ export class LinksView extends React.Component { if (docAnnot && docAnnot != FieldWaiting && docAnnot instanceof Document) { targetViews = DocumentManager.Instance.getDocumentViews(docAnnot.GetPrototype() as Document) } - targetViews.filter(tv => - tv.props.ContainingCollectionView && tv.props.ContainingCollectionView.props.Document == self - ).map(tv => srcViews.map(sv => + this.filtered(targetViews).map(tv => this.filtered(srcViews).map(sv => pairs.push({ a: sv.props.Document, b: tv.props.Document, l: link }))) }) } @@ -468,13 +476,6 @@ export class LinksView extends React.Component { return pairs; }, [] as { a: Document, b: Document, l: Document }[]); } - componentDidMount() { - reaction(() => DocumentManager.Instance.DocumentViews.map(dv => [ - dv.props.Document.Get(KeyStore.AnnotationOn, false), - dv.props.Document.Get(KeyStore.LinkedFromDocs, false), - dv.props.Document.Get(KeyStore.LinkedToDocs, false) - ]), () => runInAction(() => this._triples = this.findPairs())); - } onPointerDown(e: React.PointerEvent) { let line = (e.nativeEvent as any).path[0]; @@ -486,11 +487,9 @@ export class LinksView extends React.Component { })); } - render() { - if (!this._triples.length) - return (null); - let y: { a: Document, b: Document, l: Document, n: number }[] = [] - this._triples.map(t => { + @computed + get uniqueConnections() { + return this._connections.reduce((y, t) => { if (!y.reduce((found, yi) => { if (yi.a == t.a && yi.b == t.b) { if (yi.l != t.l) @@ -501,17 +500,22 @@ export class LinksView extends React.Component { }, false)) { y.push({ a: t.a, b: t.b, l: t.l, n: 1 }); } - }) - console.log("RENDERING " + y.length) + return y + }, [] as { a: Document, b: Document, l: Document, n: number }[]); + } + + render() { + if (!this._connections.length) + return (null); return - {y.map(pair => { - let x1 = pair.a.GetNumber(KeyStore.X, 0) + pair.a.GetNumber(KeyStore.Width, 0) / 2; - let y1 = pair.a.GetNumber(KeyStore.Y, 0) + pair.a.GetNumber(KeyStore.Height, 0) / 2; - let x2 = pair.b.GetNumber(KeyStore.X, 0) + pair.b.GetNumber(KeyStore.Width, 0) / 2; - let y2 = pair.b.GetNumber(KeyStore.Y, 0) + pair.b.GetNumber(KeyStore.Height, 0) / 2; + {this.uniqueConnections.map(c => { + let x1 = c.a.GetNumber(KeyStore.X, 0) + c.a.GetNumber(KeyStore.Width, 0) / 2; + let y1 = c.a.GetNumber(KeyStore.Y, 0) + c.a.GetNumber(KeyStore.Height, 0) / 2; + let x2 = c.b.GetNumber(KeyStore.X, 0) + c.b.GetNumber(KeyStore.Width, 0) / 2; + let y2 = c.b.GetNumber(KeyStore.Y, 0) + c.b.GetNumber(KeyStore.Height, 0) / 2; return ( - ) -- cgit v1.2.3-70-g09d2 From c3eea60fa586e8ff9cf59497c041044fd0143bb1 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 28 Mar 2019 21:03:17 -0400 Subject: organized collectionfreeformview. fixed issues with showing links --- src/client/util/DocumentManager.ts | 24 +- .../views/collections/CollectionFreeFormView.scss | 116 ----- .../views/collections/CollectionFreeFormView.tsx | 525 --------------------- src/client/views/collections/CollectionView.tsx | 7 +- src/client/views/collections/MarqueeView.scss | 8 - src/client/views/collections/MarqueeView.tsx | 175 ------- src/client/views/collections/PreviewCursor.scss | 18 - src/client/views/collections/PreviewCursor.tsx | 76 --- .../CollectionFreeFormLinkView.scss | 6 + .../CollectionFreeFormLinkView.tsx | 37 ++ .../CollectionFreeFormLinksView.scss | 7 + .../CollectionFreeFormLinksView.tsx | 56 +++ .../collectionFreeForm/CollectionFreeFormView.scss | 95 ++++ .../collectionFreeForm/CollectionFreeFormView.tsx | 416 ++++++++++++++++ .../collectionFreeForm/MarqueeView.scss | 8 + .../collections/collectionFreeForm/MarqueeView.tsx | 175 +++++++ .../collectionFreeForm/PreviewCursor.scss | 18 + .../collectionFreeForm/PreviewCursor.tsx | 76 +++ src/client/views/nodes/DocumentContentsView.tsx | 2 +- 19 files changed, 924 insertions(+), 921 deletions(-) delete mode 100644 src/client/views/collections/CollectionFreeFormView.scss delete mode 100644 src/client/views/collections/CollectionFreeFormView.tsx delete mode 100644 src/client/views/collections/MarqueeView.scss delete mode 100644 src/client/views/collections/MarqueeView.tsx delete mode 100644 src/client/views/collections/PreviewCursor.scss delete mode 100644 src/client/views/collections/PreviewCursor.tsx create mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss create mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx create mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss create mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx create mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss create mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx create mode 100644 src/client/views/collections/collectionFreeForm/MarqueeView.scss create mode 100644 src/client/views/collections/collectionFreeForm/MarqueeView.tsx create mode 100644 src/client/views/collections/collectionFreeForm/PreviewCursor.scss create mode 100644 src/client/views/collections/collectionFreeForm/PreviewCursor.tsx (limited to 'src/client/views/collections') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 28624dba8..bf59fbb43 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -1,10 +1,11 @@ import React = require('react') import { observer } from 'mobx-react'; -import { observable, action } from 'mobx'; +import { observable, action, computed } from 'mobx'; import { Document } from "../../fields/Document" import { DocumentView } from '../views/nodes/DocumentView'; import { KeyStore } from '../../fields/KeyStore'; import { FieldWaiting } from '../../fields/Field'; +import { ListField } from '../../fields/ListField'; export class DocumentManager { @@ -74,4 +75,25 @@ export class DocumentManager { return (toReturn); } + + @computed + public get LinkedDocumentViews() { + return DocumentManager.Instance.DocumentViews.reduce((pairs, dv) => { + let linksList = dv.props.Document.GetT(KeyStore.LinkedToDocs, ListField); + if (linksList && linksList != FieldWaiting && linksList.Data.length) { + pairs.push(...linksList.Data.reduce((pairs, link) => { + if (link instanceof Document) { + let linkToDoc = link.GetT(KeyStore.LinkedToDocs, Document); + if (linkToDoc && linkToDoc != FieldWaiting) { + DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => { + pairs.push({ a: dv, b: docView1, l: link }) + }) + } + } + return pairs; + }, [] as { a: DocumentView, b: DocumentView, l: Document }[])); + } + return pairs; + }, [] as { a: DocumentView, b: DocumentView, l: Document }[]); + } } \ No newline at end of file diff --git a/src/client/views/collections/CollectionFreeFormView.scss b/src/client/views/collections/CollectionFreeFormView.scss deleted file mode 100644 index 7012ce6b9..000000000 --- a/src/client/views/collections/CollectionFreeFormView.scss +++ /dev/null @@ -1,116 +0,0 @@ -@import "../global_variables"; - -.collectionfreeformview-linkLine { - stroke: black; - stroke-width: 3; - transform: translate(10000px,10000px); - pointer-events: all; -} -.collectionfreeformview-svgCanvas{ - transform: translate(-10000px,-10000px); - position: absolute; - width: 20000px; - height: 20000px; - pointer-events: none; -} -.collectionfreeformview-container { - .collectionfreeformview > .jsx-parser { - position: absolute; - height: 100%; - width: 100%; - } - - .inking-canvas { - transform-origin: 50000px 50000px; - } - //nested freeform views - // .collectionfreeformview-container { - // background-image: linear-gradient(to right, $light-color-secondary 1px, transparent 1px), - // linear-gradient(to bottom, $light-color-secondary 1px, transparent 1px); - // background-size: 30px 30px; - // } - - box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw; - border: 0px solid $light-color-secondary; - border-radius: $border-radius; - box-sizing: border-box; - position: relative; - overflow: hidden; - top: 0; - left: 0; - width: 100%; - height: 100%; - .collectionfreeformview { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - .inking-canvas { - transform-origin: 50000px 50000px; - } - } -} -.collectionfreeformview-overlay { - .collectionfreeformview > .jsx-parser { - position: absolute; - height: 100%; - } - .formattedTextBox-cont { - background: $light-color-secondary; - } - - .inking-canvas { - transform-origin: 50000px 50000px; - } - - opacity: 0.99; - border: 0px solid transparent; - border-radius: $border-radius; - box-sizing: border-box; - position:relative; - overflow: hidden; - top: 0; - left: 0; - width: 100%; - height: 100%; - .collectionfreeformview { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - .inking-canvas { - transform-origin: 50000px 50000px; - } - .formattedTextBox-cont { - background:yellow; - } - } -} - -// selection border...? -.border { - border-style: solid; - box-sizing: border-box; - width: 98%; - height: 98%; - border-radius: $border-radius; -} - -//this is an animation for the blinking cursor! -@keyframes blink { - 0% { - opacity: 0; - } - 49% { - opacity: 0; - } - 50% { - opacity: 1; - } -} - -#prevCursor { - animation: blink 1s infinite; -} diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx deleted file mode 100644 index 44f4eadaa..000000000 --- a/src/client/views/collections/CollectionFreeFormView.tsx +++ /dev/null @@ -1,525 +0,0 @@ -import { action, computed, observable, reaction, runInAction, trace, untracked, IReactionDisposer } from "mobx"; -import { observer } from "mobx-react"; -import { Document } from "../../../fields/Document"; -import { FieldWaiting, Field, Opt } from "../../../fields/Field"; -import { KeyStore } from "../../../fields/KeyStore"; -import { ListField } from "../../../fields/ListField"; -import { TextField } from "../../../fields/TextField"; -import { DragManager } from "../../util/DragManager"; -import { Transform } from "../../util/Transform"; -import { undoBatch } from "../../util/UndoManager"; -import { InkingCanvas } from "../InkingCanvas"; -import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; -import { DocumentContentsView } from "../nodes/DocumentContentsView"; -import { DocumentViewProps, DocumentView } from "../nodes/DocumentView"; -import "./CollectionFreeFormView.scss"; -import { COLLECTION_BORDER_WIDTH } from "./CollectionView"; -import { CollectionViewBase, CollectionViewProps } from "./CollectionViewBase"; -import { MarqueeView } from "./MarqueeView"; -import { PreviewCursor } from "./PreviewCursor"; -import React = require("react"); -import v5 = require("uuid/v5"); -import { DocumentManager } from "../../util/DocumentManager"; -import { Utils } from "../../../Utils"; -import { Server } from "../../Server"; -import { AverageAggregateParameters } from "../../northstar/model/idea/idea"; - -@observer -export class CollectionFreeFormView extends CollectionViewBase { - public _canvasRef = React.createRef(); - private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type) - - public addLiveTextBox = (newBox: Document) => { - // mark this collection so that when the text box is created we can send it the SelectOnLoad prop to focus itself - this._selectOnLoaded = newBox.Id; - //set text to be the typed key and get focus on text box - this.props.addDocument(newBox, false); - //remove cursor from screen - this.PreviewCursorVisible = false; - } - - public selectDocuments = (docs: Document[]) => { - this.props.CollectionView.SelectedDocs.length = 0; - docs.map(d => this.props.CollectionView.SelectedDocs.push(d.Id)); - } - - public getActiveDocuments = () => { - var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1); - const lvalue = this.props.Document.GetT>(this.props.fieldKey, ListField); - let active: Document[] = []; - if (lvalue && lvalue != FieldWaiting) { - lvalue.Data.map(doc => { - var page = doc.GetNumber(KeyStore.Page, -1); - if (page == curPage || page == -1) { - active.push(doc); - } - }) - } - - return active; - } - - //determines whether the blinking cursor for indicating whether a text will be made on key down is visible - @observable public PreviewCursorVisible: boolean = false; - @observable public MarqueeVisible = false; - @observable public DownX: number = 0; - @observable public DownY: number = 0; - @observable private _lastX: number = 0; - @observable private _lastY: number = 0; - - @computed get panX(): number { return this.props.Document.GetNumber(KeyStore.PanX, 0) } - @computed get panY(): number { return this.props.Document.GetNumber(KeyStore.PanY, 0) } - @computed get scale(): number { return this.props.Document.GetNumber(KeyStore.Scale, 1); } - @computed get isAnnotationOverlay() { return this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's? - @computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); } - @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); } - @computed get zoomScaling() { return this.props.Document.GetNumber(KeyStore.Scale, 1); } - @computed get centeringShiftX() { return !this.props.Document.GetNumber(KeyStore.NativeWidth, 0) ? this.props.panelWidth() / 2 : 0; } // shift so pan position is at center of window for non-overlay collections - @computed get centeringShiftY() { return !this.props.Document.GetNumber(KeyStore.NativeHeight, 0) ? this.props.panelHeight() / 2 : 0; }// shift so pan position is at center of window for non-overlay collections - - @undoBatch - @action - drop = (e: Event, de: DragManager.DropEvent) => { - if (super.drop(e, de)) { - if (de.data instanceof DragManager.DocumentDragData) { - let screenX = de.x - (de.data.xOffset as number || 0); - let screenY = de.y - (de.data.yOffset as number || 0); - const [x, y] = this.getTransform().transformPoint(screenX, screenY); - de.data.droppedDocument.SetNumber(KeyStore.X, x); - de.data.droppedDocument.SetNumber(KeyStore.Y, y); - if (!de.data.droppedDocument.GetNumber(KeyStore.Width, 0)) { - de.data.droppedDocument.SetNumber(KeyStore.Width, 300); - de.data.droppedDocument.SetNumber(KeyStore.Height, 300); - } - this.bringToFront(de.data.droppedDocument); - } - return true; - } - return false; - } - - - @action - cleanupInteractions = () => { - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - this.MarqueeVisible = false; - } - - @action - onPointerDown = (e: React.PointerEvent): void => { - this.PreviewCursorVisible = false; - if ((e.button === 2 && this.props.active() && (!this.isAnnotationOverlay || this.zoomScaling != 1)) || e.button == 0) { - document.removeEventListener("pointermove", this.onPointerMove); - document.addEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - document.addEventListener("pointerup", this.onPointerUp); - this._lastX = this.DownX = e.pageX; - this._lastY = this.DownY = e.pageY; - } - } - - @action - onPointerUp = (e: PointerEvent): void => { - e.stopPropagation(); - - if (Math.abs(this.DownX - e.clientX) < 4 && Math.abs(this.DownY - e.clientY) < 4) { - //show preview text cursor on tap - this.PreviewCursorVisible = true; - //select is not already selected - if (!this.props.isSelected()) { - this.props.select(false); - } - } - this.cleanupInteractions(); - } - - @action - onPointerMove = (e: PointerEvent): void => { - if (!e.cancelBubble && this.props.active()) { - if (e.buttons == 1 && !e.altKey && !e.metaKey) { - this.MarqueeVisible = true; - } - if (this.MarqueeVisible) { - e.stopPropagation(); - e.preventDefault(); - } - else 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 [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); - this.SetPan(x - dx, y - dy); - this._lastX = e.pageX; - this._lastY = e.pageY; - e.stopPropagation(); - e.preventDefault(); - } - } - } - - @action - onPointerWheel = (e: React.WheelEvent): void => { - this.props.select(false); - e.stopPropagation(); - e.preventDefault(); - let coefficient = 1000; - - if (e.ctrlKey) { - var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0); - var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0); - const coefficient = 1000; - let deltaScale = (1 - (e.deltaY / coefficient)); - this.props.Document.SetNumber(KeyStore.NativeWidth, nativeWidth * deltaScale); - this.props.Document.SetNumber(KeyStore.NativeHeight, nativeHeight * deltaScale); - e.stopPropagation(); - e.preventDefault(); - } else { - // if (modes[e.deltaMode] == 'pixels') coefficient = 50; - // else if (modes[e.deltaMode] == 'lines') coefficient = 1000; // This should correspond to line-height?? - let transform = this.getTransform(); - - let deltaScale = (1 - (e.deltaY / coefficient)); - if (deltaScale * this.zoomScaling < 1 && this.isAnnotationOverlay) - deltaScale = 1 / this.zoomScaling; - let [x, y] = transform.transformPoint(e.clientX, e.clientY); - - let localTransform = this.getLocalTransform() - localTransform = localTransform.inverse().scaleAbout(deltaScale, x, y) - // console.log(localTransform) - - this.props.Document.SetNumber(KeyStore.Scale, localTransform.Scale); - this.SetPan(-localTransform.TranslateX / localTransform.Scale, -localTransform.TranslateY / localTransform.Scale); - } - } - - @action - private SetPan(panX: number, panY: number) { - 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)); - this.props.Document.SetNumber(KeyStore.PanX, this.isAnnotationOverlay ? newPanX : panX); - this.props.Document.SetNumber(KeyStore.PanY, this.isAnnotationOverlay ? newPanY : panY); - } - - @action - onDrop = (e: React.DragEvent): void => { - var pt = this.getTransform().transformPoint(e.pageX, e.pageY); - super.onDrop(e, { x: pt[0], y: pt[1] }); - } - - onDragOver = (): void => { - } - - @action - bringToFront(doc: Document) { - const { fieldKey: fieldKey, Document: Document } = this.props; - - const value: Document[] = Document.GetList(fieldKey, []).slice(); - value.sort((doc1, doc2) => { - if (doc1 === doc) { - return 1; - } - if (doc2 === doc) { - return -1; - } - return doc1.GetNumber(KeyStore.ZIndex, 0) - doc2.GetNumber(KeyStore.ZIndex, 0); - }).map((doc, index) => { - doc.SetNumber(KeyStore.ZIndex, index + 1) - }); - } - - @computed get backgroundLayout(): string | undefined { - let field = this.props.Document.GetT(KeyStore.BackgroundLayout, TextField); - if (field && field !== FieldWaiting) { - return field.Data; - } - } - @computed get overlayLayout(): string | undefined { - let field = this.props.Document.GetT(KeyStore.OverlayLayout, TextField); - if (field && field !== FieldWaiting) { - return field.Data; - } - } - - focusDocument = (doc: Document) => { - let x = doc.GetNumber(KeyStore.X, 0) + doc.GetNumber(KeyStore.Width, 0) / 2; - let y = doc.GetNumber(KeyStore.Y, 0) + doc.GetNumber(KeyStore.Height, 0) / 2; - this.SetPan(x, y); - this.props.focus(this.props.Document); - } - - getDocumentViewProps(document: Document): DocumentViewProps { - return { - Document: document, - AddDocument: this.props.addDocument, - RemoveDocument: this.props.removeDocument, - ScreenToLocalTransform: this.getTransform, - isTopMost: false, - SelectOnLoad: document.Id == this._selectOnLoaded, - PanelWidth: document.Width, - PanelHeight: document.Height, - ContentScaling: this.noScaling, - ContainingCollectionView: this.props.CollectionView, - focus: this.focusDocument - } - } - - @computed - get views() { - var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1); - const lvalue = this.props.Document.GetT>(this.props.fieldKey, ListField); - if (lvalue && lvalue != FieldWaiting) { - return lvalue.Data.map(doc => { - if (!doc) return null; - var page = doc.GetNumber(KeyStore.Page, 0); - return (page != curPage && page != 0) ? (null) : - (); - }) - } - return null; - } - - @computed - get backgroundView() { - return !this.backgroundLayout ? (null) : - ( false} select={() => { }} />); - } - @computed - get overlayView() { - return !this.overlayLayout ? (null) : - ( false} select={() => { }} />); - } - - getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform()) - getMarqueeTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH) - getLocalTransform = (): Transform => Transform.Identity.scale(1 / this.scale).translate(this.panX, this.panY); - noScaling = () => 1; - - //when focus is lost, this will remove the preview cursor - @action - onBlur = (): void => { - this.PreviewCursorVisible = false; - } - - private crosshairs?: HTMLCanvasElement; - drawCrosshairs = (backgroundColor: string) => { - if (this.crosshairs) { - let c = this.crosshairs; - let ctx = c.getContext('2d'); - if (ctx) { - ctx.fillStyle = backgroundColor; - ctx.fillRect(0, 0, 20, 20); - - ctx.fillStyle = "black"; - ctx.lineWidth = 0.5; - - ctx.beginPath(); - - ctx.moveTo(10, 0); - ctx.lineTo(10, 8); - - ctx.moveTo(10, 20); - ctx.lineTo(10, 12); - - ctx.moveTo(0, 10); - ctx.lineTo(8, 10); - - ctx.moveTo(20, 10); - ctx.lineTo(12, 10); - - ctx.stroke(); - - // ctx.font = "10px Arial"; - // ctx.fillText(CurrentUserUtils.email[0].toUpperCase(), 10, 10); - } - } - } - - render() { - let [dx, dy] = [this.centeringShiftX, this.centeringShiftY]; - - const panx: number = -this.props.Document.GetNumber(KeyStore.PanX, 0); - const pany: number = -this.props.Document.GetNumber(KeyStore.PanY, 0); - // const panx: number = this.props.Document.GetNumber(KeyStore.PanX, 0) + this.centeringShiftX; - // const pany: number = this.props.Document.GetNumber(KeyStore.PanY, 0) + this.centeringShiftY; - // console.log("center:", this.getLocalTransform().transformPoint(this.centeringShiftX, this.centeringShiftY)); - - return ( -
super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY))} - onWheel={this.onPointerWheel} - onDrop={this.onDrop.bind(this)} - onDragOver={this.onDragOver} - onBlur={this.onBlur} - style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}// , zIndex: !this.props.isTopMost ? -1 : undefined }} - tabIndex={0} - ref={this.createDropTarget}> -
- {this.backgroundView} - - - {this.views} - - {super.getCursors().map(entry => { - if (entry.Data.length > 0) { - let id = entry.Data[0][0]; - let email = entry.Data[0][1]; - let point = entry.Data[1]; - this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22") - return ( -
- { if (el) this.crosshairs = el }} - width={20} - height={20} - style={{ - position: 'absolute', - width: "20px", - height: "20px", - opacity: 0.5, - borderRadius: "50%", - border: "2px solid black" - }} - /> -

{email[0].toUpperCase()}

-
- ); - } - })} -
- - {this.overlayView} - -
- ); - } -} - -@observer -export class LinksView extends React.Component { - @observable _connections: { a: Document, b: Document, l: Document }[] = []; - - private _reactionDisposer: Opt; - - componentDidMount() { - this._reactionDisposer = reaction(() => DocumentManager.Instance.DocumentViews.map(dv => [ - dv.props.Document.Get(KeyStore.AnnotationOn, false), - dv.props.Document.Get(KeyStore.LinkedFromDocs, false), - dv.props.Document.Get(KeyStore.LinkedToDocs, false) - ]), () => runInAction(() => this._connections = this.findPairs()), { fireImmediately: true }); - } - componentWillUnmount() { - if (this._reactionDisposer) { - this._reactionDisposer(); - } - this._reactionDisposer = undefined; - } - - filtered(views: DocumentView[]) { - return views.filter(sv => - sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document == this.props.Document - ); - } - - findPairs() { - return DocumentManager.Instance.DocumentViews.reduce((pairs, dv) => { - untracked(() => { - let srcViews = [dv]; - let srcAnnot = dv.props.Document.GetT(KeyStore.AnnotationOn, Document); - if (srcAnnot && srcAnnot != FieldWaiting && srcAnnot instanceof Document) { - srcViews = DocumentManager.Instance.getDocumentViews(srcAnnot.GetPrototype() as Document) - } - let linksList = dv.props.Document.GetT(KeyStore.LinkedToDocs, ListField); - if (linksList && linksList != FieldWaiting && linksList.Data.length) { - pairs.push(...linksList.Data.reduce((pairs, link) => { - if (link instanceof Document) { - let linkToDoc = link.GetT(KeyStore.LinkedToDocs, Document); - if (linkToDoc && linkToDoc != FieldWaiting) { - DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => { - let targetViews = [docView1]; - let docAnnot = docView1.props.Document.GetT(KeyStore.AnnotationOn, Document); - if (docAnnot && docAnnot != FieldWaiting && docAnnot instanceof Document) { - targetViews = DocumentManager.Instance.getDocumentViews(docAnnot.GetPrototype() as Document) - } - this.filtered(targetViews).map(tv => this.filtered(srcViews).map(sv => - pairs.push({ a: sv.props.Document, b: tv.props.Document, l: link }))) - }) - } - } - return pairs; - }, [] as { a: Document, b: Document, l: Document }[])); - } - }) - return pairs; - }, [] as { a: Document, b: Document, l: Document }[]); - } - - onPointerDown(e: React.PointerEvent) { - let line = (e.nativeEvent as any).path[0]; - line.style.stroke = "red"; - Server.GetField(line.id, action((f: Opt) => { - if (f instanceof Document) { - console.log(f.Title); - } - })); - } - - @computed - get uniqueConnections() { - return this._connections.reduce((y, t) => { - if (!y.reduce((found, yi) => { - if (yi.a == t.a && yi.b == t.b) { - if (yi.l != t.l) - yi.n++; - return true; - } - return found; - }, false)) { - y.push({ a: t.a, b: t.b, l: t.l, n: 1 }); - } - return y - }, [] as { a: Document, b: Document, l: Document, n: number }[]); - } - - render() { - if (!this._connections.length) - return (null); - return - {this.uniqueConnections.map(c => { - let x1 = c.a.GetNumber(KeyStore.X, 0) + c.a.GetNumber(KeyStore.Width, 0) / 2; - let y1 = c.a.GetNumber(KeyStore.Y, 0) + c.a.GetNumber(KeyStore.Height, 0) / 2; - let x2 = c.b.GetNumber(KeyStore.X, 0) + c.b.GetNumber(KeyStore.Width, 0) / 2; - let y2 = c.b.GetNumber(KeyStore.Y, 0) + c.b.GetNumber(KeyStore.Height, 0) / 2; - return ( - - ) - })} - - } -} \ No newline at end of file diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index c804085df..a1498e0ae 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -7,7 +7,7 @@ import { ContextMenu } from "../ContextMenu"; import React = require("react"); import { KeyStore } from "../../../fields/KeyStore"; import { NumberField } from "../../../fields/NumberField"; -import { CollectionFreeFormView } from "./CollectionFreeFormView"; +import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView"; import { CollectionDockingView } from "./CollectionDockingView"; import { CollectionSchemaView } from "./CollectionSchemaView"; import { CollectionViewProps } from "./CollectionViewBase"; @@ -104,6 +104,11 @@ export class CollectionView extends React.Component { break; } } + doc.GetTAsync(KeyStore.AnnotationOn, Document).then((annotationOn) => { + if (annotationOn == props.Document) { + doc.Set(KeyStore.AnnotationOn, undefined, true); + } + }) if (index !== -1) { value.splice(index, 1) diff --git a/src/client/views/collections/MarqueeView.scss b/src/client/views/collections/MarqueeView.scss deleted file mode 100644 index 6d9a79344..000000000 --- a/src/client/views/collections/MarqueeView.scss +++ /dev/null @@ -1,8 +0,0 @@ - -.marqueeView { - border-style: dashed; - box-sizing: border-box; - position: absolute; - border-width: 1px; - border-color: black; -} \ No newline at end of file diff --git a/src/client/views/collections/MarqueeView.tsx b/src/client/views/collections/MarqueeView.tsx deleted file mode 100644 index b48ad9c64..000000000 --- a/src/client/views/collections/MarqueeView.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import { action, IReactionDisposer, observable, reaction } from "mobx"; -import { observer } from "mobx-react"; -import { Document } from "../../../fields/Document"; -import { FieldWaiting, Opt } from "../../../fields/Field"; -import { KeyStore } from "../../../fields/KeyStore"; -import { Documents } from "../../documents/Documents"; -import { SelectionManager } from "../../util/SelectionManager"; -import { Transform } from "../../util/Transform"; -import { CollectionFreeFormView } from "./CollectionFreeFormView"; -import "./MarqueeView.scss"; -import React = require("react"); -import { InkField, StrokeData } from "../../../fields/InkField"; -import { Utils } from "../../../Utils"; -import { InkingCanvas } from "../InkingCanvas"; - -interface MarqueeViewProps { - getMarqueeTransform: () => Transform; - getTransform: () => Transform; - container: CollectionFreeFormView; - addDocument: (doc: Document, allowDuplicates: false) => boolean; - activeDocuments: () => Document[]; - selectDocuments: (docs: Document[]) => void; - removeDocument: (doc: Document) => boolean; -} - -@observer -export class MarqueeView extends React.Component -{ - private _reactionDisposer: Opt; - - @observable _lastX: number = 0; - @observable _lastY: number = 0; - @observable _downX: number = 0; - @observable _downY: number = 0; - - componentDidMount() { - this._reactionDisposer = reaction( - () => this.props.container.MarqueeVisible, - (visible: boolean) => this.onPointerDown(visible, this.props.container.DownX, this.props.container.DownY)) - } - componentWillUnmount() { - if (this._reactionDisposer) { - this._reactionDisposer(); - } - this.cleanupInteractions(); - } - - @action - cleanupInteractions = () => { - document.removeEventListener("pointermove", this.onPointerMove, true) - document.removeEventListener("pointerup", this.onPointerUp, true); - document.removeEventListener("keydown", this.marqueeCommand, true); - } - - @action - onPointerDown = (visible: boolean, downX: number, downY: number): void => { - if (visible) { - this._downX = this._lastX = downX; - this._downY = this._lastY = downY; - document.addEventListener("pointermove", this.onPointerMove, true) - document.addEventListener("pointerup", this.onPointerUp, true); - document.addEventListener("keydown", this.marqueeCommand, true); - } - } - - @action - onPointerMove = (e: PointerEvent): void => { - this._lastX = e.pageX; - this._lastY = e.pageY; - } - - @action - onPointerUp = (e: PointerEvent): void => { - this.cleanupInteractions(); - if (!e.shiftKey) { - SelectionManager.DeselectAll(); - } - this.props.selectDocuments(this.marqueeSelect()); - } - - intersectRect(r1: { left: number, top: number, width: number, height: number }, - r2: { left: number, top: number, width: number, height: number }) { - return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top); - } - - get Bounds() { - let left = this._downX < this._lastX ? this._downX : this._lastX; - let top = this._downY < this._lastY ? this._downY : this._lastY; - let topLeft = this.props.getTransform().transformPoint(left, top); - let size = this.props.getTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); - return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) } - } - - @action - marqueeCommand = (e: KeyboardEvent) => { - if (e.key == "Backspace" || e.key == "Delete") { - this.marqueeSelect().map(d => this.props.removeDocument(d)); - this.props.container.props.Document.SetData(KeyStore.Ink, this.marqueeInkSelect(false), InkField); - this.cleanupInteractions(); - } - if (e.key == "c") { - let bounds = this.Bounds; - let selected = this.marqueeSelect().map(d => { - this.props.removeDocument(d); - d.SetNumber(KeyStore.X, d.GetNumber(KeyStore.X, 0) - bounds.left - bounds.width / 2); - d.SetNumber(KeyStore.Y, d.GetNumber(KeyStore.Y, 0) - bounds.top - bounds.height / 2); - d.SetNumber(KeyStore.Page, 0); - d.SetText(KeyStore.Title, "" + d.GetNumber(KeyStore.Width, 0) + " " + d.GetNumber(KeyStore.Height, 0)); - return d; - }); - let liftedInk = this.marqueeInkSelect(true); - this.props.container.props.Document.SetData(KeyStore.Ink, this.marqueeInkSelect(false), InkField); - //setTimeout(() => { - let newCollection = Documents.FreeformDocument(selected, { - x: bounds.left, - y: bounds.top, - panx: 0, - pany: 0, - width: bounds.width, - height: bounds.height, - backgroundColor: "Transparent", - ink: liftedInk, - title: "a nested collection" - }); - this.props.addDocument(newCollection, false); - // }, 100); - this.cleanupInteractions(); - } - } - marqueeInkSelect(select: boolean) { - let selRect = this.Bounds; - let centerShiftX = 0 - (selRect.left + selRect.width / 2); // moves each point by the offset that shifts the selection's center to the origin. - let centerShiftY = 0 - (selRect.top + selRect.height / 2); - let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField); - if (ink && ink != FieldWaiting && ink.Data) { - let idata = new Map(); - ink.Data.forEach((value: StrokeData, key: string, map: any) => { - let inside = InkingCanvas.IntersectStrokeRect(value, selRect); - if (inside && select) { - idata.set(key, - { - pathData: value.pathData.map(val => { return { x: val.x + centerShiftX, y: val.y + centerShiftY } }), - color: value.color, - width: value.width, - tool: value.tool, - page: -1 - }); - } else if (!inside && !select) { - idata.set(key, value); - } - }) - return idata; - } - } - - marqueeSelect() { - let selRect = this.Bounds; - let selection: Document[] = []; - this.props.activeDocuments().map(doc => { - var x = doc.GetNumber(KeyStore.X, 0); - var y = doc.GetNumber(KeyStore.Y, 0); - var w = doc.GetNumber(KeyStore.Width, 0); - var h = doc.GetNumber(KeyStore.Height, 0); - if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) - selection.push(doc) - }) - return selection; - } - - render() { - let p = this.props.getMarqueeTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY); - let v = this.props.getMarqueeTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); - return (!this.props.container.MarqueeVisible ? (null) :
); - } -} \ No newline at end of file diff --git a/src/client/views/collections/PreviewCursor.scss b/src/client/views/collections/PreviewCursor.scss deleted file mode 100644 index a797411f6..000000000 --- a/src/client/views/collections/PreviewCursor.scss +++ /dev/null @@ -1,18 +0,0 @@ - -.previewCursor { - color: black; - position: absolute; - transform-origin: left top; - pointer-events: none; -} - -//this is an animation for the blinking cursor! -@keyframes blink { - 0% {opacity: 0} - 49%{opacity: 0} - 50% {opacity: 1} -} - -#previewCursor { - animation: blink 1s infinite; -} \ No newline at end of file diff --git a/src/client/views/collections/PreviewCursor.tsx b/src/client/views/collections/PreviewCursor.tsx deleted file mode 100644 index 8ae59a83a..000000000 --- a/src/client/views/collections/PreviewCursor.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { action, IReactionDisposer, observable, reaction } from "mobx"; -import { observer } from "mobx-react"; -import { Document } from "../../../fields/Document"; -import { Opt } from "../../../fields/Field"; -import { Documents } from "../../documents/Documents"; -import { Transform } from "../../util/Transform"; -import { CollectionFreeFormView } from "./CollectionFreeFormView"; -import "./PreviewCursor.scss"; -import React = require("react"); - - -export interface PreviewCursorProps { - getTransform: () => Transform; - container: CollectionFreeFormView; - addLiveTextDocument: (doc: Document) => void; -} - -@observer -export class PreviewCursor extends React.Component { - private _reactionDisposer: Opt; - - @observable _lastX: number = 0; - @observable _lastY: number = 0; - - componentDidMount() { - this._reactionDisposer = reaction( - () => this.props.container.PreviewCursorVisible, - (visible: boolean) => this.onCursorPlaced(visible, this.props.container.DownX, this.props.container.DownY)) - } - componentWillUnmount() { - if (this._reactionDisposer) { - this._reactionDisposer(); - } - this.cleanupInteractions(); - } - - - @action - cleanupInteractions = () => { - document.removeEventListener("keypress", this.onKeyPress, false); - } - - @action - onCursorPlaced = (visible: boolean, downX: number, downY: number): void => { - if (visible) { - document.addEventListener("keypress", this.onKeyPress, false); - this._lastX = downX; - this._lastY = downY; - } else - this.cleanupInteractions(); - } - - @action - onKeyPress = (e: KeyboardEvent) => { - // Mixing events between React and Native is finicky. In FormattedTextBox, we set the - // DASHFormattedTextBoxHandled flag when a text box consumes a key press so that we can ignore - // the keyPress here. - //if not these keys, make a textbox if preview cursor is active! - if (!e.ctrlKey && !e.altKey && !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) { - //make textbox and add it to this collection - let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY); - let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "typed text" }); - this.props.addLiveTextDocument(newBox); - e.stopPropagation(); - } - } - - render() { - //get local position and place cursor there! - let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY); - return ( - !this.props.container.PreviewCursorVisible ? (null) : -
I
) - - } -} \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss new file mode 100644 index 000000000..3b2f79be1 --- /dev/null +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss @@ -0,0 +1,6 @@ +.collectionfreeformlinkview-linkLine { + stroke: black; + stroke-width: 3; + transform: translate(10000px,10000px); + pointer-events: all; +} \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx new file mode 100644 index 000000000..e84f0c5ad --- /dev/null +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -0,0 +1,37 @@ +import { observer } from "mobx-react"; +import { Document } from "../../../../fields/Document"; +import { KeyStore } from "../../../../fields/KeyStore"; +import { Utils } from "../../../../Utils"; +import "./CollectionFreeFormLinkView.scss"; +import React = require("react"); +import v5 = require("uuid/v5"); + +export interface CollectionFreeFormLinkViewProps { + A: Document; + B: Document; + LinkDocs: Document[]; +} + +@observer +export class CollectionFreeFormLinkView extends React.Component { + + onPointerDown = (e: React.PointerEvent) => { + this.props.LinkDocs.map(l => + console.log("Link:" + l.Title)); + } + render() { + let l = this.props.LinkDocs; + let a = this.props.A; + let b = this.props.B; + let x1 = a.GetNumber(KeyStore.X, 0) + a.GetNumber(KeyStore.Width, 0) / 2; + let y1 = a.GetNumber(KeyStore.Y, 0) + a.GetNumber(KeyStore.Height, 0) / 2; + let x2 = b.GetNumber(KeyStore.X, 0) + b.GetNumber(KeyStore.Width, 0) / 2; + let y2 = b.GetNumber(KeyStore.Y, 0) + b.GetNumber(KeyStore.Height, 0) / 2; + return ( + + ) + } +} \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss new file mode 100644 index 000000000..9af0df7f5 --- /dev/null +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss @@ -0,0 +1,7 @@ +.collectionfreeformlinksview-svgCanvas{ + transform: translate(-10000px,-10000px); + position: absolute; + width: 20000px; + height: 20000px; + pointer-events: none; + } \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx new file mode 100644 index 000000000..4f28f43eb --- /dev/null +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -0,0 +1,56 @@ +import { computed } from "mobx"; +import { observer } from "mobx-react"; +import { Document } from "../../../../fields/Document"; +import { FieldWaiting } from "../../../../fields/Field"; +import { KeyStore } from "../../../../fields/KeyStore"; +import { Utils } from "../../../../Utils"; +import { DocumentManager } from "../../../util/DocumentManager"; +import { DocumentView } from "../../nodes/DocumentView"; +import { CollectionViewProps } from "../CollectionViewBase"; +import "./CollectionFreeFormLinksView.scss"; +import React = require("react"); +import v5 = require("uuid/v5"); +import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView"; + +@observer +export class CollectionFreeFormLinksView extends React.Component { + + documentAnchors(view: DocumentView) { + let equalViews = [view]; + let containerDoc = view.props.Document.GetT(KeyStore.AnnotationOn, Document); + if (containerDoc && containerDoc != FieldWaiting && containerDoc instanceof Document) { + equalViews = DocumentManager.Instance.getDocumentViews(containerDoc.GetPrototype() as Document) + } + return equalViews.filter(sv => sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document == this.props.Document); + } + + @computed + get uniqueConnections() { + return DocumentManager.Instance.LinkedDocumentViews.reduce((drawnPairs, connection) => { + let srcViews = this.documentAnchors(connection.a); + let targetViews = this.documentAnchors(connection.b); + let possiblePairs: { a: Document, b: Document, }[] = []; + srcViews.map(sv => targetViews.map(tv => possiblePairs.push({ a: sv.props.Document, b: tv.props.Document }))); + possiblePairs.map(possiblePair => { + if (!drawnPairs.reduce((found, drawnPair) => { + let match = (possiblePair.a == drawnPair.a && possiblePair.b == drawnPair.b); + if (match) { + if (!drawnPair.l.reduce((found, link) => found || link.Id == connection.l.Id, false)) + drawnPair.l.push(connection.l); + } + return match || found; + }, false)) { + drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] as Document[] }); + } + }) + return drawnPairs + }, [] as { a: Document, b: Document, l: Document[] }[]); + } + + render() { + return ( + + {this.uniqueConnections.map(c => )} + ); + } +} \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss new file mode 100644 index 000000000..9c5e98005 --- /dev/null +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -0,0 +1,95 @@ +@import "../../global_variables"; + +.collectionfreeformview-container { + .collectionfreeformview > .jsx-parser { + position: absolute; + height: 100%; + width: 100%; + } + + .inking-canvas { + transform-origin: 50000px 50000px; + } + //nested freeform views + // .collectionfreeformview-container { + // background-image: linear-gradient(to right, $light-color-secondary 1px, transparent 1px), + // linear-gradient(to bottom, $light-color-secondary 1px, transparent 1px); + // background-size: 30px 30px; + // } + + box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw; + border: 0px solid $light-color-secondary; + border-radius: $border-radius; + box-sizing: border-box; + position: relative; + overflow: hidden; + top: 0; + left: 0; + width: 100%; + height: 100%; + .collectionfreeformview { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + .inking-canvas { + transform-origin: 50000px 50000px; + } + } +} +.collectionfreeformview-overlay { + .collectionfreeformview > .jsx-parser { + position: absolute; + height: 100%; + } + .formattedTextBox-cont { + background: $light-color-secondary; + } + + .inking-canvas { + transform-origin: 50000px 50000px; + } + + opacity: 0.99; + border: 0px solid transparent; + border-radius: $border-radius; + box-sizing: border-box; + position:relative; + overflow: hidden; + top: 0; + left: 0; + width: 100%; + height: 100%; + .collectionfreeformview { + .formattedTextBox-cont { + background:yellow; + } + } +} + +// selection border...? +.border { + border-style: solid; + box-sizing: border-box; + width: 98%; + height: 98%; + border-radius: $border-radius; +} + +//this is an animation for the blinking cursor! +@keyframes blink { + 0% { + opacity: 0; + } + 49% { + opacity: 0; + } + 50% { + opacity: 1; + } +} + +#prevCursor { + animation: blink 1s infinite; +} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx new file mode 100644 index 000000000..1f90b0d46 --- /dev/null +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -0,0 +1,416 @@ +import { action, computed, observable } 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 { TextField } from "../../../../fields/TextField"; +import { DragManager } from "../../../util/DragManager"; +import { Transform } from "../../../util/Transform"; +import { undoBatch } from "../../../util/UndoManager"; +import { InkingCanvas } from "../../InkingCanvas"; +import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; +import { DocumentContentsView } from "../../nodes/DocumentContentsView"; +import { DocumentViewProps } from "../../nodes/DocumentView"; +import { COLLECTION_BORDER_WIDTH } from "../CollectionView"; +import { CollectionViewBase } from "../CollectionViewBase"; +import { MarqueeView } from "./MarqueeView"; +import { PreviewCursor } from "./PreviewCursor"; +import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView"; +import "./CollectionFreeFormView.scss"; +import React = require("react"); +import v5 = require("uuid/v5"); + +@observer +export class CollectionFreeFormView extends CollectionViewBase { + public _canvasRef = React.createRef(); + private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type) + + public addLiveTextBox = (newBox: Document) => { + // mark this collection so that when the text box is created we can send it the SelectOnLoad prop to focus itself + this._selectOnLoaded = newBox.Id; + //set text to be the typed key and get focus on text box + this.props.addDocument(newBox, false); + //remove cursor from screen + this.PreviewCursorVisible = false; + } + + public selectDocuments = (docs: Document[]) => { + this.props.CollectionView.SelectedDocs.length = 0; + docs.map(d => this.props.CollectionView.SelectedDocs.push(d.Id)); + } + + public getActiveDocuments = () => { + var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1); + const lvalue = this.props.Document.GetT>(this.props.fieldKey, ListField); + let active: Document[] = []; + if (lvalue && lvalue != FieldWaiting) { + lvalue.Data.map(doc => { + var page = doc.GetNumber(KeyStore.Page, -1); + if (page == curPage || page == -1) { + active.push(doc); + } + }) + } + + return active; + } + + //determines whether the blinking cursor for indicating whether a text will be made on key down is visible + @observable public PreviewCursorVisible: boolean = false; + @observable public MarqueeVisible = false; + @observable public DownX: number = 0; + @observable public DownY: number = 0; + @observable private _lastX: number = 0; + @observable private _lastY: number = 0; + + @computed get panX(): number { return this.props.Document.GetNumber(KeyStore.PanX, 0) } + @computed get panY(): number { return this.props.Document.GetNumber(KeyStore.PanY, 0) } + @computed get scale(): number { return this.props.Document.GetNumber(KeyStore.Scale, 1); } + @computed get isAnnotationOverlay() { return this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's? + @computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); } + @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); } + @computed get zoomScaling() { return this.props.Document.GetNumber(KeyStore.Scale, 1); } + @computed get centeringShiftX() { return !this.props.Document.GetNumber(KeyStore.NativeWidth, 0) ? this.props.panelWidth() / 2 : 0; } // shift so pan position is at center of window for non-overlay collections + @computed get centeringShiftY() { return !this.props.Document.GetNumber(KeyStore.NativeHeight, 0) ? this.props.panelHeight() / 2 : 0; }// shift so pan position is at center of window for non-overlay collections + + @undoBatch + @action + drop = (e: Event, de: DragManager.DropEvent) => { + if (super.drop(e, de)) { + if (de.data instanceof DragManager.DocumentDragData) { + let screenX = de.x - (de.data.xOffset as number || 0); + let screenY = de.y - (de.data.yOffset as number || 0); + const [x, y] = this.getTransform().transformPoint(screenX, screenY); + de.data.droppedDocument.SetNumber(KeyStore.X, x); + de.data.droppedDocument.SetNumber(KeyStore.Y, y); + if (!de.data.droppedDocument.GetNumber(KeyStore.Width, 0)) { + de.data.droppedDocument.SetNumber(KeyStore.Width, 300); + de.data.droppedDocument.SetNumber(KeyStore.Height, 300); + } + this.bringToFront(de.data.droppedDocument); + } + return true; + } + return false; + } + + + @action + cleanupInteractions = () => { + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + this.MarqueeVisible = false; + } + + @action + onPointerDown = (e: React.PointerEvent): void => { + this.PreviewCursorVisible = false; + if ((e.button === 2 && this.props.active() && (!this.isAnnotationOverlay || this.zoomScaling != 1)) || e.button == 0) { + document.removeEventListener("pointermove", this.onPointerMove); + document.addEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + document.addEventListener("pointerup", this.onPointerUp); + this._lastX = this.DownX = e.pageX; + this._lastY = this.DownY = e.pageY; + } + } + + @action + onPointerUp = (e: PointerEvent): void => { + e.stopPropagation(); + + if (Math.abs(this.DownX - e.clientX) < 4 && Math.abs(this.DownY - e.clientY) < 4) { + //show preview text cursor on tap + this.PreviewCursorVisible = true; + //select is not already selected + if (!this.props.isSelected()) { + this.props.select(false); + } + } + this.cleanupInteractions(); + } + + @action + onPointerMove = (e: PointerEvent): void => { + if (!e.cancelBubble && this.props.active()) { + if (e.buttons == 1 && !e.altKey && !e.metaKey) { + this.MarqueeVisible = true; + } + if (this.MarqueeVisible) { + e.stopPropagation(); + e.preventDefault(); + } + else 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 [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); + this.SetPan(x - dx, y - dy); + this._lastX = e.pageX; + this._lastY = e.pageY; + e.stopPropagation(); + e.preventDefault(); + } + } + } + + @action + onPointerWheel = (e: React.WheelEvent): void => { + this.props.select(false); + e.stopPropagation(); + e.preventDefault(); + let coefficient = 1000; + + if (e.ctrlKey) { + var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0); + var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0); + const coefficient = 1000; + let deltaScale = (1 - (e.deltaY / coefficient)); + this.props.Document.SetNumber(KeyStore.NativeWidth, nativeWidth * deltaScale); + this.props.Document.SetNumber(KeyStore.NativeHeight, nativeHeight * deltaScale); + e.stopPropagation(); + e.preventDefault(); + } else { + // if (modes[e.deltaMode] == 'pixels') coefficient = 50; + // else if (modes[e.deltaMode] == 'lines') coefficient = 1000; // This should correspond to line-height?? + let transform = this.getTransform(); + + let deltaScale = (1 - (e.deltaY / coefficient)); + if (deltaScale * this.zoomScaling < 1 && this.isAnnotationOverlay) + deltaScale = 1 / this.zoomScaling; + let [x, y] = transform.transformPoint(e.clientX, e.clientY); + + let localTransform = this.getLocalTransform() + localTransform = localTransform.inverse().scaleAbout(deltaScale, x, y) + // console.log(localTransform) + + this.props.Document.SetNumber(KeyStore.Scale, localTransform.Scale); + this.SetPan(-localTransform.TranslateX / localTransform.Scale, -localTransform.TranslateY / localTransform.Scale); + } + } + + @action + private SetPan(panX: number, panY: number) { + 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)); + this.props.Document.SetNumber(KeyStore.PanX, this.isAnnotationOverlay ? newPanX : panX); + this.props.Document.SetNumber(KeyStore.PanY, this.isAnnotationOverlay ? newPanY : panY); + } + + @action + onDrop = (e: React.DragEvent): void => { + var pt = this.getTransform().transformPoint(e.pageX, e.pageY); + super.onDrop(e, { x: pt[0], y: pt[1] }); + } + + onDragOver = (): void => { + } + + @action + bringToFront(doc: Document) { + const { fieldKey: fieldKey, Document: Document } = this.props; + + const value: Document[] = Document.GetList(fieldKey, []).slice(); + value.sort((doc1, doc2) => { + if (doc1 === doc) { + return 1; + } + if (doc2 === doc) { + return -1; + } + return doc1.GetNumber(KeyStore.ZIndex, 0) - doc2.GetNumber(KeyStore.ZIndex, 0); + }).map((doc, index) => { + doc.SetNumber(KeyStore.ZIndex, index + 1) + }); + } + + @computed get backgroundLayout(): string | undefined { + let field = this.props.Document.GetT(KeyStore.BackgroundLayout, TextField); + if (field && field !== FieldWaiting) { + return field.Data; + } + } + @computed get overlayLayout(): string | undefined { + let field = this.props.Document.GetT(KeyStore.OverlayLayout, TextField); + if (field && field !== FieldWaiting) { + return field.Data; + } + } + + focusDocument = (doc: Document) => { + let x = doc.GetNumber(KeyStore.X, 0) + doc.GetNumber(KeyStore.Width, 0) / 2; + let y = doc.GetNumber(KeyStore.Y, 0) + doc.GetNumber(KeyStore.Height, 0) / 2; + this.SetPan(x, y); + this.props.focus(this.props.Document); + } + + getDocumentViewProps(document: Document): DocumentViewProps { + return { + Document: document, + AddDocument: this.props.addDocument, + RemoveDocument: this.props.removeDocument, + ScreenToLocalTransform: this.getTransform, + isTopMost: false, + SelectOnLoad: document.Id == this._selectOnLoaded, + PanelWidth: document.Width, + PanelHeight: document.Height, + ContentScaling: this.noScaling, + ContainingCollectionView: this.props.CollectionView, + focus: this.focusDocument + } + } + + @computed + get views() { + var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1); + const lvalue = this.props.Document.GetT>(this.props.fieldKey, ListField); + if (lvalue && lvalue != FieldWaiting) { + return lvalue.Data.map(doc => { + if (!doc) return null; + var page = doc.GetNumber(KeyStore.Page, 0); + return (page != curPage && page != 0) ? (null) : + (); + }) + } + return null; + } + + @computed + get backgroundView() { + return !this.backgroundLayout ? (null) : + ( false} select={() => { }} />); + } + @computed + get overlayView() { + return !this.overlayLayout ? (null) : + ( false} select={() => { }} />); + } + + getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform()) + getMarqueeTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH) + getLocalTransform = (): Transform => Transform.Identity.scale(1 / this.scale).translate(this.panX, this.panY); + noScaling = () => 1; + + //when focus is lost, this will remove the preview cursor + @action + onBlur = (): void => { + this.PreviewCursorVisible = false; + } + + private crosshairs?: HTMLCanvasElement; + drawCrosshairs = (backgroundColor: string) => { + if (this.crosshairs) { + let c = this.crosshairs; + let ctx = c.getContext('2d'); + if (ctx) { + ctx.fillStyle = backgroundColor; + ctx.fillRect(0, 0, 20, 20); + + ctx.fillStyle = "black"; + ctx.lineWidth = 0.5; + + ctx.beginPath(); + + ctx.moveTo(10, 0); + ctx.lineTo(10, 8); + + ctx.moveTo(10, 20); + ctx.lineTo(10, 12); + + ctx.moveTo(0, 10); + ctx.lineTo(8, 10); + + ctx.moveTo(20, 10); + ctx.lineTo(12, 10); + + ctx.stroke(); + + // ctx.font = "10px Arial"; + // ctx.fillText(CurrentUserUtils.email[0].toUpperCase(), 10, 10); + } + } + } + + render() { + let [dx, dy] = [this.centeringShiftX, this.centeringShiftY]; + + const panx: number = -this.props.Document.GetNumber(KeyStore.PanX, 0); + const pany: number = -this.props.Document.GetNumber(KeyStore.PanY, 0); + // const panx: number = this.props.Document.GetNumber(KeyStore.PanX, 0) + this.centeringShiftX; + // const pany: number = this.props.Document.GetNumber(KeyStore.PanY, 0) + this.centeringShiftY; + // console.log("center:", this.getLocalTransform().transformPoint(this.centeringShiftX, this.centeringShiftY)); + + return ( +
super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY))} + onWheel={this.onPointerWheel} + onDrop={this.onDrop.bind(this)} + onDragOver={this.onDragOver} + onBlur={this.onBlur} + style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}// , zIndex: !this.props.isTopMost ? -1 : undefined }} + tabIndex={0} + ref={this.createDropTarget}> +
+ {this.backgroundView} + + + {this.views} + + {super.getCursors().map(entry => { + if (entry.Data.length > 0) { + let id = entry.Data[0][0]; + let email = entry.Data[0][1]; + let point = entry.Data[1]; + this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22") + return ( +
+ { if (el) this.crosshairs = el }} + width={20} + height={20} + style={{ + position: 'absolute', + width: "20px", + height: "20px", + opacity: 0.5, + borderRadius: "50%", + border: "2px solid black" + }} + /> +

{email[0].toUpperCase()}

+
+ ); + } + })} +
+ + {this.overlayView} + +
+ ); + } +} \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss new file mode 100644 index 000000000..6d9a79344 --- /dev/null +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss @@ -0,0 +1,8 @@ + +.marqueeView { + border-style: dashed; + box-sizing: border-box; + position: absolute; + border-width: 1px; + border-color: black; +} \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx new file mode 100644 index 000000000..2692690dd --- /dev/null +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -0,0 +1,175 @@ +import { action, IReactionDisposer, observable, reaction } from "mobx"; +import { observer } from "mobx-react"; +import { Document } from "../../../../fields/Document"; +import { FieldWaiting, Opt } from "../../../../fields/Field"; +import { KeyStore } from "../../../../fields/KeyStore"; +import { Documents } from "../../../documents/Documents"; +import { SelectionManager } from "../../../util/SelectionManager"; +import { Transform } from "../../../util/Transform"; +import { CollectionFreeFormView } from "./CollectionFreeFormView"; +import "./MarqueeView.scss"; +import React = require("react"); +import { InkField, StrokeData } from "../../../../fields/InkField"; +import { Utils } from "../../../../Utils"; +import { InkingCanvas } from "../../InkingCanvas"; + +interface MarqueeViewProps { + getMarqueeTransform: () => Transform; + getTransform: () => Transform; + container: CollectionFreeFormView; + addDocument: (doc: Document, allowDuplicates: false) => boolean; + activeDocuments: () => Document[]; + selectDocuments: (docs: Document[]) => void; + removeDocument: (doc: Document) => boolean; +} + +@observer +export class MarqueeView extends React.Component +{ + private _reactionDisposer: Opt; + + @observable _lastX: number = 0; + @observable _lastY: number = 0; + @observable _downX: number = 0; + @observable _downY: number = 0; + + componentDidMount() { + this._reactionDisposer = reaction( + () => this.props.container.MarqueeVisible, + (visible: boolean) => this.onPointerDown(visible, this.props.container.DownX, this.props.container.DownY)) + } + componentWillUnmount() { + if (this._reactionDisposer) { + this._reactionDisposer(); + } + this.cleanupInteractions(); + } + + @action + cleanupInteractions = () => { + document.removeEventListener("pointermove", this.onPointerMove, true) + document.removeEventListener("pointerup", this.onPointerUp, true); + document.removeEventListener("keydown", this.marqueeCommand, true); + } + + @action + onPointerDown = (visible: boolean, downX: number, downY: number): void => { + if (visible) { + this._downX = this._lastX = downX; + this._downY = this._lastY = downY; + document.addEventListener("pointermove", this.onPointerMove, true) + document.addEventListener("pointerup", this.onPointerUp, true); + document.addEventListener("keydown", this.marqueeCommand, true); + } + } + + @action + onPointerMove = (e: PointerEvent): void => { + this._lastX = e.pageX; + this._lastY = e.pageY; + } + + @action + onPointerUp = (e: PointerEvent): void => { + this.cleanupInteractions(); + if (!e.shiftKey) { + SelectionManager.DeselectAll(); + } + this.props.selectDocuments(this.marqueeSelect()); + } + + intersectRect(r1: { left: number, top: number, width: number, height: number }, + r2: { left: number, top: number, width: number, height: number }) { + return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top); + } + + get Bounds() { + let left = this._downX < this._lastX ? this._downX : this._lastX; + let top = this._downY < this._lastY ? this._downY : this._lastY; + let topLeft = this.props.getTransform().transformPoint(left, top); + let size = this.props.getTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); + return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) } + } + + @action + marqueeCommand = (e: KeyboardEvent) => { + if (e.key == "Backspace" || e.key == "Delete") { + this.marqueeSelect().map(d => this.props.removeDocument(d)); + this.props.container.props.Document.SetData(KeyStore.Ink, this.marqueeInkSelect(false), InkField); + this.cleanupInteractions(); + } + if (e.key == "c") { + let bounds = this.Bounds; + let selected = this.marqueeSelect().map(d => { + this.props.removeDocument(d); + d.SetNumber(KeyStore.X, d.GetNumber(KeyStore.X, 0) - bounds.left - bounds.width / 2); + d.SetNumber(KeyStore.Y, d.GetNumber(KeyStore.Y, 0) - bounds.top - bounds.height / 2); + d.SetNumber(KeyStore.Page, 0); + d.SetText(KeyStore.Title, "" + d.GetNumber(KeyStore.Width, 0) + " " + d.GetNumber(KeyStore.Height, 0)); + return d; + }); + let liftedInk = this.marqueeInkSelect(true); + this.props.container.props.Document.SetData(KeyStore.Ink, this.marqueeInkSelect(false), InkField); + //setTimeout(() => { + let newCollection = Documents.FreeformDocument(selected, { + x: bounds.left, + y: bounds.top, + panx: 0, + pany: 0, + width: bounds.width, + height: bounds.height, + backgroundColor: "Transparent", + ink: liftedInk, + title: "a nested collection" + }); + this.props.addDocument(newCollection, false); + // }, 100); + this.cleanupInteractions(); + } + } + marqueeInkSelect(select: boolean) { + let selRect = this.Bounds; + let centerShiftX = 0 - (selRect.left + selRect.width / 2); // moves each point by the offset that shifts the selection's center to the origin. + let centerShiftY = 0 - (selRect.top + selRect.height / 2); + let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField); + if (ink && ink != FieldWaiting && ink.Data) { + let idata = new Map(); + ink.Data.forEach((value: StrokeData, key: string, map: any) => { + let inside = InkingCanvas.IntersectStrokeRect(value, selRect); + if (inside && select) { + idata.set(key, + { + pathData: value.pathData.map(val => { return { x: val.x + centerShiftX, y: val.y + centerShiftY } }), + color: value.color, + width: value.width, + tool: value.tool, + page: -1 + }); + } else if (!inside && !select) { + idata.set(key, value); + } + }) + return idata; + } + } + + marqueeSelect() { + let selRect = this.Bounds; + let selection: Document[] = []; + this.props.activeDocuments().map(doc => { + var x = doc.GetNumber(KeyStore.X, 0); + var y = doc.GetNumber(KeyStore.Y, 0); + var w = doc.GetNumber(KeyStore.Width, 0); + var h = doc.GetNumber(KeyStore.Height, 0); + if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) + selection.push(doc) + }) + return selection; + } + + render() { + let p = this.props.getMarqueeTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY); + let v = this.props.getMarqueeTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); + return (!this.props.container.MarqueeVisible ? (null) :
); + } +} \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.scss b/src/client/views/collections/collectionFreeForm/PreviewCursor.scss new file mode 100644 index 000000000..a797411f6 --- /dev/null +++ b/src/client/views/collections/collectionFreeForm/PreviewCursor.scss @@ -0,0 +1,18 @@ + +.previewCursor { + color: black; + position: absolute; + transform-origin: left top; + pointer-events: none; +} + +//this is an animation for the blinking cursor! +@keyframes blink { + 0% {opacity: 0} + 49%{opacity: 0} + 50% {opacity: 1} +} + +#previewCursor { + animation: blink 1s infinite; +} \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx new file mode 100644 index 000000000..95364f04b --- /dev/null +++ b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx @@ -0,0 +1,76 @@ +import { action, IReactionDisposer, observable, reaction } from "mobx"; +import { observer } from "mobx-react"; +import { Document } from "../../../../fields/Document"; +import { Opt } from "../../../../fields/Field"; +import { Documents } from "../../../documents/Documents"; +import { Transform } from "../../../util/Transform"; +import { CollectionFreeFormView } from "./CollectionFreeFormView"; +import "./PreviewCursor.scss"; +import React = require("react"); + + +export interface PreviewCursorProps { + getTransform: () => Transform; + container: CollectionFreeFormView; + addLiveTextDocument: (doc: Document) => void; +} + +@observer +export class PreviewCursor extends React.Component { + private _reactionDisposer: Opt; + + @observable _lastX: number = 0; + @observable _lastY: number = 0; + + componentDidMount() { + this._reactionDisposer = reaction( + () => this.props.container.PreviewCursorVisible, + (visible: boolean) => this.onCursorPlaced(visible, this.props.container.DownX, this.props.container.DownY)) + } + componentWillUnmount() { + if (this._reactionDisposer) { + this._reactionDisposer(); + } + this.cleanupInteractions(); + } + + + @action + cleanupInteractions = () => { + document.removeEventListener("keypress", this.onKeyPress, false); + } + + @action + onCursorPlaced = (visible: boolean, downX: number, downY: number): void => { + if (visible) { + document.addEventListener("keypress", this.onKeyPress, false); + this._lastX = downX; + this._lastY = downY; + } else + this.cleanupInteractions(); + } + + @action + onKeyPress = (e: KeyboardEvent) => { + // Mixing events between React and Native is finicky. In FormattedTextBox, we set the + // DASHFormattedTextBoxHandled flag when a text box consumes a key press so that we can ignore + // the keyPress here. + //if not these keys, make a textbox if preview cursor is active! + if (!e.ctrlKey && !e.altKey && !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) { + //make textbox and add it to this collection + let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY); + let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "typed text" }); + this.props.addLiveTextDocument(newBox); + e.stopPropagation(); + } + } + + render() { + //get local position and place cursor there! + let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY); + return ( + !this.props.container.PreviewCursorVisible ? (null) : +
I
) + + } +} \ No newline at end of file diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index f48be2047..b54744337 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -5,7 +5,7 @@ import { Key } from "../../../fields/Key"; import { KeyStore } from "../../../fields/KeyStore"; import { ListField } from "../../../fields/ListField"; import { CollectionDockingView } from "../collections/CollectionDockingView"; -import { CollectionFreeFormView } from "../collections/CollectionFreeFormView"; +import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; import { CollectionPDFView } from "../collections/CollectionPDFView"; import { CollectionSchemaView } from "../collections/CollectionSchemaView"; import { CollectionVideoView } from "../collections/CollectionVideoView"; -- cgit v1.2.3-70-g09d2 From da7a12f9b49b43534658524b1da846948fbf3947 Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 29 Mar 2019 14:37:26 -0400 Subject: fixed some bugs added some small features to histograms. --- src/client/Server.ts | 4 ++-- src/client/documents/Documents.ts | 4 ++-- src/client/northstar/dash-nodes/HistogramBox.tsx | 17 ++++++++++++----- .../northstar/dash-nodes/HistogramBoxPrimitives.tsx | 5 ++++- src/client/northstar/utils/SizeConverter.ts | 2 +- src/client/util/DragManager.ts | 2 ++ src/client/views/Main.tsx | 6 +++--- .../collectionFreeForm/CollectionFreeFormView.scss | 20 ++++++++++---------- 8 files changed, 36 insertions(+), 24 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/Server.ts b/src/client/Server.ts index feafe9eb4..37e3c2c0d 100644 --- a/src/client/Server.ts +++ b/src/client/Server.ts @@ -75,7 +75,7 @@ export class Server { existingFields[id] = field; } } - SocketStub.SEND_FIELDS_REQUEST(neededFieldIds, (fields) => { + SocketStub.SEND_FIELDS_REQUEST(neededFieldIds, action((fields: FieldMap) => { for (let id of neededFieldIds) { let field = fields[id]; if (field) { @@ -104,7 +104,7 @@ export class Server { cb({ ...fields, ...existingFields }) } }, { fireImmediately: true }) - }); + })); }; if (callback) { fn(callback); diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 663ccae61..0bf275df8 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -190,8 +190,8 @@ export namespace Documents { return assignToDelegate(SetInstanceOptions(GetAudioPrototype(), options, [new URL(url), AudioField]), options); } - export function HistogramDocument(histoOp: HistogramOperation, options: DocumentOptions = {}, id?: string) { - return assignToDelegate(SetInstanceOptions(GetHistogramPrototype(), options, [histoOp, HistogramField], id).MakeDelegate(), options); + export function HistogramDocument(histoOp: HistogramOperation, options: DocumentOptions = {}, id?: string, delegId?: string) { + return assignToDelegate(SetInstanceOptions(GetHistogramPrototype(), options, [histoOp, HistogramField], id).MakeDelegate(delegId), options); } export function TextDocument(options: DocumentOptions = {}) { return assignToDelegate(SetInstanceOptions(GetTextPrototype(), options, ["", TextField]).MakeDelegate(), options); diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx index 9f8c2cfd0..dba4ce900 100644 --- a/src/client/northstar/dash-nodes/HistogramBox.tsx +++ b/src/client/northstar/dash-nodes/HistogramBox.tsx @@ -9,7 +9,7 @@ import { CurrentUserUtils } from "../../../server/authentication/models/current_ import { FilterModel } from '../../northstar/core/filter/FilterModel'; import { ChartType, VisualBinRange } from '../../northstar/model/binRanges/VisualBinRange'; import { VisualBinRangeHelper } from "../../northstar/model/binRanges/VisualBinRangeHelper"; -import { AggregateBinRange, BinRange, DoubleValueAggregateResult, HistogramResult, Catalog } from "../../northstar/model/idea/idea"; +import { AggregateBinRange, BinRange, DoubleValueAggregateResult, HistogramResult, Catalog, AggregateFunction } from "../../northstar/model/idea/idea"; import { ModelHelpers } from "../../northstar/model/ModelHelpers"; import { HistogramOperation } from "../../northstar/operations/HistogramOperation"; import { PIXIRectangle } from "../../northstar/utils/MathUtil"; @@ -21,6 +21,7 @@ import "../utils/Extensions" import "./HistogramBox.scss"; import { HistogramBoxPrimitives } from './HistogramBoxPrimitives'; import { HistogramLabelPrimitives } from "./HistogramLabelPrimitives"; +import { AttributeTransformationModel } from "../core/attribute/AttributeTransformationModel"; export interface HistogramPrimitivesProps { HistoBox: HistogramBox; @@ -104,7 +105,11 @@ export class HistogramBox extends React.Component { @action xLabelPointerDown = (e: React.PointerEvent) => { - + this.HistoOp.X = new AttributeTransformationModel(this.HistoOp.X.AttributeModel, this.HistoOp.X.AggregateFunction == AggregateFunction.None ? AggregateFunction.Count : AggregateFunction.None); + } + @action + yLabelPointerDown = (e: React.PointerEvent) => { + this.HistoOp.Y = new AttributeTransformationModel(this.HistoOp.Y.AttributeModel, this.HistoOp.Y.AggregateFunction == AggregateFunction.None ? AggregateFunction.Count : AggregateFunction.None); } componentWillUnmount() { @@ -138,12 +143,12 @@ export class HistogramBox extends React.Component { runInAction(() => { this.PanelWidth = r.entry.width; this.PanelHeight = r.entry.height })}> {({ measureRef }) =>
-
+
{labelY}
-
{
-
{labelX}
+
+ {labelX} +
} diff --git a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx index d2f1be4fd..5e403eb9c 100644 --- a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx +++ b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx @@ -1,5 +1,5 @@ import React = require("react") -import { computed, observable, runInAction } from "mobx"; +import { computed, observable, runInAction, reaction } from "mobx"; import { observer } from "mobx-react"; import { Utils as DashUtils } from '../../../Utils'; import { AttributeTransformationModel } from "../../northstar/core/attribute/AttributeTransformationModel"; @@ -19,6 +19,9 @@ import "./HistogramBoxPrimitives.scss"; export class HistogramBoxPrimitives extends React.Component { private get histoOp() { return this.props.HistoBox.HistoOp; } private get renderDimension() { return this.props.HistoBox.SizeConverter.RenderDimension; } + componentDidMount() { + reaction(() => this.props.HistoBox.HistogramResult, () => this._selectedPrims.length = 0); + } @observable _selectedPrims: HistogramBinPrimitive[] = []; @computed get xaxislines() { return this.renderGridLinesAndLabels(0); } @computed get yaxislines() { return this.renderGridLinesAndLabels(1); } diff --git a/src/client/northstar/utils/SizeConverter.ts b/src/client/northstar/utils/SizeConverter.ts index 30627dfd5..bb91ed4a7 100644 --- a/src/client/northstar/utils/SizeConverter.ts +++ b/src/client/northstar/utils/SizeConverter.ts @@ -62,7 +62,7 @@ export class SizeConverter { public DataToScreenPointRange(axis: number, bin: Bin, aggregateKey: AggregateKey) { var value = ModelHelpers.GetAggregateResult(bin, aggregateKey) as DoubleValueAggregateResult; - if (value.hasResult) + if (value && value.hasResult) return [this.DataToScreenCoord(value.result!, axis) - 5, this.DataToScreenCoord(value.result!, axis) + 5]; return [undefined, undefined]; diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 661fa4dc8..70b1c9829 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -186,6 +186,8 @@ export namespace DragManager { e.preventDefault(); x += e.movementX; y += e.movementY; + if (dragData instanceof DocumentDragData) + dragData.aliasOnDrop = e.ctrlKey || e.altKey; if (e.shiftKey) { abortDrag(); CollectionDockingView.Instance.StartOtherDrag(doc, { pageX: e.pageX, pageY: e.pageY, preventDefault: () => { }, button: 0 }); diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 2f20b102c..75855c3d1 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -245,7 +245,7 @@ export class Main extends React.Component { let addTextNode = action(() => Documents.TextDocument({ width: 200, height: 200, title: "a text note" })) let addColNode = action(() => Documents.FreeformDocument([], { width: 200, height: 200, title: "a freeform collection" })); let addSchemaNode = action(() => Documents.SchemaDocument([], { width: 200, height: 200, title: "a schema collection" })); - let addTreeNode = action(() => Documents.TreeDocument(this._northstarSchemas, { width: 100, height: 400, title: "northstar schemas" })); + let addTreeNode = action(() => Documents.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas" })); let addVideoNode = action(() => Documents.VideoDocument(videourl, { width: 200, height: 200, title: "video node" })); let addPDFNode = action(() => Documents.PdfDocument(pdfurl, { width: 200, height: 200, title: "a schema collection" })); let addImageNode = action(() => Documents.ImageDocument(imgurl, { width: 200, height: 200, title: "an image of a cat" })); @@ -340,7 +340,7 @@ export class Main extends React.Component { let schemaDoc = Documents.TreeDocument([], { width: 50, height: 100, title: schema.displayName! }); let schemaDocuments = schemaDoc.GetList(KeyStore.Data, [] as Document[]); CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => { - Server.GetField(attr.displayName!, action((field: Opt) => { + Server.GetField(attr.displayName! + ".alias", action((field: Opt) => { if (field instanceof Document) { schemaDocuments.push(field); } else { @@ -349,7 +349,7 @@ export class Main extends React.Component { new AttributeTransformationModel(atmod, AggregateFunction.None), new AttributeTransformationModel(atmod, AggregateFunction.Count), new AttributeTransformationModel(atmod, AggregateFunction.Count)); - schemaDocuments.push(Documents.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName! }, attr.displayName!)); + schemaDocuments.push(Documents.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName! }, undefined, attr.displayName! + ".alias")); } })); }); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index 9c5e98005..215a75243 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -1,5 +1,15 @@ @import "../../global_variables"; +.collectionfreeformview { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + .inking-canvas { + transform-origin: 50000px 50000px; + } +} .collectionfreeformview-container { .collectionfreeformview > .jsx-parser { position: absolute; @@ -27,16 +37,6 @@ left: 0; width: 100%; height: 100%; - .collectionfreeformview { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - .inking-canvas { - transform-origin: 50000px 50000px; - } - } } .collectionfreeformview-overlay { .collectionfreeformview > .jsx-parser { -- cgit v1.2.3-70-g09d2 From 6e993fb5817e8ddce756396e53883a42530f52bb Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 29 Mar 2019 18:49:22 -0400 Subject: brushes mostly working - some problems with cycles. --- src/client/northstar/dash-fields/HistogramField.ts | 17 +++++++- src/client/northstar/dash-nodes/HistogramBox.tsx | 27 ++++++++---- .../dash-nodes/HistogramBoxPrimitives.tsx | 14 ++++--- src/client/northstar/operations/BaseOperation.ts | 6 ++- .../northstar/operations/HistogramOperation.ts | 22 +++++----- .../CollectionFreeFormLinksView.tsx | 49 +++++++++++++++++++++- src/fields/KeyStore.ts | 1 + 7 files changed, 107 insertions(+), 29 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts index 00912c595..1929f8dcd 100644 --- a/src/client/northstar/dash-fields/HistogramField.ts +++ b/src/client/northstar/dash-fields/HistogramField.ts @@ -16,7 +16,8 @@ export class HistogramField extends BasicField { } toString(): string { - return JSON.stringify(this.Data); + let omitted = this.omitKeys(this.Data, ['Links', 'BrushLinks']); + return JSON.stringify(omitted); } Copy(): Field { @@ -27,10 +28,22 @@ export class HistogramField extends BasicField { return `new HistogramField("${this.Data}")`; } + omitKeys(obj: any, keys: any) { + var dup: any = {}; + for (var key in obj) { + if (keys.indexOf(key) == -1) { + dup[key] = obj[key]; + } + } + return dup; + } + ToJson(): { type: Types, data: string, _id: string } { + let omitted = this.omitKeys(this.Data, ['Links', 'BrushLinks']); return { type: Types.HistogramOp, - data: JSON.stringify(this.Data), + + data: JSON.stringify(omitted), _id: this.Id } } diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx index b464e125c..9976ff6ad 100644 --- a/src/client/northstar/dash-nodes/HistogramBox.tsx +++ b/src/client/northstar/dash-nodes/HistogramBox.tsx @@ -1,5 +1,5 @@ import React = require("react") -import { action, computed, observable, reaction, runInAction } from "mobx"; +import { action, computed, observable, reaction, runInAction, trace } from "mobx"; import { observer } from "mobx-react"; import Measure from "react-measure"; import { FieldWaiting, Opt } from "../../../fields/Field"; @@ -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 } from "../../northstar/model/idea/idea"; +import { AggregateBinRange, AggregateFunction, BinRange, Catalog, DoubleValueAggregateResult, HistogramResult, Result } from "../../northstar/model/idea/idea"; import { ModelHelpers } from "../../northstar/model/ModelHelpers"; import { HistogramOperation } from "../../northstar/operations/HistogramOperation"; import { SizeConverter } from "../../northstar/utils/SizeConverter"; @@ -39,10 +39,10 @@ export class HistogramBox extends React.Component { @observable public HistoOp: HistogramOperation = HistogramOperation.Empty; @observable public VisualBinRanges: VisualBinRange[] = []; @observable public ValueRange: number[] = []; + @observable public HistogramResult: HistogramResult = new HistogramResult(); @observable public SizeConverter: SizeConverter = new SizeConverter(); - @computed get createOperationParamsCache() { return this.HistoOp.CreateOperationParameters(); } - @computed get HistogramResult() { return this.HistoOp ? this.HistoOp.Result as HistogramResult : undefined; } + @computed get createOperationParamsCache() { trace(); return this.HistoOp.CreateOperationParameters(); } @computed get BinRanges() { return this.HistogramResult ? this.HistogramResult.binRanges : undefined; } @computed get ChartType() { return !this.BinRanges ? ChartType.SinglePoint : this.BinRanges[0] instanceof AggregateBinRange ? @@ -123,12 +123,21 @@ export class HistogramBox extends React.Component { this.props.doc.GetTAsync(this.props.fieldKey, HistogramField).then((histoOp: Opt) => runInAction(() => { this.HistoOp = histoOp ? histoOp.Data : HistogramOperation.Empty; if (this.HistoOp != HistogramOperation.Empty) { - reaction(() => this.props.doc.GetList(KeyStore.LinkedFromDocs, []), - (docs: Document[]) => { + this.HistoOp.YieldResult = (r: Result) => action(() => this.HistogramResult = r as HistogramResult)(); + reaction(() => this.props.doc.GetList(KeyStore.LinkedFromDocs, []), (docs: Document[]) => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true }); + reaction(() => this.props.doc.GetList(KeyStore.BrushingDocs, []).length, + () => { + let brushingDocs = this.props.doc.GetList(KeyStore.BrushingDocs, [] as Document[]); var availableColors = StyleConstants.BRUSH_COLORS.map(c => c); - docs.map((brush, i) => brush.SetNumber(KeyStore.BackgroundColor, availableColors[i % availableColors.length])); - this.HistoOp.BrushLinks.splice(0, this.HistoOp.BrushLinks.length, ...docs); - //this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs) + let proto = this.props.doc.GetPrototype() as Document; + let brushingLinks = brushingDocs.map((brush, i) => { + // brush.SetNumber(KeyStore.BackgroundColor, availableColors[i % availableColors.length]); + let brushed = brush.GetList(KeyStore.BrushingDocs, [] as Document[]); + if (!brushed || brushed.length < 2) + return undefined; + return { l: brush, b: brushed[0].Id == proto.Id ? brushed[1] : brushed[0] } + }).filter(x => x != undefined) as { l: Document, b: Document }[]; + this.HistoOp.BrushLinks.splice(0, this.HistoOp.BrushLinks.length, ...brushingLinks); }, { fireImmediately: true }); reaction(() => this.createOperationParamsCache, () => this.HistoOp.Update(), { fireImmediately: true }); } diff --git a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx index 648070241..c97acb064 100644 --- a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx +++ b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx @@ -1,5 +1,5 @@ import React = require("react") -import { computed, observable, runInAction, reaction } from "mobx"; +import { computed, observable, runInAction, reaction, untracked, trace } from "mobx"; import { observer } from "mobx-react"; import { Utils as DashUtils } from '../../../Utils'; import { AttributeTransformationModel } from "../../northstar/core/attribute/AttributeTransformationModel"; @@ -20,7 +20,7 @@ export class HistogramBoxPrimitives extends React.Component this.props.HistoBox.HistogramResult, () => this._selectedPrims.length = 0); + reaction(() => this.props.HistoBox.HistoOp.FilterString, () => this._selectedPrims.length = this.histoOp.FilterModels.length = 0); } @observable _selectedPrims: HistogramBinPrimitive[] = []; @computed get xaxislines() { return this.renderGridLinesAndLabels(0); } @@ -30,10 +30,10 @@ export class HistogramBoxPrimitives extends React.Component { let drawPrims = new HistogramBinPrimitiveCollection(histoResult!.bins![key], this.props.HistoBox); - let toggle = this.getSelectionToggle(drawPrims.BinPrimitives, allBrushIndex, ModelHelpers.GetBinFilterModel(histoResult!.bins![key], allBrushIndex, histoResult!, this.histoOp.X, this.histoOp.Y)); drawPrims.BinPrimitives.filter(bp => bp.DataValue && bp.BrushIndex !== allBrushIndex).map(bp => @@ -327,6 +327,7 @@ export class HistogramBinPrimitiveCollection { } private baseColorFromBrush(brush: Brush): number { + let bc = StyleConstants.BRUSH_COLORS; if (brush.brushIndex == ModelHelpers.RestBrushIndex(this.histoResult)) { return StyleConstants.HIGHLIGHT_COLOR; } @@ -336,9 +337,12 @@ export class HistogramBinPrimitiveCollection { else if (brush.brushIndex == ModelHelpers.AllBrushIndex(this.histoResult)) { return 0x00ff00; } - else if (this.histoOp.BrushColors.length > 0) { - return this.histoOp.BrushColors[brush.brushIndex! % this.histoOp.BrushColors.length]; + else if (bc.length > 0) { + return bc[brush.brushIndex! % bc.length]; } + // else if (this.histoOp.BrushColors.length > 0) { + // return this.histoOp.BrushColors[brush.brushIndex! % this.histoOp.BrushColors.length]; + // } return StyleConstants.HIGHLIGHT_COLOR; } } \ No newline at end of file diff --git a/src/client/northstar/operations/BaseOperation.ts b/src/client/northstar/operations/BaseOperation.ts index 94e0849af..21d1db749 100644 --- a/src/client/northstar/operations/BaseOperation.ts +++ b/src/client/northstar/operations/BaseOperation.ts @@ -10,7 +10,8 @@ export abstract class BaseOperation { @observable public Error: string = ""; @observable public OverridingFilters: FilterModel[] = []; - @observable public Result?: Result = undefined; + //@observable + public Result?: Result = undefined; @observable public ComputationStarted: boolean = false; public OperationReference?: OperationReference = undefined; @@ -45,8 +46,11 @@ export abstract class BaseOperation { } + public YieldResult: ((result: Result) => void) | undefined; @action public SetResult(result: Result): void { + if (this.YieldResult) + this.YieldResult(result); this.Result = result; } diff --git a/src/client/northstar/operations/HistogramOperation.ts b/src/client/northstar/operations/HistogramOperation.ts index fa51e2a8c..48bad73df 100644 --- a/src/client/northstar/operations/HistogramOperation.ts +++ b/src/client/northstar/operations/HistogramOperation.ts @@ -1,4 +1,4 @@ -import { action, computed, observable } from "mobx"; +import { action, computed, observable, trace } from "mobx"; import { Document } from "../../../fields/Document"; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; import { ColumnAttributeModel } from "../core/attribute/AttributeModel"; @@ -16,12 +16,13 @@ import { BaseOperation } from "./BaseOperation"; import { KeyStore } from "../../../fields/KeyStore"; import { HistogramField } from "../dash-fields/HistogramField"; import { FieldWaiting } from "../../../fields/Field"; +import { StyleConstants } from "../utils/StyleContants"; export class HistogramOperation extends BaseOperation implements IBaseFilterConsumer, IBaseFilterProvider { @observable public FilterOperand: FilterOperand = FilterOperand.AND; @observable public Links: Document[] = []; - @observable public BrushLinks: Document[] = []; + @observable public BrushLinks: { l: Document, b: Document }[] = []; @observable public BrushColors: number[] = []; @observable public Normalization: number = -1; @observable public FilterModels: FilterModel[] = []; @@ -69,16 +70,15 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons @computed public get BrushString(): string[] { + trace(); let brushes: string[] = []; this.BrushLinks.map(brushLink => { - let brusherDoc = brushLink.Get(KeyStore.LinkedFromDocs); - if (brusherDoc && brusherDoc != FieldWaiting && brusherDoc instanceof Document) { - let brushHistogram = brusherDoc.GetT(KeyStore.Data, HistogramField); - if (brushHistogram && brushHistogram != FieldWaiting) { - let filterModels: FilterModel[] = []; - let brush = FilterModel.GetFilterModelsRecursive(brushHistogram!.Data, new Set(), filterModels, false) - brushes.push(brush); - } + let brusherDoc = brushLink.b; + let brushHistogram = brusherDoc.GetT(KeyStore.Data, HistogramField); + if (brushHistogram && brushHistogram != FieldWaiting) { + let filterModels: FilterModel[] = []; + let brush = FilterModel.GetFilterModelsRecursive(brushHistogram!.Data, new Set(), filterModels, false) + brushes.push(brush); } }); return brushes; @@ -137,7 +137,7 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons @action public async Update(): Promise { - this.BrushColors = this.BrushLinks.map(e => e.GetNumber(KeyStore.BackgroundColor, 0)); + //this.BrushColors = this.BrushLinks.map(e => e.l.GetNumber(KeyStore.BackgroundColor, 0)); return super.Update(); } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index 4f28f43eb..8dbf80533 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -1,4 +1,4 @@ -import { computed } from "mobx"; +import { computed, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../../fields/Document"; import { FieldWaiting } from "../../../../fields/Field"; @@ -11,10 +11,57 @@ import "./CollectionFreeFormLinksView.scss"; import React = require("react"); import v5 = require("uuid/v5"); import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView"; +import { ListField } from "../../../../fields/ListField"; +import { TextField } from "../../../../fields/TextField"; +import { StyleConstants } from "../../../northstar/utils/StyleContants"; @observer export class CollectionFreeFormLinksView extends React.Component { + componentDidMount() { + reaction(() => { + return DocumentManager.Instance.getAllDocumentViews(this.props.Document).map(dv => dv.props.Document.GetNumber(KeyStore.X, 0)) + }, () => { + let views = DocumentManager.Instance.getAllDocumentViews(this.props.Document); + for (let i = 0; i < views.length; i++) { + for (let j = i + 1; j < views.length; j++) { + let srcDoc = views[j].props.Document; + let dstDoc = views[i].props.Document; + let x1 = srcDoc.GetNumber(KeyStore.X, 0); + let x1w = srcDoc.GetNumber(KeyStore.Width, 0); + let x2 = dstDoc.GetNumber(KeyStore.X, 0); + let x2w = dstDoc.GetNumber(KeyStore.Width, 0); + if (Math.abs(x1 + x1w - x2) < 20 || Math.abs(x2 + x2w - x1) < 20) { + let linkDoc: Document = new Document(); + dstDoc.GetTAsync(KeyStore.Prototype, Document).then((protoDest) => + srcDoc.GetTAsync(KeyStore.Prototype, Document).then((protoSrc) => runInAction(() => { + linkDoc.Set(KeyStore.Title, new TextField("New Brush")); + linkDoc.Set(KeyStore.LinkDescription, new TextField("")); + linkDoc.Set(KeyStore.LinkTags, new TextField("Default")); + linkDoc.SetNumber(KeyStore.BackgroundColor, StyleConstants.BRUSH_COLORS[0]); + + let dstTarg = (protoDest ? protoDest : dstDoc); + let srcTarg = (protoSrc ? protoSrc : srcDoc); + linkDoc.SetData(KeyStore.BrushingDocs, [dstTarg, srcTarg], ListField); + dstTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, field => { (field as ListField).Data.push(linkDoc) }) + srcTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, field => { (field as ListField).Data.push(linkDoc) }) + })) + ) + } else { + dstDoc.GetTAsync(KeyStore.Prototype, Document).then((protoDest) => + srcDoc.GetTAsync(KeyStore.Prototype, Document).then((protoSrc) => runInAction(() => { + + let dstTarg = (protoDest ? protoDest : dstDoc); + let srcTarg = (protoSrc ? protoSrc : srcDoc); + dstTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, field => { (field as ListField).Data.length = 0 }) + srcTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, field => { (field as ListField).Data.length = 0 }) + })) + ) + } + } + } + }); + } documentAnchors(view: DocumentView) { let equalViews = [view]; let containerDoc = view.props.Document.GetT(KeyStore.AnnotationOn, Document); diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts index aa0b9ce92..1f039e592 100644 --- a/src/fields/KeyStore.ts +++ b/src/fields/KeyStore.ts @@ -29,6 +29,7 @@ export namespace KeyStore { export const Caption = new Key("Caption"); export const ActiveWorkspace = new Key("ActiveWorkspace"); export const DocumentText = new Key("DocumentText"); + export const BrushingDocs = new Key("BrushingDocs"); export const LinkedToDocs = new Key("LinkedToDocs"); export const LinkedFromDocs = new Key("LinkedFromDocs"); export const LinkDescription = new Key("LinkDescription"); -- cgit v1.2.3-70-g09d2 From a2e4321eb23bedda9b78f012d3eb061045c5b33c Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 29 Mar 2019 22:50:49 -0400 Subject: improved brushing --- src/client/northstar/dash-nodes/HistogramBox.tsx | 5 +- src/client/northstar/operations/BaseOperation.ts | 5 +- .../CollectionFreeFormLinksView.tsx | 56 ++++++++++------------ 3 files changed, 29 insertions(+), 37 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx index 6bf2697fc..65d917fd3 100644 --- a/src/client/northstar/dash-nodes/HistogramBox.tsx +++ b/src/client/northstar/dash-nodes/HistogramBox.tsx @@ -36,7 +36,7 @@ export class HistogramBox extends React.Component { @observable public HistoOp: HistogramOperation = HistogramOperation.Empty; @observable public VisualBinRanges: VisualBinRange[] = []; @observable public ValueRange: number[] = []; - @observable public HistogramResult: HistogramResult = new HistogramResult(); + @computed public get HistogramResult(): HistogramResult { return this.HistoOp.Result as HistogramResult; } @observable public SizeConverter: SizeConverter = new SizeConverter(); @computed get createOperationParamsCache() { trace(); return this.HistoOp.CreateOperationParameters(); } @@ -120,7 +120,6 @@ export class HistogramBox extends React.Component { this.props.doc.GetTAsync(this.props.fieldKey, HistogramField).then((histoOp: Opt) => runInAction(() => { this.HistoOp = histoOp ? histoOp.Data : HistogramOperation.Empty; if (this.HistoOp != HistogramOperation.Empty) { - this.HistoOp.YieldResult = (r: Result) => action(() => this.HistogramResult = r as HistogramResult)(); reaction(() => this.props.doc.GetList(KeyStore.LinkedFromDocs, []), (docs: Document[]) => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true }); reaction(() => this.props.doc.GetList(KeyStore.BrushingDocs, []).length, () => { @@ -128,7 +127,7 @@ export class HistogramBox extends React.Component { var availableColors = StyleConstants.BRUSH_COLORS.map(c => c); let proto = this.props.doc.GetPrototype() as Document; let brushingLinks = brushingDocs.map((brush, i) => { - // brush.SetNumber(KeyStore.BackgroundColor, availableColors[i % availableColors.length]); + brush.SetNumber(KeyStore.BackgroundColor, availableColors[i % availableColors.length]); let brushed = brush.GetList(KeyStore.BrushingDocs, [] as Document[]); if (!brushed || brushed.length < 2) return undefined; diff --git a/src/client/northstar/operations/BaseOperation.ts b/src/client/northstar/operations/BaseOperation.ts index 21d1db749..f545b2c58 100644 --- a/src/client/northstar/operations/BaseOperation.ts +++ b/src/client/northstar/operations/BaseOperation.ts @@ -11,7 +11,7 @@ export abstract class BaseOperation { @observable public Error: string = ""; @observable public OverridingFilters: FilterModel[] = []; //@observable - public Result?: Result = undefined; + @observable public Result?: Result = undefined; @observable public ComputationStarted: boolean = false; public OperationReference?: OperationReference = undefined; @@ -46,11 +46,8 @@ export abstract class BaseOperation { } - public YieldResult: ((result: Result) => void) | undefined; @action public SetResult(result: Result): void { - if (this.YieldResult) - this.YieldResult(result); this.Result = result; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index 8dbf80533..2dcf7fbbe 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -3,17 +3,15 @@ import { observer } from "mobx-react"; import { Document } from "../../../../fields/Document"; import { FieldWaiting } from "../../../../fields/Field"; import { KeyStore } from "../../../../fields/KeyStore"; +import { ListField } from "../../../../fields/ListField"; import { Utils } from "../../../../Utils"; import { DocumentManager } from "../../../util/DocumentManager"; import { DocumentView } from "../../nodes/DocumentView"; import { CollectionViewProps } from "../CollectionViewBase"; import "./CollectionFreeFormLinksView.scss"; +import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView"; import React = require("react"); import v5 = require("uuid/v5"); -import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView"; -import { ListField } from "../../../../fields/ListField"; -import { TextField } from "../../../../fields/TextField"; -import { StyleConstants } from "../../../northstar/utils/StyleContants"; @observer export class CollectionFreeFormLinksView extends React.Component { @@ -31,36 +29,34 @@ export class CollectionFreeFormLinksView extends React.Component - srcDoc.GetTAsync(KeyStore.Prototype, Document).then((protoSrc) => runInAction(() => { - linkDoc.Set(KeyStore.Title, new TextField("New Brush")); - linkDoc.Set(KeyStore.LinkDescription, new TextField("")); - linkDoc.Set(KeyStore.LinkTags, new TextField("Default")); - linkDoc.SetNumber(KeyStore.BackgroundColor, StyleConstants.BRUSH_COLORS[0]); - - let dstTarg = (protoDest ? protoDest : dstDoc); - let srcTarg = (protoSrc ? protoSrc : srcDoc); + dstDoc.GetTAsync(KeyStore.Prototype, Document).then((protoDest) => + srcDoc.GetTAsync(KeyStore.Prototype, Document).then((protoSrc) => runInAction(() => { + let dstTarg = (protoDest ? protoDest : dstDoc); + let srcTarg = (protoSrc ? protoSrc : srcDoc); + let findBrush = (field: ListField) => field.Data.findIndex(brush => { + let bdocs = brush.GetList(KeyStore.BrushingDocs, [] as Document[]); + return (bdocs[0] == dstTarg && bdocs[1] == srcTarg) || (bdocs[0] == srcTarg && bdocs[1] == dstTarg) + }); + let brushAction = (field: ListField) => { + let found = findBrush(field); + if (found != -1) + field.Data.splice(found, 1); + }; + if (Math.abs(x1 + x1w - x2) < 20 || Math.abs(x2 + x2w - x1) < 20) { + let linkDoc: Document = new Document(); + linkDoc.SetText(KeyStore.Title, "Histogram Brush"); + linkDoc.SetText(KeyStore.LinkDescription, "Brush between " + srcTarg.Title + " and " + dstTarg.Title); linkDoc.SetData(KeyStore.BrushingDocs, [dstTarg, srcTarg], ListField); - dstTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, field => { (field as ListField).Data.push(linkDoc) }) - srcTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, field => { (field as ListField).Data.push(linkDoc) }) - })) - ) - } else { - dstDoc.GetTAsync(KeyStore.Prototype, Document).then((protoDest) => - srcDoc.GetTAsync(KeyStore.Prototype, Document).then((protoSrc) => runInAction(() => { - let dstTarg = (protoDest ? protoDest : dstDoc); - let srcTarg = (protoSrc ? protoSrc : srcDoc); - dstTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, field => { (field as ListField).Data.length = 0 }) - srcTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, field => { (field as ListField).Data.length = 0 }) - })) - ) - } + brushAction = (field: ListField) => (findBrush(field) == -1) && field.Data.push(linkDoc); + } + dstTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, brushAction); + srcTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, brushAction); + } + ))) } } - }); + }) } documentAnchors(view: DocumentView) { let equalViews = [view]; -- cgit v1.2.3-70-g09d2 From 74e95e596d9876e90c294ade30df3d666d167139 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 29 Mar 2019 23:48:34 -0400 Subject: fixed brush colors. --- src/client/northstar/dash-fields/HistogramField.ts | 2 +- src/client/northstar/dash-nodes/HistogramBox.tsx | 10 +++------- src/client/northstar/operations/HistogramOperation.ts | 2 +- .../collectionFreeForm/CollectionFreeFormLinksView.tsx | 10 ++++++---- 4 files changed, 11 insertions(+), 13 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts index ae83ea5ba..4a557e123 100644 --- a/src/client/northstar/dash-fields/HistogramField.ts +++ b/src/client/northstar/dash-fields/HistogramField.ts @@ -25,7 +25,7 @@ export class HistogramField extends BasicField { return dup; } toString(): string { - return JSON.stringify(this.omitKeys(this.Data, ['Links', 'BrushLinks', 'Result'])); + return JSON.stringify(this.omitKeys(this.Data, ['Links', 'BrushLinks', 'Result', 'BrushColors'])); } Copy(): Field { diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx index 65d917fd3..a968def96 100644 --- a/src/client/northstar/dash-nodes/HistogramBox.tsx +++ b/src/client/northstar/dash-nodes/HistogramBox.tsx @@ -124,16 +124,12 @@ export class HistogramBox extends React.Component { reaction(() => this.props.doc.GetList(KeyStore.BrushingDocs, []).length, () => { let brushingDocs = this.props.doc.GetList(KeyStore.BrushingDocs, [] as Document[]); - var availableColors = StyleConstants.BRUSH_COLORS.map(c => c); let proto = this.props.doc.GetPrototype() as Document; - let brushingLinks = brushingDocs.map((brush, i) => { - brush.SetNumber(KeyStore.BackgroundColor, availableColors[i % availableColors.length]); + this.HistoOp.BrushLinks.splice(0, this.HistoOp.BrushLinks.length, ...brushingDocs.map((brush, i) => { + brush.SetNumber(KeyStore.BackgroundColor, StyleConstants.BRUSH_COLORS[i % StyleConstants.BRUSH_COLORS.length]); let brushed = brush.GetList(KeyStore.BrushingDocs, [] as Document[]); - if (!brushed || brushed.length < 2) - return undefined; return { l: brush, b: brushed[0].Id == proto.Id ? brushed[1] : brushed[0] } - }).filter(x => x != undefined) as { l: Document, b: Document }[]; - this.HistoOp.BrushLinks.splice(0, this.HistoOp.BrushLinks.length, ...brushingLinks); + })); }, { fireImmediately: true }); reaction(() => this.createOperationParamsCache, () => this.HistoOp.Update(), { fireImmediately: true }); } diff --git a/src/client/northstar/operations/HistogramOperation.ts b/src/client/northstar/operations/HistogramOperation.ts index 4689cb233..e63de1632 100644 --- a/src/client/northstar/operations/HistogramOperation.ts +++ b/src/client/northstar/operations/HistogramOperation.ts @@ -116,7 +116,7 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons @action public async Update(): Promise { - //this.BrushColors = this.BrushLinks.map(e => e.l.GetNumber(KeyStore.BackgroundColor, 0)); + this.BrushColors = this.BrushLinks.map(e => e.l.GetNumber(KeyStore.BackgroundColor, 0)); return super.Update(); } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index 2dcf7fbbe..f793b3a49 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -26,16 +26,18 @@ export class CollectionFreeFormLinksView extends React.Component srcDoc.GetTAsync(KeyStore.Prototype, Document).then((protoSrc) => runInAction(() => { let dstTarg = (protoDest ? protoDest : dstDoc); let srcTarg = (protoSrc ? protoSrc : srcDoc); let findBrush = (field: ListField) => field.Data.findIndex(brush => { let bdocs = brush.GetList(KeyStore.BrushingDocs, [] as Document[]); - return (bdocs[0] == dstTarg && bdocs[1] == srcTarg) || (bdocs[0] == srcTarg && bdocs[1] == dstTarg) + return (bdocs.length == 0 || (bdocs[0] == dstTarg && bdocs[1] == srcTarg) || (bdocs[0] == srcTarg && bdocs[1] == dstTarg)) }); let brushAction = (field: ListField) => { let found = findBrush(field); @@ -48,7 +50,7 @@ export class CollectionFreeFormLinksView extends React.Component) => (findBrush(field) == -1) && field.Data.push(linkDoc); + brushAction = brushAction = (field: ListField) => (findBrush(field) == -1) && field.Data.push(linkDoc); } dstTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, brushAction); srcTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, brushAction); -- cgit v1.2.3-70-g09d2 From eb220da697e2383b1e368dee743613158994746e Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sun, 31 Mar 2019 17:58:11 -0400 Subject: restructured marquee and preview cursor to not need reactions. fixed some inking bugs. --- src/client/views/InkingCanvas.tsx | 25 ++- .../CollectionFreeFormLinksView.tsx | 1 + .../collectionFreeForm/CollectionFreeFormView.scss | 1 + .../collectionFreeForm/CollectionFreeFormView.tsx | 199 ++++++++------------- .../collectionFreeForm/MarqueeView.scss | 6 + .../collections/collectionFreeForm/MarqueeView.tsx | 169 +++++++++-------- .../collectionFreeForm/PreviewCursor.scss | 7 +- .../collectionFreeForm/PreviewCursor.tsx | 71 +++++--- 8 files changed, 245 insertions(+), 234 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx index 36a8834a0..8fb74ec48 100644 --- a/src/client/views/InkingCanvas.tsx +++ b/src/client/views/InkingCanvas.tsx @@ -1,16 +1,15 @@ -import { observer } from "mobx-react"; -import { observable } from "mobx"; import { action, computed } from "mobx"; -import { InkingControl } from "./InkingControl"; -import React = require("react"); -import { Transform } from "../util/Transform"; +import { observer } from "mobx-react"; import { Document } from "../../fields/Document"; -import { KeyStore } from "../../fields/KeyStore"; +import { FieldWaiting } from "../../fields/Field"; import { InkField, InkTool, StrokeData, StrokeMap } from "../../fields/InkField"; -import { InkingStroke } from "./InkingStroke"; -import "./InkingCanvas.scss" +import { KeyStore } from "../../fields/KeyStore"; import { Utils } from "../../Utils"; -import { FieldWaiting } from "../../fields/Field"; +import { Transform } from "../util/Transform"; +import "./InkingCanvas.scss"; +import { InkingControl } from "./InkingControl"; +import { InkingStroke } from "./InkingStroke"; +import React = require("react"); interface InkCanvasProps { getScreenTransform: () => Transform; @@ -71,8 +70,7 @@ export class InkingCanvas extends React.Component { // start the new line, saves a uuid to represent the field of the stroke this._idGenerator = Utils.GenerateGuid(); - let data = this.inkData; - data.set(this._idGenerator, + this.inkData.set(this._idGenerator, { pathData: [point], color: InkingControl.Instance.selectedColor, @@ -80,7 +78,6 @@ export class InkingCanvas extends React.Component { tool: InkingControl.Instance.selectedTool, page: this.props.Document.GetNumber(KeyStore.CurPage, -1) }); - this.inkData = data; this._isDrawing = true; } @@ -163,9 +160,7 @@ export class InkingCanvas extends React.Component { }) return ( - -
+
{paths} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index f793b3a49..ea392eaea 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -96,6 +96,7 @@ export class CollectionFreeFormLinksView extends React.Component {this.uniqueConnections.map(c => )} + {this.props.children} ); } } \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index 215a75243..c979b27a9 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -6,6 +6,7 @@ left: 0; width: 100%; height: 100%; + transform-origin: left top; .inking-canvas { transform-origin: 50000px 50000px; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 1f90b0d46..cdc5bdfb0 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -3,7 +3,6 @@ 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 { TextField } from "../../../../fields/TextField"; import { DragManager } from "../../../util/DragManager"; import { Transform } from "../../../util/Transform"; @@ -14,10 +13,9 @@ import { DocumentContentsView } from "../../nodes/DocumentContentsView"; import { DocumentViewProps } from "../../nodes/DocumentView"; import { COLLECTION_BORDER_WIDTH } from "../CollectionView"; import { CollectionViewBase } from "../CollectionViewBase"; -import { MarqueeView } from "./MarqueeView"; -import { PreviewCursor } from "./PreviewCursor"; import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView"; import "./CollectionFreeFormView.scss"; +import { MarqueeView } from "./MarqueeView"; import React = require("react"); import v5 = require("uuid/v5"); @@ -27,12 +25,15 @@ export class CollectionFreeFormView extends CollectionViewBase { private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type) public addLiveTextBox = (newBox: Document) => { - // mark this collection so that when the text box is created we can send it the SelectOnLoad prop to focus itself + // mark this collection so that when the text box is created we can send it the SelectOnLoad prop to focus itself and receive text input this._selectOnLoaded = newBox.Id; - //set text to be the typed key and get focus on text box - this.props.addDocument(newBox, false); - //remove cursor from screen - this.PreviewCursorVisible = false; + this.addDocument(newBox, false); + } + + public addDocument = (newBox: Document, allowDuplicates: boolean) => { + let added = this.props.addDocument(newBox, false); + this.bringToFront(newBox); + return added; } public selectDocuments = (docs: Document[]) => { @@ -42,23 +43,15 @@ export class CollectionFreeFormView extends CollectionViewBase { public getActiveDocuments = () => { var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1); - const lvalue = this.props.Document.GetT>(this.props.fieldKey, ListField); - let active: Document[] = []; - if (lvalue && lvalue != FieldWaiting) { - lvalue.Data.map(doc => { - var page = doc.GetNumber(KeyStore.Page, -1); - if (page == curPage || page == -1) { - active.push(doc); - } - }) - } - - return active; + return this.props.Document.GetList(this.props.fieldKey, [] as Document[]).reduce((active, doc) => { + var page = doc.GetNumber(KeyStore.Page, -1); + if (page == curPage || page == -1) { + active.push(doc); + } + return active; + }, [] as Document[]); } - //determines whether the blinking cursor for indicating whether a text will be made on key down is visible - @observable public PreviewCursorVisible: boolean = false; - @observable public MarqueeVisible = false; @observable public DownX: number = 0; @observable public DownY: number = 0; @observable private _lastX: number = 0; @@ -100,12 +93,10 @@ export class CollectionFreeFormView extends CollectionViewBase { cleanupInteractions = () => { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); - this.MarqueeVisible = false; } @action onPointerDown = (e: React.PointerEvent): void => { - this.PreviewCursorVisible = false; if ((e.button === 2 && this.props.active() && (!this.isAnnotationOverlay || this.zoomScaling != 1)) || e.button == 0) { document.removeEventListener("pointermove", this.onPointerMove); document.addEventListener("pointermove", this.onPointerMove); @@ -120,28 +111,13 @@ export class CollectionFreeFormView extends CollectionViewBase { onPointerUp = (e: PointerEvent): void => { e.stopPropagation(); - if (Math.abs(this.DownX - e.clientX) < 4 && Math.abs(this.DownY - e.clientY) < 4) { - //show preview text cursor on tap - this.PreviewCursorVisible = true; - //select is not already selected - if (!this.props.isSelected()) { - this.props.select(false); - } - } this.cleanupInteractions(); } @action onPointerMove = (e: PointerEvent): void => { if (!e.cancelBubble && this.props.active()) { - if (e.buttons == 1 && !e.altKey && !e.metaKey) { - this.MarqueeVisible = true; - } - if (this.MarqueeVisible) { - e.stopPropagation(); - e.preventDefault(); - } - else if ((!this.isAnnotationOverlay || this.zoomScaling != 1) && !e.shiftKey) { + if ((!this.isAnnotationOverlay || this.zoomScaling != 1) && !e.shiftKey) { let x = this.props.Document.GetNumber(KeyStore.PanX, 0); let y = this.props.Document.GetNumber(KeyStore.PanY, 0); let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); @@ -264,16 +240,12 @@ export class CollectionFreeFormView extends CollectionViewBase { @computed get views() { var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1); - const lvalue = this.props.Document.GetT>(this.props.fieldKey, ListField); - if (lvalue && lvalue != FieldWaiting) { - return lvalue.Data.map(doc => { - if (!doc) return null; - var page = doc.GetNumber(KeyStore.Page, 0); - return (page != curPage && page != 0) ? (null) : - (); - }) - } - return null; + return 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(); + return prev; + }, [] as JSX.Element[]) } @computed @@ -290,15 +262,10 @@ export class CollectionFreeFormView extends CollectionViewBase { } getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform()) - getMarqueeTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH) + getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH) getLocalTransform = (): Transform => Transform.Identity.scale(1 / this.scale).translate(this.panX, this.panY); noScaling = () => 1; - //when focus is lost, this will remove the preview cursor - @action - onBlur = (): void => { - this.PreviewCursorVisible = false; - } private crosshairs?: HTMLCanvasElement; drawCrosshairs = (backgroundColor: string) => { @@ -345,71 +312,63 @@ export class CollectionFreeFormView extends CollectionViewBase { return (
super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY))} - onWheel={this.onPointerWheel} - onDrop={this.onDrop.bind(this)} - onDragOver={this.onDragOver} - onBlur={this.onBlur} - style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}// , zIndex: !this.props.isTopMost ? -1 : undefined }} - tabIndex={0} - ref={this.createDropTarget}> -
- {this.backgroundView} - - - {this.views} - - {super.getCursors().map(entry => { - if (entry.Data.length > 0) { - let id = entry.Data[0][0]; - let email = entry.Data[0][1]; - let point = entry.Data[1]; - this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22") - return ( -
- { if (el) this.crosshairs = el }} - width={20} - height={20} - style={{ - position: 'absolute', - width: "20px", - height: "20px", - opacity: 0.5, - borderRadius: "50%", - border: "2px solid black" - }} - /> -

super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY))} + onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} onWheel={this.onPointerWheel} + style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} tabIndex={0} ref={this.createDropTarget}> + +

+ {this.backgroundView} + + {this.views} + + {super.getCursors().map(entry => { + if (entry.Data.length > 0) { + let id = entry.Data[0][0]; + let email = entry.Data[0][1]; + let point = entry.Data[1]; + this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22") + return ( +
{email[0].toUpperCase()}

-
- ); - } - })} -
- - {this.overlayView} - + > + { if (el) this.crosshairs = el }} + width={20} + height={20} + style={{ + position: 'absolute', + width: "20px", + height: "20px", + opacity: 0.5, + borderRadius: "50%", + border: "2px solid black" + }} + /> +

{email[0].toUpperCase()}

+
+ ); + } + })} +
+ {this.overlayView} +
); } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss index 6d9a79344..1ee3b244b 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss @@ -1,8 +1,14 @@ .marqueeView { + position: absolute; + width:100%; + height:100%; +} +.marquee { border-style: dashed; box-sizing: border-box; position: absolute; border-width: 1px; border-color: black; + pointer-events: none; } \ 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 2692690dd..2b0e7d228 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,62 +1,58 @@ -import { action, IReactionDisposer, observable, reaction } from "mobx"; +import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../../fields/Document"; -import { FieldWaiting, Opt } from "../../../../fields/Field"; +import { FieldWaiting } from "../../../../fields/Field"; +import { InkField, StrokeData } from "../../../../fields/InkField"; import { KeyStore } from "../../../../fields/KeyStore"; import { Documents } from "../../../documents/Documents"; import { SelectionManager } from "../../../util/SelectionManager"; import { Transform } from "../../../util/Transform"; +import { InkingCanvas } from "../../InkingCanvas"; import { CollectionFreeFormView } from "./CollectionFreeFormView"; import "./MarqueeView.scss"; +import { PreviewCursor } from "./PreviewCursor"; import React = require("react"); -import { InkField, StrokeData } from "../../../../fields/InkField"; -import { Utils } from "../../../../Utils"; -import { InkingCanvas } from "../../InkingCanvas"; interface MarqueeViewProps { - getMarqueeTransform: () => Transform; + getContainerTransform: () => Transform; getTransform: () => Transform; container: CollectionFreeFormView; addDocument: (doc: Document, allowDuplicates: false) => boolean; activeDocuments: () => Document[]; selectDocuments: (docs: Document[]) => void; removeDocument: (doc: Document) => boolean; + addLiveTextDocument: (doc: Document) => void; } @observer export class MarqueeView extends React.Component { - private _reactionDisposer: Opt; - @observable _lastX: number = 0; @observable _lastY: number = 0; @observable _downX: number = 0; @observable _downY: number = 0; - - componentDidMount() { - this._reactionDisposer = reaction( - () => this.props.container.MarqueeVisible, - (visible: boolean) => this.onPointerDown(visible, this.props.container.DownX, this.props.container.DownY)) - } - componentWillUnmount() { - if (this._reactionDisposer) { - this._reactionDisposer(); - } - this.cleanupInteractions(); - } + @observable _used: boolean = false; + @observable _visible: boolean = false; + static DRAG_THRESHOLD = 4; @action - cleanupInteractions = () => { - document.removeEventListener("pointermove", this.onPointerMove, true) - document.removeEventListener("pointerup", this.onPointerUp, true); + cleanupInteractions = (all: boolean = false) => { + if (all) { + document.removeEventListener("pointermove", this.onPointerMove, true) + document.removeEventListener("pointerup", this.onPointerUp, true); + } else { + this._used = true; + } document.removeEventListener("keydown", this.marqueeCommand, true); + this._visible = false; } @action - onPointerDown = (visible: boolean, downX: number, downY: number): void => { - if (visible) { - this._downX = this._lastX = downX; - this._downY = this._lastY = downY; + onPointerDown = (e: React.PointerEvent): void => { + if (e.buttons == 1 && !e.altKey && !e.metaKey && this.props.container.props.active()) { + this._downX = this._lastX = e.pageX; + this._downY = this._lastY = e.pageY; + this._used = false; document.addEventListener("pointermove", this.onPointerMove, true) document.addEventListener("pointerup", this.onPointerUp, true); document.addEventListener("keydown", this.marqueeCommand, true); @@ -67,11 +63,20 @@ export class MarqueeView extends React.Component onPointerMove = (e: PointerEvent): void => { this._lastX = e.pageX; this._lastY = e.pageY; + if (!e.cancelBubble) { + if (!this._used && e.buttons == 1 && !e.altKey && !e.metaKey && + (Math.abs(this._lastX - this._downX) > MarqueeView.DRAG_THRESHOLD || Math.abs(this._lastY - this._downY) > MarqueeView.DRAG_THRESHOLD)) { + this._visible = true; + } + e.stopPropagation(); + e.preventDefault(); + } } @action onPointerUp = (e: PointerEvent): void => { - this.cleanupInteractions(); + this.cleanupInteractions(true); + this._visible = false; if (!e.shiftKey) { SelectionManager.DeselectAll(); } @@ -83,6 +88,7 @@ export class MarqueeView extends React.Component return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top); } + @computed get Bounds() { let left = this._downX < this._lastX ? this._downX : this._lastX; let top = this._downY < this._lastY ? this._downY : this._lastY; @@ -95,7 +101,10 @@ export class MarqueeView extends React.Component marqueeCommand = (e: KeyboardEvent) => { if (e.key == "Backspace" || e.key == "Delete") { this.marqueeSelect().map(d => this.props.removeDocument(d)); - this.props.container.props.Document.SetData(KeyStore.Ink, this.marqueeInkSelect(false), InkField); + let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField); + if (ink && ink != FieldWaiting && ink.Data) { + this.marqueeInkDelete(ink.Data); + } this.cleanupInteractions(); } if (e.key == "c") { @@ -104,53 +113,61 @@ export class MarqueeView extends React.Component this.props.removeDocument(d); d.SetNumber(KeyStore.X, d.GetNumber(KeyStore.X, 0) - bounds.left - bounds.width / 2); d.SetNumber(KeyStore.Y, d.GetNumber(KeyStore.Y, 0) - bounds.top - bounds.height / 2); - d.SetNumber(KeyStore.Page, 0); + d.SetNumber(KeyStore.Page, -1); d.SetText(KeyStore.Title, "" + d.GetNumber(KeyStore.Width, 0) + " " + d.GetNumber(KeyStore.Height, 0)); return d; }); - let liftedInk = this.marqueeInkSelect(true); - this.props.container.props.Document.SetData(KeyStore.Ink, this.marqueeInkSelect(false), InkField); - //setTimeout(() => { - let newCollection = Documents.FreeformDocument(selected, { - x: bounds.left, - y: bounds.top, - panx: 0, - pany: 0, - width: bounds.width, - height: bounds.height, - backgroundColor: "Transparent", - ink: liftedInk, - title: "a nested collection" - }); - this.props.addDocument(newCollection, false); + let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField); + if (ink && ink != FieldWaiting && ink.Data) { + //setTimeout(() => { + let newCollection = Documents.FreeformDocument(selected, { + x: bounds.left, + y: bounds.top, + panx: 0, + pany: 0, + width: bounds.width, + height: bounds.height, + backgroundColor: "Transparent", + ink: this.marqueeInkSelect(ink.Data), + title: "a nested collection" + }); + this.props.addDocument(newCollection, false); + this.marqueeInkDelete(ink.Data); + } // }, 100); this.cleanupInteractions(); } } - marqueeInkSelect(select: boolean) { - let selRect = this.Bounds; - let centerShiftX = 0 - (selRect.left + selRect.width / 2); // moves each point by the offset that shifts the selection's center to the origin. - let centerShiftY = 0 - (selRect.top + selRect.height / 2); - let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField); - if (ink && ink != FieldWaiting && ink.Data) { - let idata = new Map(); - ink.Data.forEach((value: StrokeData, key: string, map: any) => { - let inside = InkingCanvas.IntersectStrokeRect(value, selRect); - if (inside && select) { - idata.set(key, - { - pathData: value.pathData.map(val => { return { x: val.x + centerShiftX, y: val.y + centerShiftY } }), - color: value.color, - width: value.width, - tool: value.tool, - page: -1 - }); - } else if (!inside && !select) { - idata.set(key, value); - } - }) - return idata; - } + @action + marqueeInkSelect(ink: Map) { + let idata = new Map(); + let centerShiftX = 0 - (this.Bounds.left + this.Bounds.width / 2); // moves each point by the offset that shifts the selection's center to the origin. + let centerShiftY = 0 - (this.Bounds.top + this.Bounds.height / 2); + ink.forEach((value: StrokeData, key: string, map: any) => { + if (InkingCanvas.IntersectStrokeRect(value, this.Bounds)) { + idata.set(key, + { + pathData: value.pathData.map(val => { return { x: val.x + centerShiftX, y: val.y + centerShiftY } }), + color: value.color, + width: value.width, + tool: value.tool, + page: -1 + }); + } + }); + return idata; + } + + @action + marqueeInkDelete(ink: Map, ) { + // bcz: this appears to work but when you restart all the deleted strokes come back -- InkField isn't observing its changes so they aren't written to the DB. + // ink.forEach((value: StrokeData, key: string, map: any) => + // InkingCanvas.IntersectStrokeRect(value, this.Bounds) && ink.delete(key)); + + let idata = new Map(); + ink.forEach((value: StrokeData, key: string, map: any) => + !InkingCanvas.IntersectStrokeRect(value, this.Bounds) && idata.set(key, value)); + this.props.container.props.Document.SetDataOnPrototype(KeyStore.Ink, idata, InkField); } marqueeSelect() { @@ -168,8 +185,16 @@ export class MarqueeView extends React.Component } render() { - let p = this.props.getMarqueeTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY); - let v = this.props.getMarqueeTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); - return (!this.props.container.MarqueeVisible ? (null) :
); + 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
+ + {this.props.children} + {!this._visible ? (null) : +
} + + +
; } } \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.scss b/src/client/views/collections/collectionFreeForm/PreviewCursor.scss index a797411f6..7d6d5aaab 100644 --- a/src/client/views/collections/collectionFreeForm/PreviewCursor.scss +++ b/src/client/views/collections/collectionFreeForm/PreviewCursor.scss @@ -5,6 +5,11 @@ transform-origin: left top; pointer-events: none; } +.previewCursorView { + position: absolute; + width:100%; + height:100%; +} //this is an animation for the blinking cursor! @keyframes blink { @@ -14,5 +19,5 @@ } #previewCursor { - animation: blink 1s infinite; + animation: blink 1s infinite; } \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx index 95364f04b..339e056a8 100644 --- a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx +++ b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx @@ -1,7 +1,6 @@ -import { action, IReactionDisposer, observable, reaction } from "mobx"; +import { action, observable } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../../fields/Document"; -import { Opt } from "../../../../fields/Field"; import { Documents } from "../../../documents/Documents"; import { Transform } from "../../../util/Transform"; import { CollectionFreeFormView } from "./CollectionFreeFormView"; @@ -11,43 +10,50 @@ import React = require("react"); export interface PreviewCursorProps { getTransform: () => Transform; + getContainerTransform: () => Transform; container: CollectionFreeFormView; addLiveTextDocument: (doc: Document) => void; } @observer export class PreviewCursor extends React.Component { - private _reactionDisposer: Opt; - @observable _lastX: number = 0; @observable _lastY: number = 0; + @observable public _visible: boolean = false; + @observable public DownX: number = 0; + @observable public DownY: number = 0; + _showOnUp: boolean = false; + public _previewDivRef = React.createRef(); - componentDidMount() { - this._reactionDisposer = reaction( - () => this.props.container.PreviewCursorVisible, - (visible: boolean) => this.onCursorPlaced(visible, this.props.container.DownX, this.props.container.DownY)) - } - componentWillUnmount() { - if (this._reactionDisposer) { - this._reactionDisposer(); - } - this.cleanupInteractions(); + @action + cleanupInteractions = () => { + document.removeEventListener("pointerup", this.onPointerUp, true); } - @action - cleanupInteractions = () => { + onPointerDown = (e: React.PointerEvent) => { + this._visible = false; document.removeEventListener("keypress", this.onKeyPress, false); + this._showOnUp = true; + this._lastX = this.DownX = e.pageX; + this._lastY = this.DownY = e.pageY; + document.addEventListener("pointerup", this.onPointerUp, true); + document.addEventListener("pointermove", this.onPointerMove, true); + } + @action + onPointerMove = (e: PointerEvent): void => { + if (Math.abs(this.DownX - e.clientX) > 4 || Math.abs(this.DownY - e.clientY) > 4) { + this._showOnUp = false; + } } @action - onCursorPlaced = (visible: boolean, downX: number, downY: number): void => { - if (visible) { + onPointerUp = (e: PointerEvent): void => { + if (this._showOnUp) { document.addEventListener("keypress", this.onKeyPress, false); - this._lastX = downX; - this._lastY = downY; - } else - this.cleanupInteractions(); + this._visible = true; + } + this.cleanupInteractions(); } @action @@ -61,16 +67,29 @@ export class PreviewCursor extends React.Component { let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY); let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "typed text" }); this.props.addLiveTextDocument(newBox); + document.removeEventListener("keypress", this.onKeyPress, false); + this._visible = false; e.stopPropagation(); } } + //when focus is lost, this will remove the preview cursor + @action + onBlur = (): void => { + this._visible = false; + document.removeEventListener("keypress", this.onKeyPress, false); + } render() { //get local position and place cursor there! - let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY); + let p = this.props.getContainerTransform().transformPoint(this._lastX, this._lastY); + if (this._visible && this._previewDivRef.current) + this._previewDivRef.current!.focus(); return ( - !this.props.container.PreviewCursorVisible ? (null) : -
I
) - +
+ {this.props.children} + {!this._visible ? (null) : +
I
} +
+ ) } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 2ac86b53d28abb8be2f3abd501b801e543973e28 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sun, 31 Mar 2019 22:45:43 -0400 Subject: restructured inkingCanvas and linksViews --- src/client/views/InkingCanvas.scss | 164 +++------------------ src/client/views/InkingCanvas.tsx | 121 +++++++-------- src/client/views/InkingControl.scss | 135 +++++++++++++++++ src/client/views/InkingControl.tsx | 3 +- .../CollectionFreeFormLinksView.scss | 3 + .../CollectionFreeFormLinksView.tsx | 9 +- .../collectionFreeForm/CollectionFreeFormView.scss | 10 -- .../collectionFreeForm/CollectionFreeFormView.tsx | 10 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 6 +- src/client/views/nodes/PDFBox.tsx | 10 +- 10 files changed, 233 insertions(+), 238 deletions(-) create mode 100644 src/client/views/InkingControl.scss (limited to 'src/client/views/collections') diff --git a/src/client/views/InkingCanvas.scss b/src/client/views/InkingCanvas.scss index e79b146b9..6d7821cd2 100644 --- a/src/client/views/InkingCanvas.scss +++ b/src/client/views/InkingCanvas.scss @@ -1,149 +1,25 @@ @import "global_variables"; -.inking-canvas { + +.inkingCanvas-paths, .inkingCanvas-paths-none { position: absolute; top: -50000px; - left: -50000px; // z-index: 99; //overlays ink on top of everything - svg { - position:absolute; - width: 100000px; - height: 100000px; - .highlight { - mix-blend-mode: multiply; - } - } + left: -50000px; + width: 100000px; + height: 100000px; + //z-index: 10000; // z-index: 99; //overlays ink on top of everything but that messes up blending and events don't propagate down to nested collections + .highlight { + mix-blend-mode: multiply; + } + .inkingCanvas-children { + transform: translate(50000px, 50000px); + pointer-events: none; + } + cursor:"crosshair"; + pointer-events: auto; + +} +.inkingCanvas-paths-none { + pointer-events: none; + cursor: "arrow"; } -.inking-control { - position: absolute; - left: 70px; - bottom: 70px; - margin: 0; - padding: 0; - display: flex; - label, - input, - option { - font-size: 12px; - } - input[type="range"] { - -webkit-appearance: none; - background-color: transparent; - vertical-align: middle; - margin-top: 8px; - &:focus { - outline: none; - } - &::-webkit-slider-runnable-track { - width: 100%; - height: 3px; - border-radius: 1.5px; - cursor: pointer; - background: $intermediate-color; - } - &::-webkit-slider-thumb { - height: 12px; - width: 12px; - border: 1px solid $intermediate-color; - border-radius: 6px; - background: $light-color; - cursor: pointer; - -webkit-appearance: none; - margin-top: -4px; - } - &::-moz-range-track { - width: 100%; - height: 3px; - border-radius: 1.5px; - cursor: pointer; - background: $light-color; - } - &::-moz-range-thumb { - height: 12px; - width: 12px; - border: 1px solid $intermediate-color; - border-radius: 6px; - background: $light-color; - cursor: pointer; - -webkit-appearance: none; - margin-top: -4px; - } - } - input[type="text"] { - border: none; - padding: 0 0px; - background: transparent; - color: $dark-color; - font-size: 12px; - margin-top: 4px; - } - .ink-panel { - margin: 6px 12px 6px 0; - height: 30px; - vertical-align: middle; - line-height: 36px; - padding: 0 10px; - color: $intermediate-color; - &:first { - margin-top: 0; - } - } - .ink-tools { - display: flex; - background-color: transparent; - border-radius: 0; - padding: 0; - button { - height: 36px; - padding: 0px; - padding-bottom: 3px; - margin-left: 10px; - background-color: transparent; - color: $intermediate-color; - } - button:hover { - transform: scale(1.15); - } - } - .ink-size { - display: flex; - justify-content: space-between; - input[type="text"] { - width: 42px; - } - >* { - margin-right: 6px; - &:last-child { - margin-right: 0; - } - } - } - .ink-color { - display: flex; - position: relative; - padding-right: 0; - label { - margin-right: 6px; - } - .ink-color-display { - border-radius: 11px; - width: 22px; - height: 22px; - margin-top: 6px; - cursor: pointer; - text-align: center; // span { - // color: $light-color; - // font-size: 8px; - // user-select: none; - // } - } - .ink-color-picker { - background-color: $light-color; - border-radius: 5px; - padding: 12px; - position: absolute; - bottom: 36px; - left: -3px; - box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw; - } - } -} \ No newline at end of file diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx index 8fb74ec48..a971264b9 100644 --- a/src/client/views/InkingCanvas.tsx +++ b/src/client/views/InkingCanvas.tsx @@ -57,51 +57,62 @@ export class InkingCanvas extends React.Component { } @action - handleMouseDown = (e: React.PointerEvent): void => { - if (e.button != 0 || + onPointerDown = (e: React.PointerEvent): void => { + this._isDrawing = false; + if (e.button != 0 || e.altKey || e.ctrlKey || InkingControl.Instance.selectedTool === InkTool.None) { return; } - e.stopPropagation() - if (InkingControl.Instance.selectedTool === InkTool.Eraser) { - return - } - const point = this.relativeCoordinatesForEvent(e); - - // start the new line, saves a uuid to represent the field of the stroke - this._idGenerator = Utils.GenerateGuid(); - this.inkData.set(this._idGenerator, - { - pathData: [point], - color: InkingControl.Instance.selectedColor, - width: InkingControl.Instance.selectedWidth, - tool: InkingControl.Instance.selectedTool, - page: this.props.Document.GetNumber(KeyStore.CurPage, -1) - }); + document.addEventListener("pointermove", this.onPointerMove, true); + document.addEventListener("pointerup", this.onPointerUp, true); + e.stopPropagation(); + this._isDrawing = true; + if (InkingControl.Instance.selectedTool != InkTool.Eraser) { + const point = this.relativeCoordinatesForEvent(e.clientX, e.clientY); + + // start the new line, saves a uuid to represent the field of the stroke + this._idGenerator = Utils.GenerateGuid(); + this.inkData.set(this._idGenerator, + { + pathData: [point], + color: InkingControl.Instance.selectedColor, + width: InkingControl.Instance.selectedWidth, + tool: InkingControl.Instance.selectedTool, + page: this.props.Document.GetNumber(KeyStore.CurPage, -1) + }); + } } - @action - handleMouseMove = (e: React.PointerEvent): void => { - if (!this._isDrawing || - InkingControl.Instance.selectedTool === InkTool.None) { - return; + onPointerUp = (e: PointerEvent): void => { + if (this._isDrawing) { + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + this._isDrawing = false; + e.stopPropagation(); } - e.stopPropagation() - if (InkingControl.Instance.selectedTool === InkTool.Eraser) { - return - } - const point = this.relativeCoordinatesForEvent(e); + } - // add points to new line as it is being drawn - let data = this.inkData; - let strokeData = data.get(this._idGenerator); - if (strokeData) { - strokeData.pathData.push(point); - data.set(this._idGenerator, strokeData); + @action + onPointerMove = (e: PointerEvent): void => { + if (this._isDrawing) { + e.stopPropagation() + e.preventDefault(); + if (InkingControl.Instance.selectedTool === InkTool.Eraser) { + return + } + const point = this.relativeCoordinatesForEvent(e.clientX, e.clientY); + + // add points to new line as it is being drawn + let data = this.inkData; + let strokeData = data.get(this._idGenerator); + if (strokeData) { + strokeData.pathData.push(point); + data.set(this._idGenerator, strokeData); + } + + this.inkData = data; } - - this.inkData = data; } @action @@ -109,8 +120,8 @@ export class InkingCanvas extends React.Component { this._isDrawing = false; } - relativeCoordinatesForEvent = (e: React.MouseEvent): { x: number, y: number } => { - let [x, y] = this.props.getScreenTransform().transformPoint(e.clientX, e.clientY); + relativeCoordinatesForEvent = (ex: number, ey: number): { x: number, y: number } => { + let [x, y] = this.props.getScreenTransform().transformPoint(ex, ey); x += InkingCanvas.InkOffset; y += InkingCanvas.InkOffset; return { x, y }; @@ -124,32 +135,9 @@ export class InkingCanvas extends React.Component { } render() { - // styling for cursor - let canvasStyle = {}; - if (InkingControl.Instance.selectedTool === InkTool.None) { - canvasStyle = { pointerEvents: "none" }; - } else { - canvasStyle = { pointerEvents: "auto", cursor: "crosshair" }; - } - - // get data from server - // let inkField = this.props.Document.GetT(KeyStore.Ink, InkField); - // if (!inkField || inkField == FieldWaiting) { - // return (
- // - // - //
) - // } - - let lines = this.inkData; - // parse data from server - let paths: Array = [] let curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1) - Array.from(lines).map(item => { - let id = item[0]; - let strokeData = item[1]; + let paths = Array.from(this.inkData).reduce((paths, [id, strokeData]) => { if (strokeData.page == -1 || strokeData.page == curPage) paths.push( { width={strokeData.width} tool={strokeData.tool} deleteCallback={this.removeLine} />) - }) + return paths; + }, [] as JSX.Element[]); + let svgCanvasStyle = InkingControl.Instance.selectedTool == InkTool.None ? "-none" : ""; return ( -
- +
+ {paths} + {this.props.children}
) } diff --git a/src/client/views/InkingControl.scss b/src/client/views/InkingControl.scss new file mode 100644 index 000000000..0d8fd8784 --- /dev/null +++ b/src/client/views/InkingControl.scss @@ -0,0 +1,135 @@ +@import "global_variables"; +.inking-control { + position: absolute; + left: 70px; + bottom: 70px; + margin: 0; + padding: 0; + display: flex; + label, + input, + option { + font-size: 12px; + } + input[type="range"] { + -webkit-appearance: none; + background-color: transparent; + vertical-align: middle; + margin-top: 8px; + &:focus { + outline: none; + } + &::-webkit-slider-runnable-track { + width: 100%; + height: 3px; + border-radius: 1.5px; + cursor: pointer; + background: $intermediate-color; + } + &::-webkit-slider-thumb { + height: 12px; + width: 12px; + border: 1px solid $intermediate-color; + border-radius: 6px; + background: $light-color; + cursor: pointer; + -webkit-appearance: none; + margin-top: -4px; + } + &::-moz-range-track { + width: 100%; + height: 3px; + border-radius: 1.5px; + cursor: pointer; + background: $light-color; + } + &::-moz-range-thumb { + height: 12px; + width: 12px; + border: 1px solid $intermediate-color; + border-radius: 6px; + background: $light-color; + cursor: pointer; + -webkit-appearance: none; + margin-top: -4px; + } + } + input[type="text"] { + border: none; + padding: 0 0px; + background: transparent; + color: $dark-color; + font-size: 12px; + margin-top: 4px; + } + .ink-panel { + margin: 6px 12px 6px 0; + height: 30px; + vertical-align: middle; + line-height: 36px; + padding: 0 10px; + color: $intermediate-color; + &:first { + margin-top: 0; + } + } + .ink-tools { + display: flex; + background-color: transparent; + border-radius: 0; + padding: 0; + button { + height: 36px; + padding: 0px; + padding-bottom: 3px; + margin-left: 10px; + background-color: transparent; + color: $intermediate-color; + } + button:hover { + transform: scale(1.15); + } + } + .ink-size { + display: flex; + justify-content: space-between; + input[type="text"] { + width: 42px; + } + >* { + margin-right: 6px; + &:last-child { + margin-right: 0; + } + } + } + .ink-color { + display: flex; + position: relative; + padding-right: 0; + label { + margin-right: 6px; + } + .ink-color-display { + border-radius: 11px; + width: 22px; + height: 22px; + margin-top: 6px; + cursor: pointer; + text-align: center; // span { + // color: $light-color; + // font-size: 8px; + // user-select: none; + // } + } + .ink-color-picker { + background-color: $light-color; + border-radius: 5px; + padding: 12px; + position: absolute; + bottom: 36px; + left: -3px; + box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw; + } + } +} \ No newline at end of file diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index eb2172d03..c1519dff8 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -2,10 +2,9 @@ import { observable, action, computed } from "mobx"; import { CirclePicker, ColorResult } from 'react-color' import React = require("react"); -import "./InkingCanvas.scss" import { InkTool } from "../../fields/InkField"; import { observer } from "mobx-react"; -import "./InkingCanvas.scss" +import "./InkingControl.scss" import { library } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faPen, faHighlighter, faEraser, faBan } from '@fortawesome/free-solid-svg-icons'; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss index 9af0df7f5..4341c82f7 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss @@ -4,4 +4,7 @@ width: 20000px; height: 20000px; pointer-events: none; + } + .collectionfreeformlinksview-container { + pointer-events: none; } \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index ea392eaea..ef60aa672 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -94,9 +94,12 @@ export class CollectionFreeFormLinksView extends React.Component - {this.uniqueConnections.map(c => )} +
+ + {this.uniqueConnections.map(c => )} + {this.props.children} - ); +
+ ); } } \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index c979b27a9..81d21d89a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -7,9 +7,6 @@ width: 100%; height: 100%; transform-origin: left top; - .inking-canvas { - transform-origin: 50000px 50000px; - } } .collectionfreeformview-container { .collectionfreeformview > .jsx-parser { @@ -18,9 +15,6 @@ width: 100%; } - .inking-canvas { - transform-origin: 50000px 50000px; - } //nested freeform views // .collectionfreeformview-container { // background-image: linear-gradient(to right, $light-color-secondary 1px, transparent 1px), @@ -48,10 +42,6 @@ background: $light-color-secondary; } - .inking-canvas { - transform-origin: 50000px 50000px; - } - opacity: 0.99; border: 0px solid transparent; border-radius: $border-radius; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index cdc5bdfb0..ae1c775e6 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -314,16 +314,18 @@ export class CollectionFreeFormView extends CollectionViewBase {
super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY))} onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} onWheel={this.onPointerWheel} - style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} tabIndex={0} ref={this.createDropTarget}> + style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} ref={this.createDropTarget}>
{this.backgroundView} - - {this.views} - + + + {this.views} + + {super.getCursors().map(entry => { if (entry.Data.length > 0) { let id = entry.Data[0][0]; diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 7f951864e..d52b662bd 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,4 +1,4 @@ -import { computed, trace, reaction, runInAction, observable } from "mobx"; +import { computed } from "mobx"; import { observer } from "mobx-react"; import { KeyStore } from "../../../fields/KeyStore"; import { NumberField } from "../../../fields/NumberField"; @@ -6,8 +6,7 @@ import { Transform } from "../../util/Transform"; import { DocumentView, DocumentViewProps } from "./DocumentView"; import "./DocumentView.scss"; import React = require("react"); -import { Document } from "../../../fields/Document"; -import { DocumentManager } from "../../util/DocumentManager"; +import { thisExpression } from "babel-types"; @observer @@ -75,6 +74,7 @@ export class CollectionFreeFormDocumentView extends React.Component { @observable private _interactive: boolean = false; @observable private _loaded: boolean = false; - @computed private get curPage() { return this.props.doc.GetNumber(KeyStore.CurPage, -1); } + @computed private get curPage() { return this.props.doc.GetNumber(KeyStore.CurPage, 1); } @computed private get thumbnailPage() { return this.props.doc.GetNumber(KeyStore.ThumbnailPage, -1); } componentDidMount() { @@ -435,8 +435,6 @@ export class PDFBox extends React.Component { @computed get pdfContent() { let page = this.curPage; - if (page == 0) - page = 1; const renderHeight = 2400; let pdfUrl = this.props.doc.GetT(this.props.fieldKey, PDFField); let xf = this.props.doc.GetNumber(KeyStore.NativeHeight, 0) / renderHeight; @@ -463,10 +461,8 @@ export class PDFBox extends React.Component { return [ this._pageInfo.area.filter(() => this._pageInfo.area).map((element: any) => element), this._currAnno.map((element: any) => element), -
- {this.pdfContent} - {proxy} -
+ this.pdfContent, + proxy ]; } -- cgit v1.2.3-70-g09d2 From 5940a2dce5b45382521cd20b4770732dcbc3e732 Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 1 Apr 2019 10:03:06 -0400 Subject: fixed inking problem - blinking cursors don't play nicely with ink --- src/client/views/InkingCanvas.tsx | 15 ++++----------- src/client/views/InkingStroke.tsx | 15 ++++----------- .../collections/collectionFreeForm/PreviewCursor.scss | 16 ++++++++-------- .../collections/collectionFreeForm/PreviewCursor.tsx | 7 ++++--- 4 files changed, 20 insertions(+), 33 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx index fc871e108..15dfb255a 100644 --- a/src/client/views/InkingCanvas.tsx +++ b/src/client/views/InkingCanvas.tsx @@ -19,19 +19,12 @@ interface InkCanvasProps { @observer export class InkingCanvas extends React.Component { static InkOffset: number = 50000; + private _currentStrokeId: string = ""; public static IntersectStrokeRect(stroke: StrokeData, selRect: { left: number, top: number, width: number, height: number }): boolean { - let inside = false; - stroke.pathData.map(val => { - if (selRect.left < val.x - InkingCanvas.InkOffset && selRect.left + selRect.width > val.x - InkingCanvas.InkOffset && + return stroke.pathData.reduce((inside, val) => inside || + (selRect.left < val.x - InkingCanvas.InkOffset && selRect.left + selRect.width > val.x - InkingCanvas.InkOffset && selRect.top < val.y - InkingCanvas.InkOffset && selRect.top + selRect.height > val.y - InkingCanvas.InkOffset) - inside = true; - }); - return inside - } - private _currentStrokeId: string = ""; - - constructor(props: Readonly) { - super(props); + , false); } @computed diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 87b5c43d8..52111c711 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -21,21 +21,14 @@ export class InkingStroke extends React.Component { @observable private _strokeColor: string = this.props.color; @observable private _strokeWidth: string = this.props.width; - deleteStroke = (e: React.MouseEvent): void => { + deleteStroke = (e: React.PointerEvent): void => { if (InkingControl.Instance.selectedTool === InkTool.Eraser && e.buttons === 1) { this.props.deleteCallback(this.props.id); } } parseData = (line: Array<{ x: number, y: number }>): string => { - if (line.length === 0) { - return ""; - } - const pathData = "M " + - line.map(p => { - return p.x + " " + p.y; - }).join(" L "); - return pathData; + return !line.length ? "" : "M " + line.map(p => p.x + " " + p.y).join(" L "); } createStyle() { @@ -57,8 +50,8 @@ export class InkingStroke extends React.Component { return ( + d={pathData} style={{ ...pathStyle, pointerEvents: "all" }} strokeLinejoin="round" strokeLinecap="round" + onPointerOver={this.deleteStroke} onPointerDown={this.deleteStroke} /> ) } } \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.scss b/src/client/views/collections/collectionFreeForm/PreviewCursor.scss index 7d6d5aaab..21210be2b 100644 --- a/src/client/views/collections/collectionFreeForm/PreviewCursor.scss +++ b/src/client/views/collections/collectionFreeForm/PreviewCursor.scss @@ -12,12 +12,12 @@ } //this is an animation for the blinking cursor! -@keyframes blink { - 0% {opacity: 0} - 49%{opacity: 0} - 50% {opacity: 1} -} +// @keyframes blink { +// 0% {opacity: 0} +// 49%{opacity: 0} +// 50% {opacity: 1} +// } -#previewCursor { - animation: blink 1s infinite; -} \ No newline at end of file +// #previewCursor { +// animation: blink 1s infinite; +// } \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx index 339e056a8..877de8846 100644 --- a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx +++ b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx @@ -1,4 +1,4 @@ -import { action, observable } from "mobx"; +import { action, observable, trace } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../../fields/Document"; import { Documents } from "../../../documents/Documents"; @@ -84,11 +84,12 @@ export class PreviewCursor extends React.Component { let p = this.props.getContainerTransform().transformPoint(this._lastX, this._lastY); if (this._visible && this._previewDivRef.current) this._previewDivRef.current!.focus(); + trace(); return ( -
+
{this.props.children} {!this._visible ? (null) : -
I
} +
I
}
) } -- cgit v1.2.3-70-g09d2 From a687a9962c8952074177e52366b69dcce231a18e Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 1 Apr 2019 13:46:32 -0400 Subject: fixed several things related to selections and creating text boxes. moved remote cursor stuff into its own file. --- src/client/util/SelectionManager.ts | 13 ++- src/client/views/InkingCanvas.tsx | 8 +- .../views/collections/CollectionViewBase.tsx | 8 -- .../CollectionFreeFormLinksView.tsx | 7 +- .../CollectionFreeFormRemoteCursors.tsx | 115 ++++++++++++++++++++ .../collectionFreeForm/CollectionFreeFormView.tsx | 118 ++++----------------- .../collections/collectionFreeForm/MarqueeView.tsx | 24 ++--- .../collectionFreeForm/PreviewCursor.tsx | 67 ++++++++---- 8 files changed, 213 insertions(+), 147 deletions(-) create mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx (limited to 'src/client/views/collections') diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 1a711ae64..6d6ae26fc 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -1,5 +1,6 @@ import { observable, action } from "mobx"; import { DocumentView } from "../views/nodes/DocumentView"; +import { Document } from "../../fields/Document" export namespace SelectionManager { class Manager { @@ -29,8 +30,16 @@ export namespace SelectionManager { return manager.SelectedDocuments.indexOf(doc) !== -1; } - export function DeselectAll(): void { - manager.SelectedDocuments = [] + export function DeselectAll(except?: Document): void { + let found: DocumentView | undefined = undefined; + if (except) { + for (let i = 0; i < manager.SelectedDocuments.length; i++) { + let view = manager.SelectedDocuments[i]; + if (view.props.Document == except) + found = view; + } + } + manager.SelectedDocuments.length = 0; } export function SelectedDocuments(): Array { diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx index 15dfb255a..123ff679b 100644 --- a/src/client/views/InkingCanvas.tsx +++ b/src/client/views/InkingCanvas.tsx @@ -101,7 +101,6 @@ export class InkingCanvas extends React.Component { @computed get drawnPaths() { - trace(); // parse data from server let curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1) let paths = Array.from(this.inkData).reduce((paths, [id, strokeData]) => { @@ -111,10 +110,10 @@ export class InkingCanvas extends React.Component { tool={strokeData.tool} deleteCallback={this.removeLine} />) return paths; }, [] as JSX.Element[]); - return [ + return [ {paths.filter(path => path.props.tool == InkTool.Highlighter)} , - + {paths.filter(path => path.props.tool != InkTool.Highlighter)} ]; } @@ -122,11 +121,10 @@ export class InkingCanvas extends React.Component { render() { let svgCanvasStyle = InkingControl.Instance.selectedTool != InkTool.None ? "canSelect" : "noSelect"; - trace(); return (
- {this.props.children} + {(this.props.children as any)() /* bcz: is there a better way to know that children is a function? */} {this.drawnPaths}
) diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx index 48adce4d8..987f3cb6c 100644 --- a/src/client/views/collections/CollectionViewBase.tsx +++ b/src/client/views/collections/CollectionViewBase.tsx @@ -76,14 +76,6 @@ export class CollectionViewBase extends React.Component } } - protected getCursors(): CursorEntry[] { - let doc = this.props.Document; - let id = CurrentUserUtils.id; - let cursors = doc.GetList(KeyStore.Cursors, []); - let notMe = cursors.filter(entry => entry.Data[0][0] !== id); - return id ? notMe : []; - } - @undoBatch @action protected drop(e: Event, de: DragManager.DropEvent): boolean { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index ef60aa672..eb20b3100 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -1,4 +1,4 @@ -import { computed, reaction, runInAction } from "mobx"; +import { computed, reaction, runInAction, trace } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../../fields/Document"; import { FieldWaiting } from "../../../../fields/Field"; @@ -71,7 +71,7 @@ export class CollectionFreeFormLinksView extends React.Component { + let connections = DocumentManager.Instance.LinkedDocumentViews.reduce((drawnPairs, connection) => { let srcViews = this.documentAnchors(connection.a); let targetViews = this.documentAnchors(connection.b); let possiblePairs: { a: Document, b: Document, }[] = []; @@ -90,13 +90,14 @@ export class CollectionFreeFormLinksView extends React.Component ); } render() { return (
- {this.uniqueConnections.map(c => )} + {this.uniqueConnections} {this.props.children}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx new file mode 100644 index 000000000..19382e66f --- /dev/null +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx @@ -0,0 +1,115 @@ +import { action, computed, observable } from "mobx"; +import { observer } from "mobx-react"; +import { Document } from "../../../../fields/Document"; +import { FieldWaiting } from "../../../../fields/Field"; +import { KeyStore } from "../../../../fields/KeyStore"; +import { TextField } from "../../../../fields/TextField"; +import { DragManager } from "../../../util/DragManager"; +import { Transform } from "../../../util/Transform"; +import { undoBatch } from "../../../util/UndoManager"; +import { InkingCanvas } from "../../InkingCanvas"; +import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; +import { DocumentContentsView } from "../../nodes/DocumentContentsView"; +import { DocumentViewProps } from "../../nodes/DocumentView"; +import { COLLECTION_BORDER_WIDTH } from "../CollectionView"; +import { CollectionViewBase, CollectionViewProps, CursorEntry } from "../CollectionViewBase"; +import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView"; +import "./CollectionFreeFormView.scss"; +import { MarqueeView } from "./MarqueeView"; +import React = require("react"); +import v5 = require("uuid/v5"); +import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; + +@observer +export class CollectionFreeFormRemoteCursors extends React.Component { + protected getCursors(): CursorEntry[] { + let doc = this.props.Document; + let id = CurrentUserUtils.id; + let cursors = doc.GetList(KeyStore.Cursors, []); + let notMe = cursors.filter(entry => entry.Data[0][0] !== id); + return id ? notMe : []; + } + + private crosshairs?: HTMLCanvasElement; + drawCrosshairs = (backgroundColor: string) => { + if (this.crosshairs) { + let c = this.crosshairs; + let ctx = c.getContext('2d'); + if (ctx) { + ctx.fillStyle = backgroundColor; + ctx.fillRect(0, 0, 20, 20); + + ctx.fillStyle = "black"; + ctx.lineWidth = 0.5; + + ctx.beginPath(); + + ctx.moveTo(10, 0); + ctx.lineTo(10, 8); + + ctx.moveTo(10, 20); + ctx.lineTo(10, 12); + + ctx.moveTo(0, 10); + ctx.lineTo(8, 10); + + ctx.moveTo(20, 10); + ctx.lineTo(12, 10); + + ctx.stroke(); + + // ctx.font = "10px Arial"; + // ctx.fillText(CurrentUserUtils.email[0].toUpperCase(), 10, 10); + } + } + } + @computed + get sharedCursors() { + return this.getCursors().map(entry => { + if (entry.Data.length > 0) { + let id = entry.Data[0][0]; + let email = entry.Data[0][1]; + let point = entry.Data[1]; + this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22") + return ( +
+ { if (el) this.crosshairs = el }} + width={20} + height={20} + style={{ + position: 'absolute', + width: "20px", + height: "20px", + opacity: 0.5, + borderRadius: "50%", + border: "2px solid black" + }} + /> +

{email[0].toUpperCase()}

+
+ ); + } + }) + } + + render() { + return this.sharedCursors; + } +} \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index ae1c775e6..60fb95ff5 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,4 +1,4 @@ -import { action, computed, observable } from "mobx"; +import { action, computed, observable, trace } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../../fields/Document"; import { FieldWaiting } from "../../../../fields/Field"; @@ -18,6 +18,8 @@ import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); import v5 = require("uuid/v5"); +import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors"; +import { PreviewCursor } from "./PreviewCursor"; @observer export class CollectionFreeFormView extends CollectionViewBase { @@ -60,7 +62,7 @@ export class CollectionFreeFormView extends CollectionViewBase { @computed get panX(): number { return this.props.Document.GetNumber(KeyStore.PanX, 0) } @computed get panY(): number { return this.props.Document.GetNumber(KeyStore.PanY, 0) } @computed get scale(): number { return this.props.Document.GetNumber(KeyStore.Scale, 1); } - @computed get isAnnotationOverlay() { return this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's? + @computed get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's? @computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); } @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); } @computed get zoomScaling() { return this.props.Document.GetNumber(KeyStore.Scale, 1); } @@ -97,13 +99,15 @@ export class CollectionFreeFormView extends CollectionViewBase { @action onPointerDown = (e: React.PointerEvent): void => { - if ((e.button === 2 && this.props.active() && (!this.isAnnotationOverlay || this.zoomScaling != 1)) || e.button == 0) { + if (((e.button === 2 && (!this.isAnnotationOverlay || this.zoomScaling != 1)) || e.button == 0) && this.props.active()) { document.removeEventListener("pointermove", this.onPointerMove); document.addEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); document.addEventListener("pointerup", this.onPointerUp); this._lastX = this.DownX = e.pageX; this._lastY = this.DownY = e.pageY; + if (this.props.isSelected()) + e.stopPropagation(); } } @@ -134,7 +138,6 @@ export class CollectionFreeFormView extends CollectionViewBase { onPointerWheel = (e: React.WheelEvent): void => { this.props.select(false); e.stopPropagation(); - e.preventDefault(); let coefficient = 1000; if (e.ctrlKey) { @@ -265,50 +268,13 @@ export class CollectionFreeFormView extends CollectionViewBase { getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH) getLocalTransform = (): Transform => Transform.Identity.scale(1 / this.scale).translate(this.panX, this.panY); noScaling = () => 1; - - - private crosshairs?: HTMLCanvasElement; - drawCrosshairs = (backgroundColor: string) => { - if (this.crosshairs) { - let c = this.crosshairs; - let ctx = c.getContext('2d'); - if (ctx) { - ctx.fillStyle = backgroundColor; - ctx.fillRect(0, 0, 20, 20); - - ctx.fillStyle = "black"; - ctx.lineWidth = 0.5; - - ctx.beginPath(); - - ctx.moveTo(10, 0); - ctx.lineTo(10, 8); - - ctx.moveTo(10, 20); - ctx.lineTo(10, 12); - - ctx.moveTo(0, 10); - ctx.lineTo(8, 10); - - ctx.moveTo(20, 10); - ctx.lineTo(12, 10); - - ctx.stroke(); - - // ctx.font = "10px Arial"; - // ctx.fillText(CurrentUserUtils.email[0].toUpperCase(), 10, 10); - } - } - } + childViews = () => this.views; render() { let [dx, dy] = [this.centeringShiftX, this.centeringShiftY]; const panx: number = -this.props.Document.GetNumber(KeyStore.PanX, 0); const pany: number = -this.props.Document.GetNumber(KeyStore.PanY, 0); - // const panx: number = this.props.Document.GetNumber(KeyStore.PanX, 0) + this.centeringShiftX; - // const pany: number = this.props.Document.GetNumber(KeyStore.PanY, 0) + this.centeringShiftY; - // console.log("center:", this.getLocalTransform().transformPoint(this.centeringShiftX, this.centeringShiftY)); return (
-
- {this.backgroundView} - - - {this.views} - - - {super.getCursors().map(entry => { - if (entry.Data.length > 0) { - let id = entry.Data[0][0]; - let email = entry.Data[0][1]; - let point = entry.Data[1]; - this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22") - return ( -
- { if (el) this.crosshairs = el }} - width={20} - height={20} - style={{ - position: 'absolute', - width: "20px", - height: "20px", - opacity: 0.5, - borderRadius: "50%", - border: "2px solid black" - }} - /> -

{email[0].toUpperCase()}

-
- ); - } - })} -
- {this.overlayView} + +
+ {this.backgroundView} + + + {this.childViews} + + + +
+ {this.overlayView} +
); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 2b0e7d228..20132a4b1 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,4 +1,4 @@ -import { action, computed, observable } from "mobx"; +import { action, computed, observable, trace } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../../fields/Document"; import { FieldWaiting } from "../../../../fields/Field"; @@ -21,7 +21,6 @@ interface MarqueeViewProps { activeDocuments: () => Document[]; selectDocuments: (docs: Document[]) => void; removeDocument: (doc: Document) => boolean; - addLiveTextDocument: (doc: Document) => void; } @observer @@ -77,10 +76,11 @@ export class MarqueeView extends React.Component onPointerUp = (e: PointerEvent): void => { this.cleanupInteractions(true); this._visible = false; + let mselect = this.marqueeSelect(); if (!e.shiftKey) { - SelectionManager.DeselectAll(); + SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document); } - this.props.selectDocuments(this.marqueeSelect()); + this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]); } intersectRect(r1: { left: number, top: number, width: number, height: number }, @@ -184,17 +184,17 @@ export class MarqueeView extends React.Component return selection; } - render() { + @computed + 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
- - {this.props.children} - {!this._visible ? (null) : -
} + return
+ } - + render() { + return
+ {this.props.children} + {!this._visible ? (null) : this.marqueeDiv}
; } } \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx index 877de8846..93c98f7b0 100644 --- a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx +++ b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx @@ -1,4 +1,4 @@ -import { action, observable, trace } from "mobx"; +import { action, observable, trace, computed, reaction } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../../fields/Document"; import { Documents } from "../../../documents/Documents"; @@ -6,6 +6,7 @@ import { Transform } from "../../../util/Transform"; import { CollectionFreeFormView } from "./CollectionFreeFormView"; import "./PreviewCursor.scss"; import React = require("react"); +import { interfaceDeclaration } from "babel-types"; export interface PreviewCursorProps { @@ -23,27 +24,29 @@ export class PreviewCursor extends React.Component { @observable public DownX: number = 0; @observable public DownY: number = 0; _showOnUp: boolean = false; - public _previewDivRef = React.createRef(); @action cleanupInteractions = () => { document.removeEventListener("pointerup", this.onPointerUp, true); + document.removeEventListener("pointermove", this.onPointerMove, true); } @action onPointerDown = (e: React.PointerEvent) => { - this._visible = false; - document.removeEventListener("keypress", this.onKeyPress, false); - this._showOnUp = true; - this._lastX = this.DownX = e.pageX; - this._lastY = this.DownY = e.pageY; - document.addEventListener("pointerup", this.onPointerUp, true); - document.addEventListener("pointermove", this.onPointerMove, true); + if (e.button == 0 && this.props.container.props.active()) { + document.removeEventListener("keypress", this.onKeyPress, false); + this._showOnUp = true; + this.DownX = e.pageX; + this.DownY = e.pageY; + document.addEventListener("pointerup", this.onPointerUp, true); + document.addEventListener("pointermove", this.onPointerMove, true); + } } @action onPointerMove = (e: PointerEvent): void => { if (Math.abs(this.DownX - e.clientX) > 4 || Math.abs(this.DownY - e.clientY) > 4) { this._showOnUp = false; + this._visible = false; } } @@ -51,6 +54,8 @@ export class PreviewCursor extends React.Component { onPointerUp = (e: PointerEvent): void => { if (this._showOnUp) { document.addEventListener("keypress", this.onKeyPress, false); + this._lastX = this.DownX; + this._lastY = this.DownY; this._visible = true; } this.cleanupInteractions(); @@ -72,25 +77,43 @@ export class PreviewCursor extends React.Component { e.stopPropagation(); } } - //when focus is lost, this will remove the preview cursor - @action - onBlur = (): void => { - this._visible = false; + + getPoint = () => this.props.getContainerTransform().transformPoint(this._lastX, this._lastY); + getVisible = () => this._visible; + setVisible = (v: boolean) => { + this._visible = v; document.removeEventListener("keypress", this.onKeyPress, false); } - render() { - //get local position and place cursor there! - let p = this.props.getContainerTransform().transformPoint(this._lastX, this._lastY); - if (this._visible && this._previewDivRef.current) - this._previewDivRef.current!.focus(); - trace(); return ( -
+
{this.props.children} - {!this._visible ? (null) : -
I
} +
) } +} + +export interface PromptProps { + getPoint: () => number[]; + getVisible: () => boolean; + setVisible: (v: boolean) => void; +} + +@observer +export class PreviewCursorPrompt extends React.Component { + private _promptRef = React.createRef(); + + //when focus is lost, this will remove the preview cursor + @action onBlur = (): void => this.props.setVisible(false); + + render() { + let p = this.props.getPoint(); + if (this.props.getVisible() && this._promptRef.current) + this._promptRef.current.focus(); + return
+ I +
; + } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 8448679e0a260ec881c54a10578bc464cd38f04e Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 1 Apr 2019 17:05:42 -0400 Subject: added dragging multiple items. --- src/client/northstar/dash-nodes/HistogramBox.tsx | 4 +- src/client/util/DragManager.ts | 168 +++++++++++---------- src/client/views/DocumentDecorations.scss | 6 + src/client/views/DocumentDecorations.tsx | 109 +++++++++---- src/client/views/Main.tsx | 7 +- .../views/collections/CollectionDockingView.tsx | 11 +- src/client/views/collections/CollectionView.tsx | 2 +- .../views/collections/CollectionViewBase.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 21 ++- src/client/views/nodes/DocumentView.tsx | 15 +- 10 files changed, 213 insertions(+), 134 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx index a968def96..49ebe5ebc 100644 --- a/src/client/northstar/dash-nodes/HistogramBox.tsx +++ b/src/client/northstar/dash-nodes/HistogramBox.tsx @@ -54,7 +54,7 @@ export class HistogramBox extends React.Component { @action dropX = (e: Event, de: DragManager.DropEvent) => { if (de.data instanceof DragManager.DocumentDragData) { - let h = de.data.draggedDocument.GetT(KeyStore.Data, HistogramField); + let h = de.data.draggedDocuments[0].GetT(KeyStore.Data, HistogramField); if (h && h != FieldWaiting) { this.HistoOp.X = h.Data.X; } @@ -65,7 +65,7 @@ export class HistogramBox extends React.Component { @action dropY = (e: Event, de: DragManager.DropEvent) => { if (de.data instanceof DragManager.DocumentDragData) { - let h = de.data.draggedDocument.GetT(KeyStore.Data, HistogramField); + let h = de.data.draggedDocuments[0].GetT(KeyStore.Data, HistogramField); if (h && h != FieldWaiting) { this.HistoOp.Y = h.Data.X; } diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 70b1c9829..9ffe964ef 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -14,9 +14,9 @@ export function setupDrag(_reference: React.RefObject, docFunc: document.removeEventListener("pointermove", onRowMove); document.removeEventListener('pointerup', onRowUp); - var dragData = new DragManager.DocumentDragData(docFunc()); + var dragData = new DragManager.DocumentDragData([docFunc()]); dragData.removeDocument = removeFunc; - DragManager.StartDocumentDrag(_reference.current!, dragData); + DragManager.StartDocumentDrag([_reference.current!], dragData); }); let onRowUp = action((e: PointerEvent): void => { document.removeEventListener("pointermove", onRowMove); @@ -27,7 +27,7 @@ export function setupDrag(_reference: React.RefObject, docFunc: if (e.button == 0) { e.stopPropagation(); if (e.shiftKey) { - CollectionDockingView.Instance.StartOtherDrag(docFunc(), e); + CollectionDockingView.Instance.StartOtherDrag([docFunc()], e); } else { document.addEventListener("pointermove", onRowMove); document.addEventListener('pointerup', onRowUp); @@ -101,20 +101,21 @@ export namespace DragManager { } export class DocumentDragData { - constructor(dragDoc: Document) { - this.draggedDocument = dragDoc; - this.droppedDocument = dragDoc; + constructor(dragDoc: Document[]) { + this.draggedDocuments = dragDoc; + this.droppedDocuments = dragDoc; } - draggedDocument: Document; - droppedDocument: Document; + draggedDocuments: Document[]; + droppedDocuments: Document[]; xOffset?: number; yOffset?: number; aliasOnDrop?: boolean; removeDocument?: (collectionDrop: CollectionView) => void; [id: string]: any; } - export function StartDocumentDrag(ele: HTMLElement, dragData: DocumentDragData, options?: DragOptions) { - StartDrag(ele, dragData, options, (dropData: { [id: string]: any }) => dropData.droppedDocument = dragData.aliasOnDrop ? dragData.draggedDocument.CreateAlias() : dragData.draggedDocument); + + export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, options?: DragOptions) { + StartDrag(eles, dragData, options, (dropData: { [id: string]: any }) => dropData.droppedDocuments = dragData.aliasOnDrop ? dragData.draggedDocuments.map(d => d.CreateAlias()) : dragData.draggedDocuments); } export class LinkDragData { @@ -125,49 +126,58 @@ export namespace DragManager { [id: string]: any; } export function StartLinkDrag(ele: HTMLElement, dragData: LinkDragData, options?: DragOptions) { - StartDrag(ele, dragData, options); + StartDrag([ele], dragData, options); } - function StartDrag(ele: HTMLElement, dragData: { [id: string]: any }, options?: DragOptions, finishDrag?: (dropData: { [id: string]: any }) => void) { + function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, options?: DragOptions, finishDrag?: (dropData: { [id: string]: any }) => void) { if (!dragDiv) { dragDiv = document.createElement("div"); dragDiv.className = "dragManager-dragDiv" DragManager.Root().appendChild(dragDiv); } - const w = ele.offsetWidth, h = ele.offsetHeight; - const rect = ele.getBoundingClientRect(); - const scaleX = rect.width / w, scaleY = rect.height / h; - let x = rect.left, y = rect.top; - // const offsetX = e.x - rect.left, offsetY = e.y - rect.top; - - let dragElement = ele.cloneNode(true) as HTMLElement; - dragElement.style.opacity = "0.7"; - dragElement.style.position = "absolute"; - dragElement.style.bottom = ""; - dragElement.style.left = ""; - dragElement.style.transformOrigin = "0 0"; - dragElement.style.zIndex = "1000"; - dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`; - dragElement.style.width = `${rect.width / scaleX}px`; - dragElement.style.height = `${rect.height / scaleY}px`; - - // bcz: PDFs don't show up if you clone them because they contain a canvas. - // however, PDF's have a thumbnail field that contains an image of their canvas. - // So we replace the pdf's canvas with the image thumbnail - const doc: Document = dragData["draggedDocument"]; - if (doc) { - var pdfBox = dragElement.getElementsByClassName("pdfBox-cont")[0] as HTMLElement; - let thumbnail = doc.GetT(KeyStore.Thumbnail, ImageField); - if (pdfBox && pdfBox.childElementCount && thumbnail) { - let img = new Image(); - img!.src = thumbnail.toString(); - img!.style.position = "absolute"; - img!.style.width = `${rect.width / scaleX}px`; - img!.style.height = `${rect.height / scaleY}px`; - pdfBox.replaceChild(img!, pdfBox.children[0]) + + let scaleXs: number[] = []; + let scaleYs: number[] = []; + let xs: number[] = []; + let ys: number[] = []; + + const docs: Document[] = dragData instanceof DocumentDragData ? dragData.draggedDocuments : []; + let dragElements = eles.map(ele => { + const w = ele.offsetWidth, h = ele.offsetHeight; + const rect = ele.getBoundingClientRect(); + const scaleX = rect.width / w, scaleY = rect.height / h; + let x = rect.left, y = rect.top; + xs.push(x); ys.push(y); + scaleXs.push(scaleX); scaleYs.push(scaleY); + let dragElement = ele.cloneNode(true) as HTMLElement; + dragElement.style.opacity = "0.7"; + dragElement.style.position = "absolute"; + dragElement.style.bottom = ""; + dragElement.style.left = ""; + dragElement.style.transformOrigin = "0 0"; + dragElement.style.zIndex = "1000"; + dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`; + dragElement.style.width = `${rect.width / scaleX}px`; + dragElement.style.height = `${rect.height / scaleY}px`; + + // bcz: PDFs don't show up if you clone them because they contain a canvas. + // however, PDF's have a thumbnail field that contains an image of their canvas. + // So we replace the pdf's canvas with the image thumbnail + if (docs.length) { + var pdfBox = dragElement.getElementsByClassName("pdfBox-cont")[0] as HTMLElement; + let thumbnail = docs[0].GetT(KeyStore.Thumbnail, ImageField); + if (pdfBox && pdfBox.childElementCount && thumbnail) { + let img = new Image(); + img!.src = thumbnail.toString(); + img!.style.position = "absolute"; + img!.style.width = `${rect.width / scaleX}px`; + img!.style.height = `${rect.height / scaleY}px`; + pdfBox.replaceChild(img!, pdfBox.children[0]) + } } - } - dragDiv.appendChild(dragElement); + dragDiv.appendChild(dragElement); + return dragElement; + }); let hideSource = false; if (options) { @@ -177,62 +187,64 @@ export namespace DragManager { hideSource = options.hideSource(); } } - const wasHidden = ele.hidden; - if (hideSource) { - ele.hidden = true; - } + eles.map(ele => ele.hidden = hideSource); + const moveHandler = (e: PointerEvent) => { e.stopPropagation(); e.preventDefault(); - x += e.movementX; - y += e.movementY; if (dragData instanceof DocumentDragData) dragData.aliasOnDrop = e.ctrlKey || e.altKey; if (e.shiftKey) { abortDrag(); - CollectionDockingView.Instance.StartOtherDrag(doc, { pageX: e.pageX, pageY: e.pageY, preventDefault: () => { }, button: 0 }); + CollectionDockingView.Instance.StartOtherDrag(docs, { pageX: e.pageX, pageY: e.pageY, preventDefault: () => { }, button: 0 }); } - dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`; + dragElements.map((dragElement, i) => dragElement.style.transform = `translate(${xs[i] += e.movementX}px, ${ys[i] += e.movementY}px) scale(${scaleXs[i]}, ${scaleYs[i]})`); }; const abortDrag = () => { document.removeEventListener("pointermove", moveHandler, true); document.removeEventListener("pointerup", upHandler); - dragDiv.removeChild(dragElement); - ele.hidden = false; + dragElements.map(dragElement => dragDiv.removeChild(dragElement)); + eles.map(ele => ele.hidden = false); } const upHandler = (e: PointerEvent) => { abortDrag(); - FinishDrag(ele, e, dragData, options, finishDrag); + FinishDrag(eles, e, dragData, options, finishDrag); }; document.addEventListener("pointermove", moveHandler, true); document.addEventListener("pointerup", upHandler); } - function FinishDrag(dragEle: HTMLElement, e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (dragData: { [index: string]: any }) => void) { - let parent = dragEle.parentElement; - if (parent) - parent.removeChild(dragEle); + function FinishDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (dragData: { [index: string]: any }) => void) { + let removed = dragEles.map(dragEle => { + let parent = dragEle.parentElement; + if (parent) + parent.removeChild(dragEle); + return [dragEle, parent]; + }); const target = document.elementFromPoint(e.x, e.y); - if (parent) - parent.appendChild(dragEle); - if (!target) { - return; - } - if (finishDrag) - finishDrag(dragData); - - target.dispatchEvent(new CustomEvent("dashOnDrop", { - bubbles: true, - detail: { - x: e.x, - y: e.y, - data: dragData + removed.map(r => { + let dragEle: HTMLElement = r[0]!; + let parent: HTMLElement | null = r[1]; + if (parent) + parent.appendChild(dragEle); + }); + if (target) { + if (finishDrag) + finishDrag(dragData); + + target.dispatchEvent(new CustomEvent("dashOnDrop", { + bubbles: true, + detail: { + x: e.x, + y: e.y, + data: dragData + } + })); + + if (options) { + options.handlers.dragComplete({}); } - })); - - if (options) { - options.handlers.dragComplete({}); } DocumentDecorations.Instance.Hidden = false; } diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 11595aa01..7a43f3087 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -32,6 +32,12 @@ } } +.documentDecorations-background { + background:lightblue; + position: absolute; + opacity: 0.1; +} + // position: absolute; // display: grid; // z-index: 1000; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 47098c3b5..faba3b6ea 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -1,14 +1,14 @@ -import { observable, computed, action } from "mobx"; -import React = require("react"); -import { SelectionManager } from "../util/SelectionManager"; +import { action, computed, observable, trace } from "mobx"; import { observer } from "mobx-react"; -import './DocumentDecorations.scss' -import { KeyStore } from '../../fields/KeyStore' +import { KeyStore } from '../../fields/KeyStore'; +import { ListField } from "../../fields/ListField"; import { NumberField } from "../../fields/NumberField"; -import { props } from "bluebird"; import { DragManager } from "../util/DragManager"; +import { SelectionManager } from "../util/SelectionManager"; +import { CollectionView } from "./collections/CollectionView"; +import './DocumentDecorations.scss'; import { LinkMenu } from "./nodes/LinkMenu"; -import { ListField } from "../../fields/ListField"; +import React = require("react"); const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -22,6 +22,7 @@ export class DocumentDecorations extends React.Component { private _resizeBorderWidth = 16; private _linkButton = React.createRef(); @observable private _hidden = false; + @observable private _dragging = false; constructor(props: Readonly<{}>) { super(props) @@ -50,6 +51,50 @@ export class DocumentDecorations extends React.Component { public get Hidden() { return this._hidden; } public set Hidden(value: boolean) { this._hidden = value; } + _lastDrag: number[] = [0, 0]; + onBackgroundDown = (e: React.PointerEvent): void => { + document.removeEventListener("pointermove", this.onBackgroundMove); + document.addEventListener("pointermove", this.onBackgroundMove); + document.removeEventListener("pointerup", this.onBackgroundUp); + document.addEventListener("pointerup", this.onBackgroundUp); + this._lastDrag = [e.clientX, e.clientY] + e.stopPropagation(); + e.preventDefault(); + } + + @action + onBackgroundMove = (e: PointerEvent): void => { + let dragDocView = SelectionManager.SelectedDocuments()[0]; + const [left, top] = dragDocView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); + let dragData = new DragManager.DocumentDragData(SelectionManager.SelectedDocuments().map(dv => dv.props.Document)); + dragData.aliasOnDrop = false; + dragData.xOffset = e.x - left; + dragData.yOffset = e.y - top; + dragData.removeDocument = (dropCollectionView: CollectionView) => + dragData.draggedDocuments.map(d => { + if (dragDocView.props.RemoveDocument && dragDocView.props.ContainingCollectionView !== dropCollectionView) { + dragDocView.props.RemoveDocument(d); + } + }); + this._dragging = true; + document.removeEventListener("pointermove", this.onBackgroundMove); + document.removeEventListener("pointerup", this.onBackgroundUp); + DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(docView => (docView as any)._mainCont!.current!), dragData, { + handlers: { + dragComplete: action(() => this._dragging = false), + }, + hideSource: true + }) + e.stopPropagation(); + } + + onBackgroundUp = (e: PointerEvent): void => { + document.removeEventListener("pointermove", this.onBackgroundMove); + document.removeEventListener("pointerup", this.onBackgroundUp); + e.stopPropagation(); + e.preventDefault(); + } + onPointerDown = (e: React.PointerEvent): void => { e.stopPropagation(); if (e.button === 0) { @@ -191,7 +236,6 @@ export class DocumentDecorations extends React.Component { // buttonOnPointerUp = (e: React.PointerEvent): void => { // e.stopPropagation(); // } - render() { var bounds = this.Bounds; if (this.Hidden) { @@ -218,25 +262,36 @@ export class DocumentDecorations extends React.Component { ); } return ( -
-
e.preventDefault()}>
-
e.preventDefault()}>
-
e.preventDefault()}>
-
e.preventDefault()}>
-
-
e.preventDefault()}>
-
e.preventDefault()}>
-
e.preventDefault()}>
-
e.preventDefault()}>
- -
{linkButton}
- -
+
+
1 ? 1000 : 0, + }} onPointerDown={this.onBackgroundDown} onContextMenu={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation() }} > +
+
+
e.preventDefault()}>
+
e.preventDefault()}>
+
e.preventDefault()}>
+
e.preventDefault()}>
+
+
e.preventDefault()}>
+
e.preventDefault()}>
+
e.preventDefault()}>
+
e.preventDefault()}>
+ +
{linkButton}
+ +
+
) } } \ No newline at end of file diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 75855c3d1..6f66f8f38 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -218,7 +218,8 @@ export class Main extends React.Component { focusDocument = (doc: Document) => { } noScaling = () => 1; - get content() { + @computed + get mainContent() { return !this.mainContainer ? (null) : + runInAction(() => { this.pwidth = r.entry.width; this.pheight = r.entry.height; })}> {({ measureRef }) =>
- {this.content} + {this.mainContent}
}
- {this.nodesMenu} {this.miscButtons} diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index f123149dd..c6cbc05e7 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -47,9 +47,10 @@ export class CollectionDockingView extends React.Component { }, button: 0 }) + public StartOtherDrag(dragDocs: Document[], e: any) { + dragDocs.map(dragDoc => + this.AddRightSplit(dragDoc, true).contentItems[0].tab._dragListener. + onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: () => { }, button: 0 })); } @action @@ -199,7 +200,7 @@ export class CollectionDockingView extends React.Component) => { if (f instanceof Document) - DragManager.StartDocumentDrag(tab, new DragManager.DocumentDragData(f as Document), + DragManager.StartDocumentDrag([tab], new DragManager.DocumentDragData([f as Document]), { handlers: { dragComplete: action(() => { }), @@ -265,6 +266,7 @@ export class CollectionDockingView extends React.Component { } render() { + trace(); if (!this._document) return (null); var content = diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index a1498e0ae..014aa1d8f 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -113,7 +113,7 @@ export class CollectionView extends React.Component { if (index !== -1) { value.splice(index, 1) - SelectionManager.DeselectAll() + //SelectionManager.DeselectAll() ContextMenu.Instance.clearItems() return true; } diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx index 987f3cb6c..316d20c9d 100644 --- a/src/client/views/collections/CollectionViewBase.tsx +++ b/src/client/views/collections/CollectionViewBase.tsx @@ -82,9 +82,9 @@ export class CollectionViewBase extends React.Component if (de.data instanceof DragManager.DocumentDragData) { if (de.data.aliasOnDrop) { [KeyStore.Width, KeyStore.Height, KeyStore.CurPage].map(key => - de.data.draggedDocument.GetTAsync(key, NumberField, (f: Opt) => f ? de.data.droppedDocument.SetNumber(key, f.Data) : null)); + de.data.draggedDocuments.GetTAsync(key, NumberField, (f: Opt) => f ? de.data.droppedDocument.SetNumber(key, f.Data) : null)); } - let added = this.props.addDocument(de.data.droppedDocument, false); + let added = de.data.droppedDocuments.reduce((added, d) => this.props.addDocument(d, false), true); if (added && de.data.removeDocument && !de.data.aliasOnDrop) { de.data.removeDocument(this.props.CollectionView); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 60fb95ff5..c5178f69d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -77,13 +77,20 @@ export class CollectionFreeFormView extends CollectionViewBase { let screenX = de.x - (de.data.xOffset as number || 0); let screenY = de.y - (de.data.yOffset as number || 0); const [x, y] = this.getTransform().transformPoint(screenX, screenY); - de.data.droppedDocument.SetNumber(KeyStore.X, x); - de.data.droppedDocument.SetNumber(KeyStore.Y, y); - if (!de.data.droppedDocument.GetNumber(KeyStore.Width, 0)) { - de.data.droppedDocument.SetNumber(KeyStore.Width, 300); - de.data.droppedDocument.SetNumber(KeyStore.Height, 300); - } - this.bringToFront(de.data.droppedDocument); + let dragDoc = de.data.draggedDocuments[0]; + let dragX = dragDoc.GetNumber(KeyStore.X, 0); + let dragY = dragDoc.GetNumber(KeyStore.Y, 0); + de.data.draggedDocuments.map(d => { + let docX = d.GetNumber(KeyStore.X, 0); + let docY = d.GetNumber(KeyStore.Y, 0); + d.SetNumber(KeyStore.X, x + (docX - dragX)); + d.SetNumber(KeyStore.Y, y + (docY - dragY)); + if (!d.GetNumber(KeyStore.Width, 0)) { + d.SetNumber(KeyStore.Width, 300); + d.SetNumber(KeyStore.Height, 300); + } + this.bringToFront(d); + }) } return true; } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 71613ca4f..1195128dc 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -106,7 +106,7 @@ export class DocumentView extends React.Component { if (this.props.isTopMost) { this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey); } - else CollectionDockingView.Instance.StartOtherDrag(this.props.Document, e); + else CollectionDockingView.Instance.StartOtherDrag([this.props.Document], e); e.stopPropagation(); } else { if (this.active && !e.isDefaultPrevented()) { @@ -125,9 +125,7 @@ export class DocumentView extends React.Component { if (this._mainCont.current) { this.dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } }); } - runInAction(() => { - DocumentManager.Instance.DocumentViews.push(this); - }) + runInAction(() => DocumentManager.Instance.DocumentViews.push(this)) this._reactionDisposer = reaction( () => this.props.ContainingCollectionView && this.props.ContainingCollectionView.SelectedDocs.slice(), () => { @@ -149,10 +147,7 @@ export class DocumentView extends React.Component { if (this.dropDisposer) { this.dropDisposer(); } - runInAction(() => { - DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1); - - }) + runInAction(() => DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1)) if (this._reactionDisposer) { this._reactionDisposer(); } @@ -161,7 +156,7 @@ export class DocumentView extends React.Component { startDragging(x: number, y: number, dropAliasOfDraggedDoc: boolean) { if (this._mainCont.current) { const [left, top] = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); - let dragData = new DragManager.DocumentDragData(this.props.Document); + let dragData = new DragManager.DocumentDragData([this.props.Document]); dragData.aliasOnDrop = dropAliasOfDraggedDoc; dragData.xOffset = x - left; dragData.yOffset = y - top; @@ -170,7 +165,7 @@ export class DocumentView extends React.Component { this.props.RemoveDocument(this.props.Document); } } - DragManager.StartDocumentDrag(this._mainCont.current, dragData, { + DragManager.StartDocumentDrag([this._mainCont.current], dragData, { handlers: { dragComplete: action(() => { }), }, -- cgit v1.2.3-70-g09d2 From 8b48b3a4afcd3d09b87f966d26fa9e21029d4cc1 Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 1 Apr 2019 17:06:09 -0400 Subject: from last --- src/client/views/collections/CollectionDockingView.tsx | 2 -- 1 file changed, 2 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index c6cbc05e7..39e0dd989 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -266,7 +266,6 @@ export class CollectionDockingView extends React.Component { } render() { - trace(); if (!this._document) return (null); var content = -- cgit v1.2.3-70-g09d2 From 85e1eeb77241303307ff5f98d550663e18b807b8 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 2 Apr 2019 20:05:13 -0400 Subject: fixed collection from marquee --- .../collections/collectionFreeForm/MarqueeView.tsx | 45 +++++++++++----------- 1 file changed, 23 insertions(+), 22 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 20132a4b1..e2239c8be 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -102,7 +102,7 @@ export class MarqueeView extends React.Component if (e.key == "Backspace" || e.key == "Delete") { this.marqueeSelect().map(d => this.props.removeDocument(d)); let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField); - if (ink && ink != FieldWaiting && ink.Data) { + if (ink && ink != FieldWaiting) { this.marqueeInkDelete(ink.Data); } this.cleanupInteractions(); @@ -118,22 +118,21 @@ export class MarqueeView extends React.Component return d; }); let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField); - if (ink && ink != FieldWaiting && ink.Data) { - //setTimeout(() => { - let newCollection = Documents.FreeformDocument(selected, { - x: bounds.left, - y: bounds.top, - panx: 0, - pany: 0, - width: bounds.width, - height: bounds.height, - backgroundColor: "Transparent", - ink: this.marqueeInkSelect(ink.Data), - title: "a nested collection" - }); - this.props.addDocument(newCollection, false); - this.marqueeInkDelete(ink.Data); - } + let inkData = ink && ink != FieldWaiting ? ink.Data : undefined; + //setTimeout(() => { + let newCollection = Documents.FreeformDocument(selected, { + x: bounds.left, + y: bounds.top, + panx: 0, + pany: 0, + width: bounds.width, + height: bounds.height, + backgroundColor: "Transparent", + ink: inkData ? this.marqueeInkSelect(inkData) : undefined, + title: "a nested collection" + }); + this.props.addDocument(newCollection, false); + this.marqueeInkDelete(inkData); // }, 100); this.cleanupInteractions(); } @@ -159,15 +158,17 @@ export class MarqueeView extends React.Component } @action - marqueeInkDelete(ink: Map, ) { + marqueeInkDelete(ink?: Map) { // bcz: this appears to work but when you restart all the deleted strokes come back -- InkField isn't observing its changes so they aren't written to the DB. // ink.forEach((value: StrokeData, key: string, map: any) => // InkingCanvas.IntersectStrokeRect(value, this.Bounds) && ink.delete(key)); - let idata = new Map(); - ink.forEach((value: StrokeData, key: string, map: any) => - !InkingCanvas.IntersectStrokeRect(value, this.Bounds) && idata.set(key, value)); - this.props.container.props.Document.SetDataOnPrototype(KeyStore.Ink, idata, InkField); + if (ink) { + let idata = new Map(); + ink.forEach((value: StrokeData, key: string, map: any) => + !InkingCanvas.IntersectStrokeRect(value, this.Bounds) && idata.set(key, value)); + this.props.container.props.Document.SetDataOnPrototype(KeyStore.Ink, idata, InkField); + } } marqueeSelect() { -- cgit v1.2.3-70-g09d2 From 1ebf40ba117ec063ff58ee750e6cc57aa3f3abfb Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 3 Apr 2019 15:49:42 -0400 Subject: fixed pen ink. fixed alias dropping. switched PDFs to use svg --- src/client/views/InkingCanvas.tsx | 2 +- src/client/views/collections/CollectionViewBase.tsx | 3 ++- src/client/views/nodes/PDFBox.tsx | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx index cad4b74b1..45ca52d00 100644 --- a/src/client/views/InkingCanvas.tsx +++ b/src/client/views/InkingCanvas.tsx @@ -134,7 +134,7 @@ export class InkingCanvas extends React.Component { {paths.filter(path => path.props.tool == InkTool.Highlighter)} , + style={{ left: `${this.inkMidX - this.maxCanvasDim}px`, top: `${this.inkMidY - this.maxCanvasDim}px` }}> {paths.filter(path => path.props.tool != InkTool.Highlighter)} ]; } diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx index 316d20c9d..2b0689800 100644 --- a/src/client/views/collections/CollectionViewBase.tsx +++ b/src/client/views/collections/CollectionViewBase.tsx @@ -82,7 +82,8 @@ export class CollectionViewBase extends React.Component if (de.data instanceof DragManager.DocumentDragData) { if (de.data.aliasOnDrop) { [KeyStore.Width, KeyStore.Height, KeyStore.CurPage].map(key => - de.data.draggedDocuments.GetTAsync(key, NumberField, (f: Opt) => f ? de.data.droppedDocument.SetNumber(key, f.Data) : null)); + de.data.draggedDocuments.map((draggedDocument: Document, i: number) => + draggedDocument.GetTAsync(key, NumberField, (f: Opt) => f ? de.data.droppedDocuments[i].SetNumber(key, f.Data) : null))); } let added = de.data.droppedDocuments.reduce((added, d) => this.props.addDocument(d, false), true); if (added && de.data.removeDocument && !de.data.aliasOnDrop) { diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 28a1f9757..3cb3cf782 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -439,7 +439,7 @@ export class PDFBox extends React.Component { let pdfUrl = this.props.doc.GetT(this.props.fieldKey, PDFField); let xf = this.props.doc.GetNumber(KeyStore.NativeHeight, 0) / renderHeight; return
- + {({ measureRef }) =>
-- cgit v1.2.3-70-g09d2 From b78aff5115862cbfa5e704422cb738aa7fd73c64 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 4 Apr 2019 14:13:29 -0400 Subject: fixed a number of smaller issues related to linking --- src/client/northstar/dash-nodes/HistogramBox.scss | 5 + src/client/util/DragManager.ts | 533 +++++++------- src/client/util/SelectionManager.ts | 74 +- src/client/views/DocumentDecorations.scss | 61 +- src/client/views/DocumentDecorations.tsx | 81 ++- src/client/views/InkingCanvas.scss | 2 + src/client/views/Main.scss | 7 + .../views/collections/CollectionDockingView.scss | 2 +- .../views/collections/CollectionPDFView.scss | 2 + .../views/collections/CollectionVideoView.scss | 2 + .../views/collections/CollectionViewBase.tsx | 20 + .../CollectionFreeFormLinkView.tsx | 8 +- .../CollectionFreeFormLinksView.scss | 2 + .../collectionFreeForm/CollectionFreeFormView.scss | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 13 +- .../collectionFreeForm/MarqueeView.scss | 2 + .../collectionFreeForm/PreviewCursor.scss | 4 + .../views/nodes/CollectionFreeFormDocumentView.tsx | 4 + src/client/views/nodes/DocumentView.scss | 2 + src/client/views/nodes/PDFBox.scss | 4 + src/client/views/nodes/WebBox.scss | 2 + src/fields/Document.ts | 773 +++++++++++---------- 22 files changed, 882 insertions(+), 727 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/northstar/dash-nodes/HistogramBox.scss b/src/client/northstar/dash-nodes/HistogramBox.scss index b11840a65..94da36e29 100644 --- a/src/client/northstar/dash-nodes/HistogramBox.scss +++ b/src/client/northstar/dash-nodes/HistogramBox.scss @@ -1,6 +1,8 @@ .histogrambox-container { padding: 0vw; position: absolute; + top: 0; + left:0; text-align: center; width: 100%; height: 100%; @@ -8,6 +10,7 @@ } .histogrambox-xaxislabel { position:absolute; + left:0; width:100%; text-align: center; bottom:0; @@ -19,11 +22,13 @@ position:absolute; height:100%; width: 25px; + left:0; bottom:0; background: lightgray; } .histogrambox-yaxislabel-text { position:absolute; + left:0; transform-origin: left; transform: rotate(-90deg); text-align: center; diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index c0f482e18..e8f8cce7c 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -8,304 +8,307 @@ import { CollectionView } from "../views/collections/CollectionView"; import { DocumentView } from "../views/nodes/DocumentView"; export function setupDrag( - _reference: React.RefObject, - docFunc: () => Document, - removeFunc: (containingCollection: CollectionView) => void = () => {} + _reference: React.RefObject, + docFunc: () => Document, + removeFunc: (containingCollection: CollectionView) => void = () => { } ) { - let onRowMove = action( - (e: PointerEvent): void => { - e.stopPropagation(); - e.preventDefault(); + let onRowMove = action( + (e: PointerEvent): void => { + e.stopPropagation(); + e.preventDefault(); - document.removeEventListener("pointermove", onRowMove); - document.removeEventListener("pointerup", onRowUp); - var dragData = new DragManager.DocumentDragData([docFunc()]); - dragData.removeDocument = removeFunc; - DragManager.StartDocumentDrag([_reference.current!], dragData); - } - ); - let onRowUp = action( - (e: PointerEvent): void => { - document.removeEventListener("pointermove", onRowMove); - document.removeEventListener("pointerup", onRowUp); - } - ); - let onItemDown = (e: React.PointerEvent) => { - // if (this.props.isSelected() || this.props.isTopMost) { - if (e.button == 0) { - e.stopPropagation(); - if (e.shiftKey) { - CollectionDockingView.Instance.StartOtherDrag([docFunc()], e); - } else { - document.addEventListener("pointermove", onRowMove); - document.addEventListener("pointerup", onRowUp); - } - } - //} - }; - return onItemDown; + document.removeEventListener("pointermove", onRowMove); + document.removeEventListener("pointerup", onRowUp); + var dragData = new DragManager.DocumentDragData([docFunc()]); + dragData.removeDocument = removeFunc; + DragManager.StartDocumentDrag([_reference.current!], dragData); + } + ); + let onRowUp = action( + (e: PointerEvent): void => { + document.removeEventListener("pointermove", onRowMove); + document.removeEventListener("pointerup", onRowUp); + } + ); + let onItemDown = (e: React.PointerEvent) => { + // if (this.props.isSelected() || this.props.isTopMost) { + if (e.button == 0) { + e.stopPropagation(); + if (e.shiftKey) { + CollectionDockingView.Instance.StartOtherDrag([docFunc()], e); + } else { + document.addEventListener("pointermove", onRowMove); + document.addEventListener("pointerup", onRowUp); + } + } + //} + }; + return onItemDown; } export namespace DragManager { - export function Root() { - const root = document.getElementById("root"); - if (!root) { - throw new Error("No root element found"); + export function Root() { + const root = document.getElementById("root"); + if (!root) { + throw new Error("No root element found"); + } + return root; } - return root; - } - let dragDiv: HTMLDivElement; + let dragDiv: HTMLDivElement; - export enum DragButtons { - Left = 1, - Right = 2, - Both = Left | Right - } + export enum DragButtons { + Left = 1, + Right = 2, + Both = Left | Right + } - interface DragOptions { - handlers: DragHandlers; + interface DragOptions { + handlers: DragHandlers; - hideSource: boolean | (() => boolean); - } + hideSource: boolean | (() => boolean); + } - export interface DragDropDisposer { - (): void; - } + export interface DragDropDisposer { + (): void; + } - export class DragCompleteEvent {} + export class DragCompleteEvent { } - export interface DragHandlers { - dragComplete: (e: DragCompleteEvent) => void; - } + export interface DragHandlers { + dragComplete: (e: DragCompleteEvent) => void; + } - export interface DropOptions { - handlers: DropHandlers; - } - export class DropEvent { - constructor( - readonly x: number, - readonly y: number, - readonly data: { [id: string]: any } - ) {} - } + export interface DropOptions { + handlers: DropHandlers; + } + export class DropEvent { + constructor( + readonly x: number, + readonly y: number, + readonly data: { [id: string]: any } + ) { } + } - export interface DropHandlers { - drop: (e: Event, de: DropEvent) => void; - } + export interface DropHandlers { + drop: (e: Event, de: DropEvent) => void; + } - export function MakeDropTarget( - element: HTMLElement, - options: DropOptions - ): DragDropDisposer { - if ("canDrop" in element.dataset) { - throw new Error( - "Element is already droppable, can't make it droppable again" - ); + export function MakeDropTarget( + element: HTMLElement, + options: DropOptions + ): DragDropDisposer { + if ("canDrop" in element.dataset) { + throw new Error( + "Element is already droppable, can't make it droppable again" + ); + } + element.dataset["canDrop"] = "true"; + const handler = (e: Event) => { + const ce = e as CustomEvent; + options.handlers.drop(e, ce.detail); + }; + element.addEventListener("dashOnDrop", handler); + return () => { + element.removeEventListener("dashOnDrop", handler); + delete element.dataset["canDrop"]; + }; } - element.dataset["canDrop"] = "true"; - const handler = (e: Event) => { - const ce = e as CustomEvent; - options.handlers.drop(e, ce.detail); - }; - element.addEventListener("dashOnDrop", handler); - return () => { - element.removeEventListener("dashOnDrop", handler); - delete element.dataset["canDrop"]; - }; - } - export class DocumentDragData { - constructor(dragDoc: Document[]) { - this.draggedDocuments = dragDoc; - this.droppedDocuments = dragDoc; + export class DocumentDragData { + constructor(dragDoc: Document[]) { + this.draggedDocuments = dragDoc; + this.droppedDocuments = dragDoc; + } + draggedDocuments: Document[]; + droppedDocuments: Document[]; + xOffset?: number; + yOffset?: number; + aliasOnDrop?: boolean; + removeDocument?: (collectionDrop: CollectionView) => void; + [id: string]: any; } - draggedDocuments: Document[]; - droppedDocuments: Document[]; - xOffset?: number; - yOffset?: number; - aliasOnDrop?: boolean; - removeDocument?: (collectionDrop: CollectionView) => void; - [id: string]: any; - } - export function StartDocumentDrag( - eles: HTMLElement[], - dragData: DocumentDragData, - options?: DragOptions - ) { - StartDrag( - eles, - dragData, - options, - (dropData: { [id: string]: any }) => - (dropData.droppedDocuments = dragData.aliasOnDrop - ? dragData.draggedDocuments.map(d => d.CreateAlias()) - : dragData.draggedDocuments) - ); - } + export function StartDocumentDrag( + eles: HTMLElement[], + dragData: DocumentDragData, + options?: DragOptions + ) { + StartDrag( + eles, + dragData, + options, + (dropData: { [id: string]: any }) => + (dropData.droppedDocuments = dragData.aliasOnDrop + ? dragData.draggedDocuments.map(d => d.CreateAlias()) + : dragData.draggedDocuments) + ); + } - export class LinkDragData { - constructor(linkSourceDoc: DocumentView) { - this.linkSourceDocumentView = linkSourceDoc; + export class LinkDragData { + constructor(linkSourceDoc: DocumentView) { + this.linkSourceDocumentView = linkSourceDoc; + } + droppedDocuments: Document[] = []; + linkSourceDocumentView: DocumentView; + [id: string]: any; } - linkSourceDocumentView: DocumentView; - [id: string]: any; - } - export function StartLinkDrag( - ele: HTMLElement, - dragData: LinkDragData, - options?: DragOptions - ) { - StartDrag([ele], dragData, options); - } - function StartDrag( - eles: HTMLElement[], - dragData: { [id: string]: any }, - options?: DragOptions, - finishDrag?: (dropData: { [id: string]: any }) => void - ) { - if (!dragDiv) { - dragDiv = document.createElement("div"); - dragDiv.className = "dragManager-dragDiv"; - DragManager.Root().appendChild(dragDiv); + export function StartLinkDrag( + ele: HTMLElement, + dragData: LinkDragData, + options?: DragOptions + ) { + StartDrag([ele], dragData, options); } + function StartDrag( + eles: HTMLElement[], + dragData: { [id: string]: any }, + options?: DragOptions, + finishDrag?: (dropData: { [id: string]: any }) => void + ) { + if (!dragDiv) { + dragDiv = document.createElement("div"); + dragDiv.className = "dragManager-dragDiv"; + DragManager.Root().appendChild(dragDiv); + } - let scaleXs: number[] = []; - let scaleYs: number[] = []; - let xs: number[] = []; - let ys: number[] = []; + let scaleXs: number[] = []; + let scaleYs: number[] = []; + let xs: number[] = []; + let ys: number[] = []; - const docs: Document[] = - dragData instanceof DocumentDragData ? dragData.draggedDocuments : []; - let dragElements = eles.map(ele => { - const w = ele.offsetWidth, - h = ele.offsetHeight; - const rect = ele.getBoundingClientRect(); - const scaleX = rect.width / w, - scaleY = rect.height / h; - let x = rect.left, - y = rect.top; - xs.push(x); - ys.push(y); - scaleXs.push(scaleX); - scaleYs.push(scaleY); - let dragElement = ele.cloneNode(true) as HTMLElement; - dragElement.style.opacity = "0.7"; - dragElement.style.position = "absolute"; - dragElement.style.bottom = ""; - dragElement.style.left = ""; - dragElement.style.transformOrigin = "0 0"; - dragElement.style.zIndex = "1000"; - dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`; - dragElement.style.width = `${rect.width / scaleX}px`; - dragElement.style.height = `${rect.height / scaleY}px`; + const docs: Document[] = + dragData instanceof DocumentDragData ? dragData.draggedDocuments : []; + let dragElements = eles.map(ele => { + const w = ele.offsetWidth, + h = ele.offsetHeight; + const rect = ele.getBoundingClientRect(); + const scaleX = rect.width / w, + scaleY = rect.height / h; + let x = rect.left, + y = rect.top; + xs.push(x); + ys.push(y); + scaleXs.push(scaleX); + scaleYs.push(scaleY); + let dragElement = ele.cloneNode(true) as HTMLElement; + dragElement.style.opacity = "0.7"; + dragElement.style.position = "absolute"; + dragElement.style.margin = "0"; + dragElement.style.top = "0"; + dragElement.style.bottom = ""; + dragElement.style.left = "0"; + dragElement.style.transformOrigin = "0 0"; + dragElement.style.zIndex = "1000"; + dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`; + dragElement.style.width = `${rect.width / scaleX}px`; + dragElement.style.height = `${rect.height / scaleY}px`; - // bcz: PDFs don't show up if you clone them because they contain a canvas. - // however, PDF's have a thumbnail field that contains an image of their canvas. - // So we replace the pdf's canvas with the image thumbnail - if (docs.length) { - var pdfBox = dragElement.getElementsByClassName( - "pdfBox-cont" - )[0] as HTMLElement; - let thumbnail = docs[0].GetT(KeyStore.Thumbnail, ImageField); - if (pdfBox && pdfBox.childElementCount && thumbnail) { - let img = new Image(); - img!.src = thumbnail.toString(); - img!.style.position = "absolute"; - img!.style.width = `${rect.width / scaleX}px`; - img!.style.height = `${rect.height / scaleY}px`; - pdfBox.replaceChild(img!, pdfBox.children[0]); + // bcz: PDFs don't show up if you clone them because they contain a canvas. + // however, PDF's have a thumbnail field that contains an image of their canvas. + // So we replace the pdf's canvas with the image thumbnail + if (docs.length) { + var pdfBox = dragElement.getElementsByClassName( + "pdfBox-cont" + )[0] as HTMLElement; + let thumbnail = docs[0].GetT(KeyStore.Thumbnail, ImageField); + if (pdfBox && pdfBox.childElementCount && thumbnail) { + let img = new Image(); + img!.src = thumbnail.toString(); + img!.style.position = "absolute"; + img!.style.width = `${rect.width / scaleX}px`; + img!.style.height = `${rect.height / scaleY}px`; + pdfBox.replaceChild(img!, pdfBox.children[0]); + } + } + + dragDiv.appendChild(dragElement); + return dragElement; + }); + + let hideSource = false; + if (options) { + if (typeof options.hideSource === "boolean") { + hideSource = options.hideSource; + } else { + hideSource = options.hideSource(); + } } - } + eles.map(ele => (ele.hidden = hideSource)); - dragDiv.appendChild(dragElement); - return dragElement; - }); + const moveHandler = (e: PointerEvent) => { + e.stopPropagation(); + e.preventDefault(); + if (dragData instanceof DocumentDragData) + dragData.aliasOnDrop = e.ctrlKey || e.altKey; + if (e.shiftKey) { + abortDrag(); + CollectionDockingView.Instance.StartOtherDrag(docs, { + pageX: e.pageX, + pageY: e.pageY, + preventDefault: () => { }, + button: 0 + }); + } + dragElements.map( + (dragElement, i) => + (dragElement.style.transform = `translate(${(xs[i] += + e.movementX)}px, ${(ys[i] += e.movementY)}px) scale(${ + scaleXs[i] + }, ${scaleYs[i]})`) + ); + }; - let hideSource = false; - if (options) { - if (typeof options.hideSource === "boolean") { - hideSource = options.hideSource; - } else { - hideSource = options.hideSource(); - } + const abortDrag = () => { + document.removeEventListener("pointermove", moveHandler, true); + document.removeEventListener("pointerup", upHandler); + dragElements.map(dragElement => dragDiv.removeChild(dragElement)); + eles.map(ele => (ele.hidden = false)); + }; + const upHandler = (e: PointerEvent) => { + abortDrag(); + FinishDrag(eles, e, dragData, options, finishDrag); + }; + document.addEventListener("pointermove", moveHandler, true); + document.addEventListener("pointerup", upHandler); } - eles.map(ele => (ele.hidden = hideSource)); - const moveHandler = (e: PointerEvent) => { - e.stopPropagation(); - e.preventDefault(); - if (dragData instanceof DocumentDragData) - dragData.aliasOnDrop = e.ctrlKey || e.altKey; - if (e.shiftKey) { - abortDrag(); - CollectionDockingView.Instance.StartOtherDrag(docs, { - pageX: e.pageX, - pageY: e.pageY, - preventDefault: () => {}, - button: 0 + function FinishDrag( + dragEles: HTMLElement[], + e: PointerEvent, + dragData: { [index: string]: any }, + options?: DragOptions, + finishDrag?: (dragData: { [index: string]: any }) => void + ) { + let removed = dragEles.map(dragEle => { + let parent = dragEle.parentElement; + if (parent) parent.removeChild(dragEle); + return [dragEle, parent]; }); - } - dragElements.map( - (dragElement, i) => - (dragElement.style.transform = `translate(${(xs[i] += - e.movementX)}px, ${(ys[i] += e.movementY)}px) scale(${ - scaleXs[i] - }, ${scaleYs[i]})`) - ); - }; - - const abortDrag = () => { - document.removeEventListener("pointermove", moveHandler, true); - document.removeEventListener("pointerup", upHandler); - dragElements.map(dragElement => dragDiv.removeChild(dragElement)); - eles.map(ele => (ele.hidden = false)); - }; - const upHandler = (e: PointerEvent) => { - abortDrag(); - FinishDrag(eles, e, dragData, options, finishDrag); - }; - document.addEventListener("pointermove", moveHandler, true); - document.addEventListener("pointerup", upHandler); - } - - function FinishDrag( - dragEles: HTMLElement[], - e: PointerEvent, - dragData: { [index: string]: any }, - options?: DragOptions, - finishDrag?: (dragData: { [index: string]: any }) => void - ) { - let removed = dragEles.map(dragEle => { - let parent = dragEle.parentElement; - if (parent) parent.removeChild(dragEle); - return [dragEle, parent]; - }); - const target = document.elementFromPoint(e.x, e.y); - removed.map(r => { - let dragEle: HTMLElement = r[0]!; - let parent: HTMLElement | null = r[1]; - if (parent) parent.appendChild(dragEle); - }); - if (target) { - if (finishDrag) finishDrag(dragData); + const target = document.elementFromPoint(e.x, e.y); + removed.map(r => { + let dragEle: HTMLElement = r[0]!; + let parent: HTMLElement | null = r[1]; + if (parent) parent.appendChild(dragEle); + }); + if (target) { + if (finishDrag) finishDrag(dragData); - target.dispatchEvent( - new CustomEvent("dashOnDrop", { - bubbles: true, - detail: { - x: e.x, - y: e.y, - data: dragData - } - }) - ); + target.dispatchEvent( + new CustomEvent("dashOnDrop", { + bubbles: true, + detail: { + x: e.x, + y: e.y, + data: dragData + } + }) + ); - if (options) { - options.handlers.dragComplete({}); - } + if (options) { + options.handlers.dragComplete({}); + } + } + DocumentDecorations.Instance.Hidden = false; } - DocumentDecorations.Instance.Hidden = false; - } } diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 438659108..958c14491 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -3,48 +3,46 @@ import { DocumentView } from "../views/nodes/DocumentView"; import { Document } from "../../fields/Document"; export namespace SelectionManager { - class Manager { - @observable - SelectedDocuments: Array = []; - - @action - SelectDoc(doc: DocumentView, ctrlPressed: boolean): void { - // if doc is not in SelectedDocuments, add it - if (!ctrlPressed) { - manager.SelectedDocuments = []; - } - - if (manager.SelectedDocuments.indexOf(doc) === -1) { - manager.SelectedDocuments.push(doc); - } + class Manager { + @observable + SelectedDocuments: Array = []; + + @action + SelectDoc(doc: DocumentView, ctrlPressed: boolean): void { + // if doc is not in SelectedDocuments, add it + if (!ctrlPressed) { + manager.SelectedDocuments = []; + } + + if (manager.SelectedDocuments.indexOf(doc) === -1) { + manager.SelectedDocuments.push(doc); + } + } } - } - const manager = new Manager(); + const manager = new Manager(); - export function SelectDoc(doc: DocumentView, ctrlPressed: boolean): void { - if (!doc.isMinimized()) { - manager.SelectDoc(doc, ctrlPressed); + export function SelectDoc(doc: DocumentView, ctrlPressed: boolean): void { + manager.SelectDoc(doc, ctrlPressed); } - } - - export function IsSelected(doc: DocumentView): boolean { - return manager.SelectedDocuments.indexOf(doc) !== -1; - } - - export function DeselectAll(except?: Document): void { - let found: DocumentView | undefined = undefined; - if (except) { - for (let i = 0; i < manager.SelectedDocuments.length; i++) { - let view = manager.SelectedDocuments[i]; - if (view.props.Document == except) found = view; - } + + export function IsSelected(doc: DocumentView): boolean { + return manager.SelectedDocuments.indexOf(doc) !== -1; + } + + export function DeselectAll(except?: Document): void { + let found: DocumentView | undefined = undefined; + if (except) { + for (let i = 0; i < manager.SelectedDocuments.length; i++) { + let view = manager.SelectedDocuments[i]; + if (view.props.Document == except) found = view; + } + } + manager.SelectedDocuments.length = 0; + if (found) manager.SelectedDocuments.push(found); } - manager.SelectedDocuments.length = 0; - if (found) manager.SelectedDocuments.push(found); - } - export function SelectedDocuments(): Array { - return manager.SelectedDocuments; - } + export function SelectedDocuments(): Array { + return manager.SelectedDocuments; + } } diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index befe175b5..080c9fa1e 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -2,13 +2,16 @@ #documentDecorations-container { position: absolute; + top: 0; + left:0; display: grid; z-index: 1000; grid-template-rows: 20px 8px 1fr 8px; - grid-template-columns: 8px 1fr 8px; + grid-template-columns: 8px 8px 1fr 8px; pointer-events: none; #documentDecorations-centerCont { + grid-column:3; background: none; } @@ -18,6 +21,24 @@ opacity: 0.8; } + #documentDecorations-topLeftResizer, + #documentDecorations-leftResizer, + #documentDecorations-bottomLeftResizer { + grid-column: 1 + } + + #documentDecorations-topResizer, + #documentDecorations-bottomResizer { + grid-column-start: 2; + grid-column-end: 4; + } + + #documentDecorations-bottomRightResizer, + #documentDecorations-topRightResizer, + #documentDecorations-rightResizer { + grid-column:4 + } + #documentDecorations-topLeftResizer, #documentDecorations-bottomRightResizer { cursor: nwse-resize; @@ -39,15 +60,19 @@ } .title{ background: lightblue; - grid-column-start:2; - grid-column-end: 4; + grid-column-start:3; + grid-column-end: 5; pointer-events: auto; } } .documentDecorations-minimizeButton { - background: rgb(250, 57, 9); + background:$alt-accent; + opacity: 0.8; + grid-column-start: 1; + grid-column-end: 3; pointer-events: all; + text-align: center; } .documentDecorations-background { background: lightblue; @@ -87,7 +112,8 @@ // } // } .linkFlyout { - grid-column: 1/4 + grid-column: 1/4; + margin-left: 25px; } .linkButton-empty:hover { @@ -102,6 +128,31 @@ cursor: pointer; } +.linkButton-linker { + position:absolute; + bottom:0px; + left: 0px; + height: 20px; + width: 20px; + margin-top: 10px; + margin-right: 5px; + border-radius: 50%; + opacity: 0.9; + pointer-events: auto; + color: $dark-color; + border: $dark-color 1px solid; + text-transform: uppercase; + letter-spacing: 2px; + font-size: 75%; + transition: transform 0.2s; + text-align: center; + display: flex; + justify-content: center; + align-items: center; +} +.linkButton-tray { + grid-column: 1/4; +} .linkButton-empty { height: 20px; width: 20px; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index b74737ec9..a46087c9a 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -1,10 +1,11 @@ -import { action, computed, observable } from "mobx"; +import { action, computed, observable, trace, runInAction } 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 } from "../util/DragManager"; import { SelectionManager } from "../util/SelectionManager"; @@ -13,6 +14,7 @@ import './DocumentDecorations.scss'; import { DocumentView } from "./nodes/DocumentView"; import { LinkMenu } from "./nodes/LinkMenu"; import React = require("react"); +import { FieldWaiting } from "../../fields/Field"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -28,6 +30,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> private _linkBoxHeight = 30; private _titleHeight = 20; private _linkButton = React.createRef(); + private _linkerButton = React.createRef(); //@observable private _title: string = this._documents[0].props.Document.Title; @observable private _title: string = this._documents.length > 0 ? this._documents[0].props.Document.Title : ""; @observable private _fieldKey: Key = KeyStore.Title; @@ -174,18 +177,40 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } } - onLinkButtonDown = (e: React.PointerEvent): void => { - // if () - // let linkMenu = new LinkMenu(SelectionManager.SelectedDocuments()[0]); - // linkMenu.Hidden = false; - console.log("down"); + onLinkerButtonDown = (e: React.PointerEvent): void => { + e.stopPropagation(); + document.removeEventListener("pointermove", this.onLinkerButtonMoved) + document.addEventListener("pointermove", this.onLinkerButtonMoved); + document.removeEventListener("pointerup", this.onLinkerButtonUp) + document.addEventListener("pointerup", this.onLinkerButtonUp); + } + onLinkerButtonUp = (e: PointerEvent): void => { + document.removeEventListener("pointermove", this.onLinkerButtonMoved) + document.removeEventListener("pointerup", this.onLinkerButtonUp) + e.stopPropagation(); + } + onLinkerButtonMoved = (e: PointerEvent): void => { + if (this._linkerButton.current != null) { + document.removeEventListener("pointermove", this.onLinkerButtonMoved) + document.removeEventListener("pointerup", this.onLinkerButtonUp) + let dragData = new DragManager.LinkDragData(SelectionManager.SelectedDocuments()[0]); + DragManager.StartLinkDrag(this._linkerButton.current, dragData, { + handlers: { + dragComplete: action(() => { }), + }, + hideSource: false + }) + } + e.stopPropagation(); + } + + onLinkButtonDown = (e: React.PointerEvent): void => { e.stopPropagation(); document.removeEventListener("pointermove", this.onLinkButtonMoved) document.addEventListener("pointermove", this.onLinkButtonMoved); document.removeEventListener("pointerup", this.onLinkButtonUp) document.addEventListener("pointerup", this.onLinkButtonUp); - } onLinkButtonUp = (e: PointerEvent): void => { @@ -194,18 +219,32 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> e.stopPropagation(); } - onLinkButtonMoved = (e: PointerEvent): void => { if (this._linkButton.current != null) { document.removeEventListener("pointermove", this.onLinkButtonMoved) - document.removeEventListener("pointerup", this.onLinkButtonUp) - let dragData = new DragManager.LinkDragData(SelectionManager.SelectedDocuments()[0]); - DragManager.StartLinkDrag(this._linkButton.current, dragData, { - handlers: { - dragComplete: action(() => { }), - }, - hideSource: false - }) + document.removeEventListener("pointerup", this.onLinkButtonUp); + + let sourceDoc = SelectionManager.SelectedDocuments()[0].props.Document; + let srcTarg = sourceDoc.GetT(KeyStore.Prototype, Document) + let draggedDocs = (srcTarg && srcTarg != FieldWaiting) ? + srcTarg.GetList(KeyStore.LinkedToDocs, [] as Document[]).map(linkDoc => + (linkDoc.GetT(KeyStore.LinkedToDocs, Document)) as Document) : []; + let draggedFromDocs = (srcTarg && srcTarg != FieldWaiting) ? + srcTarg.GetList(KeyStore.LinkedFromDocs, [] as Document[]).map(linkDoc => + (linkDoc.GetT(KeyStore.LinkedFromDocs, Document)) as Document) : []; + draggedDocs.push(...draggedFromDocs); + if (draggedDocs.length) { + let dragData = new DragManager.DocumentDragData(draggedDocs.map(d => { + let annot = d.GetT(KeyStore.AnnotationOn, Document); // bcz: ... needs to change ... the annotationOn document may not have been retrieved yet... + return annot && annot != FieldWaiting ? annot : d; + })); + DragManager.StartDocumentDrag([this._linkButton.current], dragData, { + handlers: { + dragComplete: action(() => { }), + }, + hideSource: false + }) + } } e.stopPropagation(); } @@ -298,7 +337,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } getValue = (): string => { - console.log("value watched"); if (this._title === "changed" && this._documents.length > 0) { let field = this._documents[0].props.Document.Get(this._fieldKey); if (field instanceof TextField) { @@ -338,8 +376,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> linkButton = ( - + }>
{linkCount}
); @@ -361,7 +398,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight, opacity: this._opacity }}> -
v
+
...
e.preventDefault()}>
e.preventDefault()}>
@@ -373,8 +410,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
e.preventDefault()}>
e.preventDefault()}>
-
{linkButton}
- +
{linkButton}
+
) diff --git a/src/client/views/InkingCanvas.scss b/src/client/views/InkingCanvas.scss index 35c8ee942..c5a2a50cb 100644 --- a/src/client/views/InkingCanvas.scss +++ b/src/client/views/InkingCanvas.scss @@ -2,6 +2,8 @@ .inkingCanvas-paths-ink, .inkingCanvas-paths-markers, .inkingCanvas-noSelect, .inkingCanvas-canSelect { position: absolute; + top: 0; + left:0; width: 8192px; height: 8192px; cursor:"crosshair"; diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss index 698a9c617..594803aab 100644 --- a/src/client/views/Main.scss +++ b/src/client/views/Main.scss @@ -7,6 +7,9 @@ body { overflow: hidden; font-family: $sans-serif; margin: 0; + position:absolute; + top: 0; + left:0; } #dash-title { @@ -158,11 +161,15 @@ button:hover { width:100%; height:100%; position:absolute; + top: 0; + left:0; } #mainContent-div { width:100%; height:100%; position:absolute; + top: 0; + left:0; } #add-options-content { display: table; diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss index 2706c3272..583d50c5b 100644 --- a/src/client/views/collections/CollectionDockingView.scss +++ b/src/client/views/collections/CollectionDockingView.scss @@ -3,7 +3,7 @@ } .collectiondockingview-container { - position: relative; + position: absolute; top: 0; left: 0; overflow: hidden; diff --git a/src/client/views/collections/CollectionPDFView.scss b/src/client/views/collections/CollectionPDFView.scss index 0144625c1..0eca3f1cd 100644 --- a/src/client/views/collections/CollectionPDFView.scss +++ b/src/client/views/collections/CollectionPDFView.scss @@ -9,6 +9,8 @@ width: 100%; height: 100%; position: absolute; + top: 0; + left:0; } .collectionPdfView-backward { diff --git a/src/client/views/collections/CollectionVideoView.scss b/src/client/views/collections/CollectionVideoView.scss index cbb981b13..ed56ad268 100644 --- a/src/client/views/collections/CollectionVideoView.scss +++ b/src/client/views/collections/CollectionVideoView.scss @@ -3,6 +3,8 @@ width: 100%; height: 100%; position: absolute; + top: 0; + left:0; } .collectionVideoView-time{ diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx index 2b0689800..daeca69e2 100644 --- a/src/client/views/collections/CollectionViewBase.tsx +++ b/src/client/views/collections/CollectionViewBase.tsx @@ -17,6 +17,8 @@ import { NumberField } from "../../../fields/NumberField"; import request = require("request"); import { ServerUtils } from "../../../server/ServerUtil"; import { Server } from "../../Server"; +import { CollectionDockingView } from "./CollectionDockingView"; +import { runReactions } from "mobx/lib/internal"; export interface CollectionViewProps { fieldKey: Key; @@ -92,6 +94,24 @@ export class CollectionViewBase extends React.Component e.stopPropagation(); return added; } + if (de.data instanceof DragManager.LinkDragData) { + let sourceDoc: Document = de.data.linkSourceDocumentView.props.Document; + if (sourceDoc) runInAction(() => { + let srcTarg = sourceDoc.GetT(KeyStore.Prototype, Document) + if (srcTarg && srcTarg != FieldWaiting) { + let linkDocs = srcTarg.GetList(KeyStore.LinkedToDocs, [] as Document[]); + linkDocs.map(linkDoc => { + let targDoc = linkDoc.GetT(KeyStore.LinkedToDocs, Document); + if (targDoc && targDoc != FieldWaiting) { + let dropdoc = targDoc.MakeDelegate(); + de.data.droppedDocuments.push(dropdoc); + this.props.addDocument(dropdoc, false); + } + }) + } + }) + return true; + } return false; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index e84f0c5ad..3dfd74ec8 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -23,10 +23,10 @@ export class CollectionFreeFormLinkView extends React.Component { if (super.drop(e, de)) { - if (de.data instanceof DragManager.DocumentDragData) { - let screenX = de.x - (de.data.xOffset as number || 0); - let screenY = de.y - (de.data.yOffset as number || 0); + let droppedDocs = de.data.droppedDocuments as Document[]; + let xoff = de.data.xOffset as number || 0; + let yoff = de.data.yOffset as number || 0; + if (droppedDocs && droppedDocs.length) { + let screenX = de.x - xoff; + let screenY = de.y - yoff; const [x, y] = this.getTransform().transformPoint(screenX, screenY); - let dragDoc = de.data.draggedDocuments[0]; + let dragDoc = de.data.droppedDocuments[0]; let dragX = dragDoc.GetNumber(KeyStore.X, 0); let dragY = dragDoc.GetNumber(KeyStore.Y, 0); - de.data.draggedDocuments.map(d => { + droppedDocs.map(d => { let docX = d.GetNumber(KeyStore.X, 0); let docY = d.GetNumber(KeyStore.Y, 0); d.SetNumber(KeyStore.X, x + (docX - dragX)); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss index 1ee3b244b..0b406e722 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss @@ -1,6 +1,8 @@ .marqueeView { position: absolute; + top:0; + left:0; width:100%; height:100%; } diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.scss b/src/client/views/collections/collectionFreeForm/PreviewCursor.scss index 21210be2b..7a67c29bf 100644 --- a/src/client/views/collections/collectionFreeForm/PreviewCursor.scss +++ b/src/client/views/collections/collectionFreeForm/PreviewCursor.scss @@ -3,9 +3,13 @@ color: black; position: absolute; transform-origin: left top; + top: 0; + left:0; pointer-events: none; } .previewCursorView { + top: 0; + left:0; position: absolute; width:100%; height:100%; diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index d52b662bd..1a0f0cbbd 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -66,8 +66,12 @@ export class CollectionFreeFormDocumentView extends React.Component } + panelWidth = () => this.props.Document.GetBoolean(KeyStore.Minimized, false) ? 10 : this.props.PanelWidth(); + panelHeight = () => this.props.Document.GetBoolean(KeyStore.Minimized, false) ? 10 : this.props.PanelHeight(); render() { return ( diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 127a6b535..5126e69f9 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -2,6 +2,8 @@ .documentView-node { position: absolute; + top: 0; + left:0; background: $light-color; //overflow: hidden; &.minimized { diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss index ad947afd5..830dfe6c6 100644 --- a/src/client/views/nodes/PDFBox.scss +++ b/src/client/views/nodes/PDFBox.scss @@ -1,12 +1,16 @@ .react-pdf__Page { transform-origin: left top; position: absolute; + top: 0; + left:0; } .react-pdf__Document { position: absolute; } .pdfBox-buttonTray { position:absolute; + top: 0; + left:0; z-index: 25; } .pdfBox-contentContainer { diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss index a535b2638..c73bc0c47 100644 --- a/src/client/views/nodes/WebBox.scss +++ b/src/client/views/nodes/WebBox.scss @@ -2,6 +2,8 @@ .webBox-cont { padding: 0vw; position: absolute; + top: 0; + left:0; width: 100%; height: 100%; overflow: scroll; diff --git a/src/fields/Document.ts b/src/fields/Document.ts index 8e71019a4..92d03d765 100644 --- a/src/fields/Document.ts +++ b/src/fields/Document.ts @@ -9,415 +9,420 @@ import { Server } from "../client/Server"; import { Types } from "../server/Message"; import { UndoManager } from "../client/util/UndoManager"; import { HtmlField } from "./HtmlField"; +import { BooleanField } from "./BooleanField"; export class Document extends Field { - //TODO tfs: We should probably store FieldWaiting in fields when we request it from the server so that we don't set up multiple server gets for the same document and field - public fields: ObservableMap< - string, - { key: Key; field: Field } - > = new ObservableMap(); - public _proxies: ObservableMap = new ObservableMap(); - - constructor(id?: string, save: boolean = true) { - super(id); - - if (save) { - Server.UpdateField(this); + //TODO tfs: We should probably store FieldWaiting in fields when we request it from the server so that we don't set up multiple server gets for the same document and field + public fields: ObservableMap< + string, + { key: Key; field: Field } + > = new ObservableMap(); + public _proxies: ObservableMap = new ObservableMap(); + + constructor(id?: string, save: boolean = true) { + super(id); + + if (save) { + Server.UpdateField(this); + } } - } - UpdateFromServer(data: [string, string][]) { - for (const key in data) { - const element = data[key]; - this._proxies.set(element[0], element[1]); - } - } - - public Width = () => { - return this.GetNumber(KeyStore.Width, 0); - }; - public Height = () => { - return this.GetNumber( - KeyStore.Height, - this.GetNumber(KeyStore.NativeWidth, 0) - ? (this.GetNumber(KeyStore.NativeHeight, 0) / - this.GetNumber(KeyStore.NativeWidth, 0)) * - this.GetNumber(KeyStore.Width, 0) - : 0 - ); - }; - public Scale = () => { - return this.GetNumber(KeyStore.Scale, 1); - }; - - @computed - public get Title(): string { - let title = this.Get(KeyStore.Title, true); - if (title) - if (title != FieldWaiting && title instanceof TextField) - return title.Data; - else return "-waiting-"; - let parTitle = this.GetT(KeyStore.Title, TextField); - if (parTitle) - if (parTitle != FieldWaiting) return parTitle.Data + ".alias"; - else return "-waiting-.alias"; - return "-untitled-"; - } - - @computed - public get Fields() { - return this.fields; - } - - /** - * Get the field in the document associated with the given key. If the - * associated field has not yet been filled in from the server, a request - * to the server will automatically be sent, the value will be filled in - * when the request is completed, and {@link Field.ts#FieldWaiting} will be returned. - * @param key - The key of the value to get - * @param ignoreProto - If true, ignore any prototype this document - * might have and only search for the value on this immediate document. - * If false (default), search up the prototype chain, starting at this document, - * for a document that has a field associated with the given key, and return the first - * one found. - * - * @returns If the document does not have a field associated with the given key, returns `undefined`. - * If the document does have an associated field, but the field has not been fetched from the server, returns {@link Field.ts#FieldWaiting}. - * If the document does have an associated field, and the field has not been fetched from the server, returns the associated field. - */ - Get(key: Key, ignoreProto: boolean = false): FieldValue { - let field: FieldValue; - if (ignoreProto) { - if (this.fields.has(key.Id)) { - field = this.fields.get(key.Id)!.field; - } else if (this._proxies.has(key.Id)) { - Server.GetDocumentField(this, key); - /* - The field might have been instantly filled from the cache - Maybe we want to just switch back to returning the value - from Server.GetDocumentField if it's in the cache - */ - if (this.fields.has(key.Id)) { - field = this.fields.get(key.Id)!.field; - } else { - field = FieldWaiting; + UpdateFromServer(data: [string, string][]) { + for (const key in data) { + const element = data[key]; + this._proxies.set(element[0], element[1]); } - } - } else { - let doc: FieldValue = this; - while (doc && doc != FieldWaiting && field != FieldWaiting) { - let curField = doc.fields.get(key.Id); - let curProxy = doc._proxies.get(key.Id); - if (!curField || (curProxy && curField.field.Id !== curProxy)) { - if (curProxy) { - Server.GetDocumentField(doc, key); - /* + } + + public Width = () => { + return this.GetNumber(KeyStore.Width, 0); + }; + public Height = () => { + return this.GetNumber( + KeyStore.Height, + this.GetNumber(KeyStore.NativeWidth, 0) + ? (this.GetNumber(KeyStore.NativeHeight, 0) / + this.GetNumber(KeyStore.NativeWidth, 0)) * + this.GetNumber(KeyStore.Width, 0) + : 0 + ); + }; + public Scale = () => { + return this.GetNumber(KeyStore.Scale, 1); + }; + + @computed + public get Title(): string { + let title = this.Get(KeyStore.Title, true); + if (title) + if (title != FieldWaiting && title instanceof TextField) + return title.Data; + else return "-waiting-"; + let parTitle = this.GetT(KeyStore.Title, TextField); + if (parTitle) + if (parTitle != FieldWaiting) return parTitle.Data + ".alias"; + else return "-waiting-.alias"; + return "-untitled-"; + } + + @computed + public get Fields() { + return this.fields; + } + + /** + * Get the field in the document associated with the given key. If the + * associated field has not yet been filled in from the server, a request + * to the server will automatically be sent, the value will be filled in + * when the request is completed, and {@link Field.ts#FieldWaiting} will be returned. + * @param key - The key of the value to get + * @param ignoreProto - If true, ignore any prototype this document + * might have and only search for the value on this immediate document. + * If false (default), search up the prototype chain, starting at this document, + * for a document that has a field associated with the given key, and return the first + * one found. + * + * @returns If the document does not have a field associated with the given key, returns `undefined`. + * If the document does have an associated field, but the field has not been fetched from the server, returns {@link Field.ts#FieldWaiting}. + * If the document does have an associated field, and the field has not been fetched from the server, returns the associated field. + */ + Get(key: Key, ignoreProto: boolean = false): FieldValue { + let field: FieldValue; + if (ignoreProto) { + if (this.fields.has(key.Id)) { + field = this.fields.get(key.Id)!.field; + } else if (this._proxies.has(key.Id)) { + Server.GetDocumentField(this, key); + /* The field might have been instantly filled from the cache Maybe we want to just switch back to returning the value from Server.GetDocumentField if it's in the cache */ - if (this.fields.has(key.Id)) { - field = this.fields.get(key.Id)!.field; - } else { - field = FieldWaiting; + if (this.fields.has(key.Id)) { + field = this.fields.get(key.Id)!.field; + } else { + field = FieldWaiting; + } } - break; - } - if ( - doc.fields.has(KeyStore.Prototype.Id) || - doc._proxies.has(KeyStore.Prototype.Id) - ) { - doc = doc.GetPrototype(); - } else { - break; - } } else { - field = curField.field; - break; + let doc: FieldValue = this; + while (doc && doc != FieldWaiting && field != FieldWaiting) { + let curField = doc.fields.get(key.Id); + let curProxy = doc._proxies.get(key.Id); + if (!curField || (curProxy && curField.field.Id !== curProxy)) { + if (curProxy) { + Server.GetDocumentField(doc, key); + /* + The field might have been instantly filled from the cache + Maybe we want to just switch back to returning the value + from Server.GetDocumentField if it's in the cache + */ + if (this.fields.has(key.Id)) { + field = this.fields.get(key.Id)!.field; + } else { + field = FieldWaiting; + } + break; + } + if ( + doc.fields.has(KeyStore.Prototype.Id) || + doc._proxies.has(KeyStore.Prototype.Id) + ) { + doc = doc.GetPrototype(); + } else { + break; + } + } else { + field = curField.field; + break; + } + } + if (doc == FieldWaiting) field = FieldWaiting; } - } - if (doc == FieldWaiting) field = FieldWaiting; + + return field; } - return field; - } - - /** - * Tries to get the field associated with the given key, and if there is an - * associated field, calls the given callback with that field. - * @param key - The key of the value to get - * @param callback - A function that will be called with the associated field, if it exists, - * once it is fetched from the server (this may be immediately if the field has already been fetched). - * Note: The callback will not be called if there is no associated field. - * @returns `true` if the field exists on the document and `callback` will be called, and `false` otherwise - */ - GetAsync(key: Key, callback: (field: Opt) => void): void { - //TODO: This currently doesn't deal with prototypes - let field = this.fields.get(key.Id); - if (field && field.field) { - callback(field.field); - } else if (this._proxies.has(key.Id)) { - Server.GetDocumentField(this, key, callback); - } else if (this._proxies.has(KeyStore.Prototype.Id)) { - this.GetTAsync(KeyStore.Prototype, Document, proto => { - if (proto) { - proto.GetAsync(key, callback); + /** + * Tries to get the field associated with the given key, and if there is an + * associated field, calls the given callback with that field. + * @param key - The key of the value to get + * @param callback - A function that will be called with the associated field, if it exists, + * once it is fetched from the server (this may be immediately if the field has already been fetched). + * Note: The callback will not be called if there is no associated field. + * @returns `true` if the field exists on the document and `callback` will be called, and `false` otherwise + */ + GetAsync(key: Key, callback: (field: Opt) => void): void { + //TODO: This currently doesn't deal with prototypes + let field = this.fields.get(key.Id); + if (field && field.field) { + callback(field.field); + } else if (this._proxies.has(key.Id)) { + Server.GetDocumentField(this, key, callback); + } else if (this._proxies.has(KeyStore.Prototype.Id)) { + this.GetTAsync(KeyStore.Prototype, Document, proto => { + if (proto) { + proto.GetAsync(key, callback); + } else { + callback(undefined); + } + }); } else { - callback(undefined); + callback(undefined); } - }); - } else { - callback(undefined); } - } - - GetTAsync(key: Key, ctor: { new (): T }): Promise>; - GetTAsync( - key: Key, - ctor: { new (): T }, - callback: (field: Opt) => void - ): void; - GetTAsync( - key: Key, - ctor: { new (): T }, - callback?: (field: Opt) => void - ): Promise> | void { - let fn = (cb: (field: Opt) => void) => { - return this.GetAsync(key, field => { - cb(Cast(field, ctor)); - }); - }; - if (callback) { - fn(callback); - } else { - return new Promise(res => fn(res)); + + GetTAsync(key: Key, ctor: { new(): T }): Promise>; + GetTAsync( + key: Key, + ctor: { new(): T }, + callback: (field: Opt) => void + ): void; + GetTAsync( + key: Key, + ctor: { new(): T }, + callback?: (field: Opt) => void + ): Promise> | void { + let fn = (cb: (field: Opt) => void) => { + return this.GetAsync(key, field => { + cb(Cast(field, ctor)); + }); + }; + if (callback) { + fn(callback); + } else { + return new Promise(res => fn(res)); + } } - } - - /** - * Same as {@link Document#GetAsync}, except a field of the given type - * will be created if there is no field associated with the given key, - * or the field associated with the given key is not of the given type. - * @param ctor - Constructor of the field type to get. E.g., TextField, ImageField, etc. - */ - GetOrCreateAsync( - key: Key, - ctor: { new (): T }, - callback: (field: T) => void - ): void { - //This currently doesn't deal with prototypes - if (this._proxies.has(key.Id)) { - Server.GetDocumentField(this, key, field => { - if (field && field instanceof ctor) { - callback(field); + + /** + * Same as {@link Document#GetAsync}, except a field of the given type + * will be created if there is no field associated with the given key, + * or the field associated with the given key is not of the given type. + * @param ctor - Constructor of the field type to get. E.g., TextField, ImageField, etc. + */ + GetOrCreateAsync( + key: Key, + ctor: { new(): T }, + callback: (field: T) => void + ): void { + //This currently doesn't deal with prototypes + if (this._proxies.has(key.Id)) { + Server.GetDocumentField(this, key, field => { + if (field && field instanceof ctor) { + callback(field); + } else { + let newField = new ctor(); + this.Set(key, newField); + callback(newField); + } + }); } else { - let newField = new ctor(); - this.Set(key, newField); - callback(newField); + let newField = new ctor(); + this.Set(key, newField); + callback(newField); } - }); - } else { - let newField = new ctor(); - this.Set(key, newField); - callback(newField); } - } - - /** - * Same as {@link Document#Get}, except that it will additionally - * check if the field is of the given type. - * @param ctor - Constructor of the field type to get. E.g., `TextField`, `ImageField`, etc. - * @returns Same as {@link Document#Get}, except will return `undefined` - * if there is an associated field but it is of the wrong type. - */ - GetT( - key: Key, - ctor: { new (...args: any[]): T }, - ignoreProto: boolean = false - ): FieldValue { - var getfield = this.Get(key, ignoreProto); - if (getfield != FieldWaiting) { - return Cast(getfield, ctor); + + /** + * Same as {@link Document#Get}, except that it will additionally + * check if the field is of the given type. + * @param ctor - Constructor of the field type to get. E.g., `TextField`, `ImageField`, etc. + * @returns Same as {@link Document#Get}, except will return `undefined` + * if there is an associated field but it is of the wrong type. + */ + GetT( + key: Key, + ctor: { new(...args: any[]): T }, + ignoreProto: boolean = false + ): FieldValue { + var getfield = this.Get(key, ignoreProto); + if (getfield != FieldWaiting) { + return Cast(getfield, ctor); + } + return FieldWaiting; } - return FieldWaiting; - } - - GetOrCreate( - key: Key, - ctor: { new (): T }, - ignoreProto: boolean = false - ): T { - const field = this.GetT(key, ctor, ignoreProto); - if (field && field != FieldWaiting) { - return field; + + GetOrCreate( + key: Key, + ctor: { new(): T }, + ignoreProto: boolean = false + ): T { + const field = this.GetT(key, ctor, ignoreProto); + if (field && field != FieldWaiting) { + return field; + } + const newField = new ctor(); + this.Set(key, newField); + return newField; } - const newField = new ctor(); - this.Set(key, newField); - return newField; - } - - GetData( - key: Key, - ctor: { new (): U }, - defaultVal: T - ): T { - let val = this.Get(key); - let vval = val && val instanceof ctor ? val.Data : defaultVal; - return vval; - } - - GetHtml(key: Key, defaultVal: string): string { - return this.GetData(key, HtmlField, defaultVal); - } - - GetNumber(key: Key, defaultVal: number): number { - return this.GetData(key, NumberField, defaultVal); - } - - GetText(key: Key, defaultVal: string): string { - return this.GetData(key, TextField, defaultVal); - } - - GetList(key: Key, defaultVal: T[]): T[] { - return this.GetData>(key, ListField, defaultVal); - } - - @action - Set(key: Key, field: Field | undefined, setOnPrototype = false): void { - let old = this.fields.get(key.Id); - let oldField = old ? old.field : undefined; - if (setOnPrototype) { - this.SetOnPrototype(key, field); - } else { - if (field) { - this.fields.set(key.Id, { key, field }); - this._proxies.set(key.Id, field.Id); - // Server.AddDocumentField(this, key, field); - } else { - this.fields.delete(key.Id); - this._proxies.delete(key.Id); - // Server.DeleteDocumentField(this, key); - } - Server.UpdateField(this); + + GetData( + key: Key, + ctor: { new(): U }, + defaultVal: T + ): T { + let val = this.Get(key); + let vval = val && val instanceof ctor ? val.Data : defaultVal; + return vval; } - if (oldField || field) { - UndoManager.AddEvent({ - undo: () => this.Set(key, oldField, setOnPrototype), - redo: () => this.Set(key, field, setOnPrototype) - }); + + GetHtml(key: Key, defaultVal: string): string { + return this.GetData(key, HtmlField, defaultVal); } - } - - @action - SetOnPrototype(key: Key, field: Field | undefined): void { - this.GetTAsync(KeyStore.Prototype, Document, (f: Opt) => { - f && f.Set(key, field); - }); - } - - @action - SetDataOnPrototype( - key: Key, - value: T, - ctor: { new (): U }, - replaceWrongType = true - ) { - this.GetTAsync(KeyStore.Prototype, Document, (f: Opt) => { - f && f.SetData(key, value, ctor); - }); - } - - @action - SetData( - key: Key, - value: T, - ctor: { new (data: T): U }, - replaceWrongType = true - ) { - let field = this.Get(key, true); - if (field instanceof ctor) { - field.Data = value; - } else if (!field || replaceWrongType) { - let newField = new ctor(value); - // newField.Data = value; - this.Set(key, newField); + + GetBoolean(key: Key, defaultVal: boolean): boolean { + return this.GetData(key, BooleanField, defaultVal); } - } - - @action - SetText(key: Key, value: string, replaceWrongType = true) { - this.SetData(key, value, TextField, replaceWrongType); - } - - @action - SetNumber(key: Key, value: number, replaceWrongType = true) { - this.SetData(key, value, NumberField, replaceWrongType); - } - - GetPrototype(): FieldValue { - return this.GetT(KeyStore.Prototype, Document, true); - } - - GetAllPrototypes(): Document[] { - let protos: Document[] = []; - let doc: FieldValue = this; - while (doc && doc != FieldWaiting) { - protos.push(doc); - doc = doc.GetPrototype(); + + GetNumber(key: Key, defaultVal: number): number { + return this.GetData(key, NumberField, defaultVal); + } + + GetText(key: Key, defaultVal: string): string { + return this.GetData(key, TextField, defaultVal); + } + + GetList(key: Key, defaultVal: T[]): T[] { + return this.GetData>(key, ListField, defaultVal); + } + + @action + Set(key: Key, field: Field | undefined, setOnPrototype = false): void { + let old = this.fields.get(key.Id); + let oldField = old ? old.field : undefined; + if (setOnPrototype) { + this.SetOnPrototype(key, field); + } else { + if (field) { + this.fields.set(key.Id, { key, field }); + this._proxies.set(key.Id, field.Id); + // Server.AddDocumentField(this, key, field); + } else { + this.fields.delete(key.Id); + this._proxies.delete(key.Id); + // Server.DeleteDocumentField(this, key); + } + Server.UpdateField(this); + } + if (oldField || field) { + UndoManager.AddEvent({ + undo: () => this.Set(key, oldField, setOnPrototype), + redo: () => this.Set(key, field, setOnPrototype) + }); + } + } + + @action + SetOnPrototype(key: Key, field: Field | undefined): void { + this.GetTAsync(KeyStore.Prototype, Document, (f: Opt) => { + f && f.Set(key, field); + }); + } + + @action + SetDataOnPrototype( + key: Key, + value: T, + ctor: { new(): U }, + replaceWrongType = true + ) { + this.GetTAsync(KeyStore.Prototype, Document, (f: Opt) => { + f && f.SetData(key, value, ctor); + }); + } + + @action + SetData( + key: Key, + value: T, + ctor: { new(data: T): U }, + replaceWrongType = true + ) { + let field = this.Get(key, true); + if (field instanceof ctor) { + field.Data = value; + } else if (!field || replaceWrongType) { + let newField = new ctor(value); + // newField.Data = value; + this.Set(key, newField); + } + } + + @action + SetText(key: Key, value: string, replaceWrongType = true) { + this.SetData(key, value, TextField, replaceWrongType); + } + + @action + SetNumber(key: Key, value: number, replaceWrongType = true) { + this.SetData(key, value, NumberField, replaceWrongType); + } + + GetPrototype(): FieldValue { + return this.GetT(KeyStore.Prototype, Document, true); + } + + GetAllPrototypes(): Document[] { + let protos: Document[] = []; + let doc: FieldValue = this; + while (doc && doc != FieldWaiting) { + protos.push(doc); + doc = doc.GetPrototype(); + } + return protos; + } + + CreateAlias(id?: string): Document { + let alias = new Document(id); + this.GetTAsync(KeyStore.Prototype, Document, (f: Opt) => { + f && alias.Set(KeyStore.Prototype, f); + }); + + return alias; + } + + MakeDelegate(id?: string): Document { + let delegate = new Document(id); + + delegate.Set(KeyStore.Prototype, this); + + return delegate; + } + + ToScriptString(): string { + return ""; + } + + TrySetValue(value: any): boolean { + throw new Error("Method not implemented."); + } + GetValue() { + return this.Title; + var title = + (this._proxies.has(KeyStore.Title.Id) ? "???" : this.Title) + + "(" + + this.Id + + ")"; + return title; + //throw new Error("Method not implemented."); + } + Copy(): Field { + throw new Error("Method not implemented."); + } + + ToJson(): { type: Types; data: [string, string][]; _id: string } { + let fields: [string, string][] = []; + this._proxies.forEach((field, key) => { + if (field) { + fields.push([key, field as string]); + } + }); + + return { + type: Types.Document, + data: fields, + _id: this.Id + }; } - return protos; - } - - CreateAlias(id?: string): Document { - let alias = new Document(id); - this.GetTAsync(KeyStore.Prototype, Document, (f: Opt) => { - f && alias.Set(KeyStore.Prototype, f); - }); - - return alias; - } - - MakeDelegate(id?: string): Document { - let delegate = new Document(id); - - delegate.Set(KeyStore.Prototype, this); - - return delegate; - } - - ToScriptString(): string { - return ""; - } - - TrySetValue(value: any): boolean { - throw new Error("Method not implemented."); - } - GetValue() { - return this.Title; - var title = - (this._proxies.has(KeyStore.Title.Id) ? "???" : this.Title) + - "(" + - this.Id + - ")"; - return title; - //throw new Error("Method not implemented."); - } - Copy(): Field { - throw new Error("Method not implemented."); - } - - ToJson(): { type: Types; data: [string, string][]; _id: string } { - let fields: [string, string][] = []; - this._proxies.forEach((field, key) => { - if (field) { - fields.push([key, field as string]); - } - }); - - return { - type: Types.Document, - data: fields, - _id: this.Id - }; - } } -- cgit v1.2.3-70-g09d2 From e70be930dc7c8ff1e999bf163bcbb511a60bae6f Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 4 Apr 2019 17:46:33 -0400 Subject: added close button and fixed some async stuff. --- src/client/views/DocumentDecorations.scss | 17 +++++++--- src/client/views/DocumentDecorations.tsx | 38 +++++++++++++++++++--- .../views/collections/CollectionViewBase.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 9 +++-- .../collections/collectionFreeForm/MarqueeView.tsx | 1 + src/client/views/nodes/PDFBox.tsx | 10 +++--- 6 files changed, 60 insertions(+), 17 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 080c9fa1e..d7137d7a2 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -7,7 +7,7 @@ display: grid; z-index: 1000; grid-template-rows: 20px 8px 1fr 8px; - grid-template-columns: 8px 8px 1fr 8px; + grid-template-columns: 8px 8px 1fr 8px 8px; pointer-events: none; #documentDecorations-centerCont { @@ -30,13 +30,14 @@ #documentDecorations-topResizer, #documentDecorations-bottomResizer { grid-column-start: 2; - grid-column-end: 4; + grid-column-end: 5; } #documentDecorations-bottomRightResizer, #documentDecorations-topRightResizer, #documentDecorations-rightResizer { - grid-column:4 + grid-column-start:5; + grid-column-end:7; } #documentDecorations-topLeftResizer, @@ -61,11 +62,19 @@ .title{ background: lightblue; grid-column-start:3; - grid-column-end: 5; + grid-column-end: 4; pointer-events: auto; } } +.documentDecorations-closeButton { + background:$alt-accent; + opacity: 0.8; + grid-column-start: 4; + grid-column-end: 6; + pointer-events: all; + text-align: center; +} .documentDecorations-minimizeButton { background:$alt-accent; opacity: 0.8; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index a46087c9a..8bf1a42d1 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -142,6 +142,30 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> e.preventDefault(); } + onCloseDown = (e: React.PointerEvent): void => { + e.stopPropagation(); + if (e.button === 0) { + document.removeEventListener("pointermove", this.onCloseMove); + document.addEventListener("pointermove", this.onCloseMove); + document.removeEventListener("pointerup", this.onCloseUp); + document.addEventListener("pointerup", this.onCloseUp); + } + } + onCloseMove = (e: PointerEvent): void => { + e.stopPropagation(); + if (e.button === 0) { + } + } + @action + onCloseUp = (e: PointerEvent): void => { + e.stopPropagation(); + if (e.button === 0) { + SelectionManager.SelectedDocuments().map(dv => dv.props.RemoveDocument && dv.props.RemoveDocument(dv.props.Document)); + SelectionManager.DeselectAll(); + document.removeEventListener("pointermove", this.onCloseMove); + document.removeEventListener("pointerup", this.onCloseUp); + } + } onMinimizeDown = (e: React.PointerEvent): void => { e.stopPropagation(); if (e.button === 0) { @@ -219,7 +243,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> e.stopPropagation(); } - onLinkButtonMoved = (e: PointerEvent): void => { + onLinkButtonMoved = async (e: PointerEvent) => { if (this._linkButton.current != null) { document.removeEventListener("pointermove", this.onLinkButtonMoved) document.removeEventListener("pointerup", this.onLinkButtonUp); @@ -234,10 +258,13 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> (linkDoc.GetT(KeyStore.LinkedFromDocs, Document)) as Document) : []; draggedDocs.push(...draggedFromDocs); if (draggedDocs.length) { - let dragData = new DragManager.DocumentDragData(draggedDocs.map(d => { - let annot = d.GetT(KeyStore.AnnotationOn, Document); // bcz: ... needs to change ... the annotationOn document may not have been retrieved yet... - return annot && annot != FieldWaiting ? annot : d; - })); + let moddrag = [] as Document[]; + for (let i = 0; i < draggedDocs.length; i++) { + let doc = await draggedDocs[i].GetTAsync(KeyStore.AnnotationOn, Document); + if (doc) + moddrag.push(doc); + } + let dragData = new DragManager.DocumentDragData(moddrag); DragManager.StartDocumentDrag([this._linkButton.current], dragData, { handlers: { dragComplete: action(() => { }), @@ -400,6 +427,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> }}>
...
+
X
e.preventDefault()}>
e.preventDefault()}>
e.preventDefault()}>
diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx index daeca69e2..9eee23a1d 100644 --- a/src/client/views/collections/CollectionViewBase.tsx +++ b/src/client/views/collections/CollectionViewBase.tsx @@ -128,6 +128,7 @@ export class CollectionViewBase extends React.Component } if (type.indexOf("pdf") !== -1) { ctor = Documents.PdfDocument; + options.nativeWidth = 600; } if (type.indexOf("html") !== -1) { if (path.includes('localhost')) { @@ -177,7 +178,6 @@ export class CollectionViewBase extends React.Component let item = e.dataTransfer.items[i]; if (item.kind === "string" && item.type.indexOf("uri") != -1) { e.dataTransfer.items[i].getAsString(action((s: string) => { - let document: Document; request.head(ServerUtils.prepend(RouteStore.corsProxy + "/" + s), (err, res, body) => { let type = res.headers["content-type"]; if (type) { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 3484e963e..1ddb84a99 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -20,6 +20,7 @@ import React = require("react"); import v5 = require("uuid/v5"); import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors"; import { PreviewCursor } from "./PreviewCursor"; +import { NumberField } from "../../../../fields/NumberField"; @observer export class CollectionFreeFormView extends CollectionViewBase { @@ -83,13 +84,17 @@ export class CollectionFreeFormView extends CollectionViewBase { let dragDoc = de.data.droppedDocuments[0]; let dragX = dragDoc.GetNumber(KeyStore.X, 0); let dragY = dragDoc.GetNumber(KeyStore.Y, 0); - droppedDocs.map(d => { + 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)); - if (!d.GetNumber(KeyStore.Width, 0)) { + 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); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index e2239c8be..df150a045 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -135,6 +135,7 @@ export class MarqueeView extends React.Component this.marqueeInkDelete(inkData); // }, 100); this.cleanupInteractions(); + SelectionManager.DeselectAll(); } } @action diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 3cb3cf782..c9fed7f66 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -1,5 +1,5 @@ import * as htmlToImage from "html-to-image"; -import { action, computed, observable, reaction, IReactionDisposer, trace, keys } from 'mobx'; +import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from "mobx-react"; import 'react-image-lightbox/style.css'; import Measure from "react-measure"; @@ -10,6 +10,7 @@ import { FieldWaiting, Opt } from '../../../fields/Field'; import { ImageField } from '../../../fields/ImageField'; import { KeyStore } from '../../../fields/KeyStore'; import { PDFField } from '../../../fields/PDFField'; +import { RouteStore } from "../../../server/RouteStore"; import { Utils } from '../../../Utils'; import { Annotation } from './Annotation'; import { FieldView, FieldViewProps } from './FieldView'; @@ -17,8 +18,6 @@ import "./ImageBox.scss"; import "./PDFBox.scss"; import { Sticky } from './Sticky'; //you should look at sticky and annotation, because they are used here import React = require("react") -import { RouteStore } from "../../../server/RouteStore"; -import { NumberField } from "../../../fields/NumberField"; /** ALSO LOOK AT: Annotation.tsx, Sticky.tsx * This method renders PDF and puts all kinds of functionalities such as annotation, highlighting, @@ -378,8 +377,9 @@ export class PDFBox extends React.Component { saveThumbnail = () => { setTimeout(() => { var me = this; - htmlToImage.toPng(this._mainDiv.current!, - { width: me.props.doc.GetNumber(KeyStore.NativeWidth, 0), height: me.props.doc.GetNumber(KeyStore.NativeHeight, 0), quality: 0.5 }) + let nwidth = me.props.doc.GetNumber(KeyStore.NativeWidth, 0); + let nheight = me.props.doc.GetNumber(KeyStore.NativeHeight, 0); + htmlToImage.toPng(this._mainDiv.current!, { width: nwidth, height: nheight, quality: 1 }) .then(function (dataUrl: string) { me.props.doc.SetData(KeyStore.Thumbnail, new URL(dataUrl), ImageField); me.props.doc.SetNumber(KeyStore.ThumbnailPage, me.props.doc.GetNumber(KeyStore.CurPage, -1)); -- cgit v1.2.3-70-g09d2 From 4fde212cd00bd2f8fc2fa122309af3bb71bba2fd Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 4 Apr 2019 18:18:45 -0400 Subject: improved pdf bitmaps a bit, but still blurry. --- src/client/documents/Documents.ts | 2 +- src/client/views/collections/CollectionViewBase.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/PDFBox.tsx | 20 +++++++++++--------- 4 files changed, 14 insertions(+), 12 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 0bf275df8..1f0744782 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -136,7 +136,7 @@ export namespace Documents { function GetPdfPrototype(): Document { if (!pdfProto) { pdfProto = setupPrototypeOptions(pdfProtoId, "PDF_PROTO", CollectionPDFView.LayoutString("AnnotationsKey"), - { x: 0, y: 0, nativeWidth: 600, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations] }); + { x: 0, y: 0, nativeWidth: 1200, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations] }); pdfProto.SetNumber(KeyStore.CurPage, 1); pdfProto.SetText(KeyStore.BackgroundLayout, PDFBox.LayoutString()); } diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx index 9eee23a1d..458bae7ab 100644 --- a/src/client/views/collections/CollectionViewBase.tsx +++ b/src/client/views/collections/CollectionViewBase.tsx @@ -128,7 +128,7 @@ export class CollectionViewBase extends React.Component } if (type.indexOf("pdf") !== -1) { ctor = Documents.PdfDocument; - options.nativeWidth = 600; + options.nativeWidth = 1200; } if (type.indexOf("html") !== -1) { if (path.includes('localhost')) { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 714ab9447..b9329f269 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -249,7 +249,7 @@ export class DocumentView extends React.Component { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); e.stopPropagation(); - if ( + if (!SelectionManager.IsSelected(this) && Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientY - this._downY) < 4 ) { diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index c9fed7f66..f9f5bc8f8 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -18,6 +18,7 @@ import "./ImageBox.scss"; import "./PDFBox.scss"; import { Sticky } from './Sticky'; //you should look at sticky and annotation, because they are used here import React = require("react") +import { SelectionManager } from "../../util/SelectionManager"; /** ALSO LOOK AT: Annotation.tsx, Sticky.tsx * This method renders PDF and puts all kinds of functionalities such as annotation, highlighting, @@ -57,6 +58,8 @@ export class PDFBox extends React.Component { private _mainDiv = React.createRef() private _pdf = React.createRef(); + @observable private _renderAsSvg = true; + //very useful for keeping track of X and y position throughout the PDF Canvas private initX: number = 0; private initY: number = 0; @@ -91,9 +94,9 @@ export class PDFBox extends React.Component { componentDidMount() { this._reactionDisposer = reaction( - () => [this.curPage, this.thumbnailPage], + () => [SelectionManager.SelectedDocuments().slice()], () => { - if (this.curPage > 0 && this.thumbnailPage > 0 && this.curPage != this.thumbnailPage) { + if (this.curPage > 0 && this.thumbnailPage > 0 && this.curPage != this.thumbnailPage && !this.props.isSelected()) { this.saveThumbnail(); this._interactive = true; } @@ -375,19 +378,21 @@ export class PDFBox extends React.Component { @action saveThumbnail = () => { + this._renderAsSvg = false; setTimeout(() => { var me = this; let nwidth = me.props.doc.GetNumber(KeyStore.NativeWidth, 0); let nheight = me.props.doc.GetNumber(KeyStore.NativeHeight, 0); htmlToImage.toPng(this._mainDiv.current!, { width: nwidth, height: nheight, quality: 1 }) - .then(function (dataUrl: string) { + .then(action((dataUrl: string) => { me.props.doc.SetData(KeyStore.Thumbnail, new URL(dataUrl), ImageField); me.props.doc.SetNumber(KeyStore.ThumbnailPage, me.props.doc.GetNumber(KeyStore.CurPage, -1)); - }) + me._renderAsSvg = true; + })) .catch(function (error: any) { console.error('oops, something went wrong!', error); }); - }, 1000); + }, 250); } @action @@ -427,9 +432,6 @@ export class PDFBox extends React.Component { this.props.doc.SetNumber(KeyStore.Height, nativeHeight / nativeWidth * this.props.doc.GetNumber(KeyStore.Width, 0)); this.props.doc.SetNumber(KeyStore.NativeHeight, nativeHeight); } - if (!this.props.doc.GetT(KeyStore.Thumbnail, ImageField)) { - this.saveThumbnail(); - } } @computed @@ -439,7 +441,7 @@ export class PDFBox extends React.Component { let pdfUrl = this.props.doc.GetT(this.props.fieldKey, PDFField); let xf = this.props.doc.GetNumber(KeyStore.NativeHeight, 0) / renderHeight; return
- + {({ measureRef }) =>
-- cgit v1.2.3-70-g09d2