diff options
| author | Sam Wilkins <abdullah_ahmed@brown.edu> | 2019-04-13 19:14:42 -0400 |
|---|---|---|
| committer | Sam Wilkins <abdullah_ahmed@brown.edu> | 2019-04-13 19:14:42 -0400 |
| commit | 7a1a028d1d3f5602d39f559cb1614abda658a371 (patch) | |
| tree | 5de71b963a2e2e370818591f6e99fba868503ee0 /src/client/views/collections | |
| parent | 35f09470b2939a6640b85c94a9a4ea2551a6ea42 (diff) | |
| parent | b7252aeacbbd73120bc6f80d551f4505646a883b (diff) | |
resolved merge conflicts with master
Diffstat (limited to 'src/client/views/collections')
16 files changed, 237 insertions, 303 deletions
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index 4380c8194..b5eaab349 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -32,19 +32,18 @@ export interface CollectionViewProps extends FieldViewProps { contentRef?: React.Ref<HTMLDivElement>; } -export const COLLECTION_BORDER_WIDTH = 1; @observer export class CollectionBaseView extends React.Component<CollectionViewProps> { - get collectionViewType(): CollectionViewType { + get collectionViewType(): CollectionViewType | undefined { let Document = this.props.Document; let viewField = Document.GetT(KeyStore.ViewType, NumberField); if (viewField === FieldWaiting) { - return CollectionViewType.Invalid; + return undefined; } else if (viewField) { return viewField.Data; } else { - return CollectionViewType.Freeform; + return CollectionViewType.Invalid; } } @@ -107,15 +106,18 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { const field = new ListField([doc]); // const script = CompileScript(` // if(added) { - // console.log("added " + field.Title); + // console.log("added " + field.Title + " " + doc.Title); // } else { - // console.log("removed " + field.Title); + // console.log("removed " + field.Title + " " + doc.Title); // } // `, { // addReturn: false, // params: { // field: Document.name, // added: "boolean" + // }, + // capturedVariables: { + // doc: this.props.Document // } // }); // if (script.compiled) { @@ -177,9 +179,10 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { active: this.active, onActiveChanged: this.onActiveChanged, }; + const viewtype = this.collectionViewType; return ( <div className={this.props.className || "collectionView-cont"} onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}> - {this.props.children(this.collectionViewType, props)} + {viewtype !== undefined ? this.props.children(viewtype, props) : (null)} </div> ); } diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss index 583d50c5b..0e7e0afa7 100644 --- a/src/client/views/collections/CollectionDockingView.scss +++ b/src/client/views/collections/CollectionDockingView.scss @@ -1,8 +1,29 @@ +@import "../../views/globalCssVariables.scss"; + .collectiondockingview-content { height: 100%; } +.lm_active .messageCounter{ + color:white; + background: #999999; +} +.messageCounter { + width:18px; + height:20px; + text-align: center; + border-radius: 20px; + margin-left: 5px; + transform: translate(0px, -8px); + display: inline-block; + background: transparent; + border: 1px #999999 solid; +} .collectiondockingview-container { + width: 100%; + height: 100%; + border-style: solid; + border-width: $COLLECTION_BORDER_WIDTH; position: absolute; top: 0; left: 0; diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 212cf8a69..eb1cd1c09 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -7,18 +7,18 @@ import * as ReactDOM from 'react-dom'; import { Document } from "../../../fields/Document"; import { KeyStore } from "../../../fields/KeyStore"; import Measure from "react-measure"; -import { FieldId, Opt, Field } from "../../../fields/Field"; -import { Utils, returnTrue, emptyFunction } from "../../../Utils"; +import { FieldId, Opt, Field, FieldWaiting } from "../../../fields/Field"; +import { Utils, returnTrue, emptyFunction, emptyDocFunction } from "../../../Utils"; import { Server } from "../../Server"; import { undoBatch } from "../../util/UndoManager"; import { DocumentView } from "../nodes/DocumentView"; import "./CollectionDockingView.scss"; -import { COLLECTION_BORDER_WIDTH } from "./CollectionBaseView"; import React = require("react"); import { SubCollectionViewProps } from "./CollectionSubView"; import { ServerUtils } from "../../../server/ServerUtil"; -import { DragManager } from "../../util/DragManager"; +import { DragManager, DragLinksAsDocuments } from "../../util/DragManager"; import { TextField } from "../../../fields/TextField"; +import { ListField } from "../../../fields/ListField"; @observer export class CollectionDockingView extends React.Component<SubCollectionViewProps> { @@ -194,23 +194,35 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp @action onPointerDown = (e: React.PointerEvent): void => { var className = (e.target as any).className; - if ((className === "lm_title" || className === "lm_tab lm_active") && (e.ctrlKey || e.altKey)) { + if (className === "messageCounter") { e.stopPropagation(); e.preventDefault(); + let x = e.clientX; + let y = e.clientY; let docid = (e.target as any).DashDocId; let tab = (e.target as any).parentElement as HTMLElement; - Server.GetField(docid, action((f: Opt<Field>) => { - if (f instanceof Document) { - DragManager.StartDocumentDrag([tab], new DragManager.DocumentDragData([f]), e.pageX, e.pageY, - { - handlers: { - dragComplete: action(emptyFunction), - }, - hideSource: false - }); - } - })); - } + Server.GetField(docid, action(async (sourceDoc: Opt<Field>) => + (sourceDoc instanceof Document) && DragLinksAsDocuments(tab, x, y, sourceDoc))); + } else + if ((className === "lm_title" || className === "lm_tab lm_active") && !e.shiftKey) { + e.stopPropagation(); + e.preventDefault(); + let x = e.clientX; + let y = e.clientY; + let docid = (e.target as any).DashDocId; + let tab = (e.target as any).parentElement as HTMLElement; + Server.GetField(docid, action((f: Opt<Field>) => { + if (f instanceof Document) { + DragManager.StartDocumentDrag([tab], new DragManager.DocumentDragData([f]), x, y, + { + handlers: { + dragComplete: action(emptyFunction), + }, + hideSource: false + }); + } + })); + } if (className === "lm_drag_handle" || className === "lm_close" || className === "lm_maximise" || className === "lm_minimise" || className === "lm_close_tab") { this._flush = true; } @@ -229,24 +241,44 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp this.stateChanged(); } + htmlToElement(html: string) { + var template = document.createElement('template'); + html = html.trim(); // Never return a text node of whitespace as the result + template.innerHTML = html; + return template.content.firstChild; + } + tabCreated = (tab: any) => { if (tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") { - if (tab.titleElement[0].textContent.indexOf("-waiting") !== -1) { - Server.GetField(tab.contentItem.config.props.documentId, action((f: Opt<Field>) => { - if (f !== undefined && f instanceof Document) { - f.GetTAsync(KeyStore.Title, TextField, (tfield) => { - if (tfield !== undefined) { - tab.titleElement[0].textContent = f.Title; - } - }); - } - })); - tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId; - } - tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId; + Server.GetField(tab.contentItem.config.props.documentId, action((f: Opt<Field>) => { + if (f !== undefined && f instanceof Document) { + f.GetTAsync(KeyStore.Title, TextField, (tfield) => { + if (tfield !== undefined) { + tab.titleElement[0].textContent = f.Title; + } + }); + f.GetTAsync(KeyStore.LinkedFromDocs, ListField).then(lf => + f.GetTAsync(KeyStore.LinkedToDocs, ListField).then(lt => { + let count = (lf ? lf.Data.length : 0) + (lt ? lt.Data.length : 0); + let counter: any = this.htmlToElement(`<div class="messageCounter">${count}</div>`); + tab.element.append(counter); + counter.DashDocId = tab.contentItem.config.props.documentId; + (tab as any).reactionDisposer = reaction(() => [f.GetT(KeyStore.LinkedFromDocs, ListField), f.GetT(KeyStore.LinkedToDocs, ListField)], + (lists) => { + let count = (lists.length > 0 && lists[0] && lists[0]!.Data ? lists[0]!.Data.length : 0) + + (lists.length > 1 && lists[1] && lists[1]!.Data ? lists[1]!.Data.length : 0); + counter.innerHTML = count; + }); + })); + tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId; + } + })); } tab.closeElement.off('click') //unbind the current click handler .click(function () { + if (tab.reactionDisposer) { + tab.reactionDisposer(); + } tab.contentItem.remove(); }); } @@ -271,13 +303,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp render() { return ( <div className="collectiondockingview-container" id="menuContainer" - onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef} - style={{ - width: "100%", - height: "100%", - borderStyle: "solid", - borderWidth: `${COLLECTION_BORDER_WIDTH}px`, - }} /> + onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef} /> ); } } @@ -325,7 +351,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { selectOnLoad={false} parentActive={returnTrue} onActiveChanged={emptyFunction} - focus={(doc: Document) => { }} + focus={emptyDocFunction} ContainingCollectionView={undefined} /> </div>; diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss index 9cd6c8a39..c8bfedff4 100644 --- a/src/client/views/collections/CollectionSchemaView.scss +++ b/src/client/views/collections/CollectionSchemaView.scss @@ -1,4 +1,4 @@ -@import "../global_variables"; +@import "../globalCssVariables"; //options menu styling #schemaOptionsMenuBtn { @@ -58,7 +58,9 @@ ul { .collectionSchemaView-container { - border: 1px solid $intermediate-color; + border-width: $COLLECTION_BORDER_WIDTH; + border-color : $intermediate-color; + border-style: solid; border-radius: $border-radius; box-sizing: border-box; position: absolute; diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index f1b3e1b8f..fdb82690a 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -2,31 +2,29 @@ import React = require("react"); import { library } from '@fortawesome/fontawesome-svg-core'; import { faCog, faPlus } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable, trace, untracked } from "mobx"; +import { action, computed, observable, untracked } from "mobx"; import { observer } from "mobx-react"; import Measure from "react-measure"; import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults } from "react-table"; import "react-table/react-table.css"; import { Document } from "../../../fields/Document"; -import { Field, Opt, FieldWaiting } from "../../../fields/Field"; +import { Field, Opt } from "../../../fields/Field"; import { Key } from "../../../fields/Key"; import { KeyStore } from "../../../fields/KeyStore"; import { ListField } from "../../../fields/ListField"; +import { emptyDocFunction, emptyFunction, returnFalse } from "../../../Utils"; import { Server } from "../../Server"; -import { setupDrag } from "../../util/DragManager"; +import { SetupDrag } from "../../util/DragManager"; import { CompileScript, ToField } from "../../util/Scripting"; import { Transform } from "../../util/Transform"; +import { COLLECTION_BORDER_WIDTH } from "../../views/globalCssVariables.scss"; import { anchorPoints, Flyout } from "../DocumentDecorations"; import '../DocumentDecorations.scss'; import { EditableView } from "../EditableView"; import { DocumentView } from "../nodes/DocumentView"; import { FieldView, FieldViewProps } from "../nodes/FieldView"; import "./CollectionSchemaView.scss"; -import { CollectionView } from "./CollectionView"; import { CollectionSubView } from "./CollectionSubView"; -import { TextField } from "../../../fields/TextField"; -import { COLLECTION_BORDER_WIDTH } from "./CollectionBaseView"; -import { emptyFunction, returnFalse } from "../../../Utils"; // bcz: need to add drag and drop of rows and columns. This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657 @@ -82,7 +80,7 @@ export class CollectionSchemaView extends CollectionSubView { isTopMost: false, selectOnLoad: false, ScreenToLocalTransform: Transform.Identity, - focus: emptyFunction, + focus: emptyDocFunction, active: returnFalse, onActiveChanged: emptyFunction, }; @@ -90,7 +88,7 @@ export class CollectionSchemaView extends CollectionSubView { <FieldView {...props} /> ); let reference = React.createRef<HTMLDivElement>(); - let onItemDown = setupDrag(reference, () => props.Document, this.props.moveDocument); + let onItemDown = SetupDrag(reference, () => props.Document, this.props.moveDocument); let applyToDoc = (doc: Document, run: (args?: { [name: string]: any }) => any) => { const res = run({ this: doc }); if (!res.success) return false; @@ -245,13 +243,13 @@ export class CollectionSchemaView extends CollectionSubView { this._contentScaling = r.entry.width / selected!.GetNumber(KeyStore.NativeWidth, r.entry.width); } + @computed + get borderWidth() { return COLLECTION_BORDER_WIDTH; } getContentScaling = (): number => this._contentScaling; getPanelWidth = (): number => this._panelWidth; getPanelHeight = (): number => this._panelHeight; - getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling); - getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX - this._tableWidth, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling); - - focusDocument = (doc: Document) => { }; + getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- this.borderWidth - this.DIVIDER_WIDTH - this._dividerX, - this.borderWidth).scale(1 / this._contentScaling); + getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- this.borderWidth - this.DIVIDER_WIDTH - this._dividerX - this._tableWidth, - this.borderWidth).scale(1 / this._contentScaling); onPointerDown = (e: React.PointerEvent): void => { if (e.button === 1 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) { @@ -315,7 +313,7 @@ export class CollectionSchemaView extends CollectionSubView { PanelWidth={this.getPanelWidth} PanelHeight={this.getPanelHeight} ContainingCollectionView={undefined} - focus={this.focusDocument} + focus={emptyDocFunction} parentActive={this.props.active} onActiveChanged={this.props.onActiveChanged} /> : null} <input value={this.previewScript} onChange={this.onPreviewScriptChange} @@ -349,7 +347,7 @@ export class CollectionSchemaView extends CollectionSubView { </Flyout>); return ( - <div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} onWheel={this.onWheel} ref={this._mainCont} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} > + <div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} onWheel={this.onWheel} ref={this._mainCont}> <div className="collectionSchemaView-dropTarget" onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}> <Measure onResize={this.setTableDimensions}> {({ measureRef }) => diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 6a6a6c900..d91db68bb 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -166,22 +166,16 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> { let item = e.dataTransfer.items[i]; if (item.kind === "string" && item.type.indexOf("uri") !== -1) { let str: string; - let prom = new Promise<string>(res => - e.dataTransfer.items[i].getAsString(res)).then(action((s: string) => { - str = s; - return rp.head(ServerUtils.prepend(RouteStore.corsProxy + "/" + s)); - })).then(res => { - let type = res.headers["content-type"]; + let prom = new Promise<string>(resolve => e.dataTransfer.items[i].getAsString(resolve)) + .then(action((s: string) => rp.head(ServerUtils.prepend(RouteStore.corsProxy + "/" + (str = s))))) + .then(result => { + let type = result.headers["content-type"]; if (type) { - this.getDocumentFromType(type, str, { ...options, width: 300, nativeWidth: 300 }).then(doc => { - if (doc) { - this.props.addDocument(doc, false); - } - }); + this.getDocumentFromType(type, str, { ...options, width: 300, nativeWidth: 300 }) + .then(doc => doc && this.props.addDocument(doc, false)); } }); promises.push(prom); - // this.props.addDocument(Documents.WebDocument(s, { ...options, width: 300, height: 300 }), false) } let type = item.type; if (item.kind === "file") { @@ -197,33 +191,30 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> { method: 'POST', body: formData }).then(async (res: Response) => { - const json = await res.json(); - json.map((file: any) => { + (await res.json()).map(action((file: any) => { let path = window.location.origin + file; - runInAction(() => { - let docPromise = this.getDocumentFromType(type, path, { ...options, nativeWidth: 300, width: 300, title: dropFileName }); + let docPromise = this.getDocumentFromType(type, path, { ...options, nativeWidth: 300, width: 300, title: dropFileName }); - docPromise.then(doc => runInAction(() => { - let docs = this.props.Document.GetT(KeyStore.Data, ListField); - if (docs !== FieldWaiting) { - if (!docs) { - docs = new ListField<Document>(); - this.props.Document.Set(KeyStore.Data, docs); - } - if (doc) { - docs.Data.push(doc); - } + docPromise.then(action((doc?: Document) => { + let docs = this.props.Document.GetT(KeyStore.Data, ListField); + if (docs !== FieldWaiting) { + if (!docs) { + docs = new ListField<Document>(); + this.props.Document.Set(KeyStore.Data, docs); } - })); - }); - }); + if (doc) { + docs.Data.push(doc); + } + } + })); + })); }); promises.push(prom); } } if (promises.length) { - Promise.all(promises).catch(emptyFunction).then(() => batch.end()); + Promise.all(promises).finally(() => batch.end()); } else { batch.end(); } diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index 4f940e094..8ecc5b67b 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -1,7 +1,9 @@ -@import "../global_variables"; +@import "../globalCssVariables"; .collectionTreeView-dropTarget { - border: 0px solid transparent; + border-width: $COLLECTION_BORDER_WIDTH; + border-color: transparent; + border-style: solid; border-radius: $border-radius; box-sizing: border-box; height: 100%; diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 659cff9fe..51a02fc25 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -7,13 +7,13 @@ import { Document } from "../../../fields/Document"; import { FieldWaiting } from "../../../fields/Field"; import { KeyStore } from "../../../fields/KeyStore"; import { ListField } from "../../../fields/ListField"; -import { setupDrag, DragManager } from "../../util/DragManager"; +import { SetupDrag, DragManager } from "../../util/DragManager"; import { EditableView } from "../EditableView"; import "./CollectionTreeView.scss"; import { CollectionView } from "./CollectionView"; +import * as globalCssVariables from "../../views/globalCssVariables.scss"; import { CollectionSubView } from "./CollectionSubView"; import React = require("react"); -import { COLLECTION_BORDER_WIDTH } from './CollectionBaseView'; import { props } from 'bluebird'; @@ -77,7 +77,7 @@ class TreeView extends React.Component<TreeViewProps> { */ renderTitle() { let reference = React.createRef<HTMLDivElement>(); - let onItemDown = setupDrag(reference, () => this.props.document, this.props.moveDocument, this.props.copyOnDrag); + let onItemDown = SetupDrag(reference, () => this.props.document, this.props.moveDocument, this.props.copyOnDrag); let editableView = (titleString: string) => (<EditableView display={"inline"} @@ -139,7 +139,7 @@ export class CollectionTreeView extends CollectionSubView { ); return ( - <div id="body" className="collectionTreeView-dropTarget" onWheel={(e: React.WheelEvent) => e.stopPropagation()} onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}> + <div id="body" className="collectionTreeView-dropTarget" onWheel={(e: React.WheelEvent) => e.stopPropagation()} onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}> <div className="coll-title"> <EditableView contents={this.props.Document.Title} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index cf058090d..647c83d4d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -1,4 +1,4 @@ -import { computed, reaction } from "mobx"; +import { computed, reaction, trace, IReactionDisposer } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../../fields/Document"; import { FieldWaiting } from "../../../../fields/Field"; @@ -15,18 +15,15 @@ import React = require("react"); @observer export class CollectionFreeFormLinksView extends React.Component<CollectionViewProps> { - HackToAvoidReactionFiringUnnecessarily?: Document = undefined; + _brushReactionDisposer?: IReactionDisposer; componentDidMount() { - this.HackToAvoidReactionFiringUnnecessarily = this.props.Document; - reaction(() => - DocumentManager.Instance.getAllDocumentViews(this.HackToAvoidReactionFiringUnnecessarily!). - map(dv => dv.props.Document.GetNumber(KeyStore.X, 0)), + this._brushReactionDisposer = reaction(() => this.props.Document.GetList<Document>(this.props.fieldKey, []).map(doc => doc.GetNumber(KeyStore.X, 0)), () => { - let views = DocumentManager.Instance.getAllDocumentViews(this.props.Document); + let views = this.props.Document.GetList<Document>(this.props.fieldKey, []); for (let i = 0; i < views.length; i++) { for (let j = 0; j < views.length; j++) { - let srcDoc = views[j].props.Document; - let dstDoc = views[i].props.Document; + let srcDoc = views[j]; + let dstDoc = views[i]; let x1 = srcDoc.GetNumber(KeyStore.X, 0); let x1w = srcDoc.GetNumber(KeyStore.Width, -1); let x2 = dstDoc.GetNumber(KeyStore.X, 0); @@ -53,7 +50,7 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP linkDoc.SetText(KeyStore.LinkDescription, "Brush between " + srcTarg.Title + " and " + dstTarg.Title); linkDoc.SetData(KeyStore.BrushingDocs, [dstTarg, srcTarg], ListField); - brushAction = brushAction = (field: ListField<Document>) => { + brushAction = (field: ListField<Document>) => { if (findBrush(field) === -1) { console.log("ADD BRUSH " + srcTarg.Title + " " + dstTarg.Title); (findBrush(field) === -1) && field.Data.push(linkDoc); @@ -67,6 +64,11 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP } }); } + componentWillUnmount() { + if (this._brushReactionDisposer) { + this._brushReactionDisposer(); + } + } documentAnchors(view: DocumentView) { let equalViews = [view]; let containerDoc = view.props.Document.GetT(KeyStore.AnnotationOn, Document); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss index c38787802..c5b8fc5e8 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss @@ -1,4 +1,4 @@ -@import "global_variables"; +@import "globalCssVariables"; .collectionFreeFormRemoteCursors-cont { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index 31809f30b..392bd514f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -1,13 +1,13 @@ -@import "../../global_variables"; +@import "../../globalCssVariables"; .collectionfreeformview-measure { - position: absolute; + position: inherit; top: 0; left: 0; width: 100%; height: 100%; } .collectionfreeformview { - position: absolute; + position: inherit; top: 0; left: 0; width: 100%; @@ -16,7 +16,7 @@ } .collectionfreeformview-container { .collectionfreeformview > .jsx-parser { - position: absolute; + position: inherit; height: 100%; width: 100%; } @@ -28,8 +28,10 @@ // background-size: 30px 30px; // } + border-width: $COLLECTION_BORDER_WIDTH; box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw; - border: 0px solid $light-color-secondary; + border-color: $light-color-secondary; + border-style: solid; border-radius: $border-radius; box-sizing: border-box; position: absolute; @@ -41,7 +43,7 @@ } .collectionfreeformview-overlay { .collectionfreeformview > .jsx-parser { - position: absolute; + position: inherit; height: 100%; } .formattedTextBox-cont { @@ -50,10 +52,12 @@ } opacity: 0.99; - border: 0px solid transparent; + border-width: 0; + border-color: transparent; + border-style: solid; border-radius: $border-radius; box-sizing: border-box; - position:absolute; + position: absolute; overflow: hidden; top: 0; left: 0; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 01ebbe0e1..e19dc98fa 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,31 +1,30 @@ -import { action, computed, observable, trace, ObservableSet, runInAction } from "mobx"; +import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; +import Measure from "react-measure"; import { Document } from "../../../../fields/Document"; import { FieldWaiting } from "../../../../fields/Field"; import { KeyStore } from "../../../../fields/KeyStore"; +import { NumberField } from "../../../../fields/NumberField"; import { TextField } from "../../../../fields/TextField"; +import { emptyFunction, returnFalse } from "../../../../Utils"; +import { DocumentManager } from "../../../util/DocumentManager"; import { DragManager } from "../../../util/DragManager"; +import { SelectionManager } from "../../../util/SelectionManager"; import { Transform } from "../../../util/Transform"; import { undoBatch } from "../../../util/UndoManager"; +import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss"; import { InkingCanvas } from "../../InkingCanvas"; +import { Main } from "../../Main"; import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; import { DocumentContentsView } from "../../nodes/DocumentContentsView"; import { DocumentViewProps } from "../../nodes/DocumentView"; -import { COLLECTION_BORDER_WIDTH } from "../CollectionBaseView"; import { CollectionSubView } from "../CollectionSubView"; import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView"; +import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors"; import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); import v5 = require("uuid/v5"); -import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors"; -import { PreviewCursor } from "./PreviewCursor"; -import { DocumentManager } from "../../../util/DocumentManager"; -import { SelectionManager } from "../../../util/SelectionManager"; -import { NumberField } from "../../../../fields/NumberField"; -import { Main } from "../../Main"; -import Measure from "react-measure"; -import { returnFalse, emptyFunction } from "../../../../Utils"; @observer export class CollectionFreeFormView extends CollectionSubView { @@ -297,8 +296,12 @@ export class CollectionFreeFormView extends CollectionSubView { layoutKey={KeyStore.OverlayLayout} isTopMost={this.props.isTopMost} isSelected={returnFalse} select={emptyFunction} />); } - getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform()); - getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH); + @computed + get borderWidth() { + return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; + } + getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform()); + getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth); getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.scale).translate(this.panX, this.panY); noScaling = () => 1; childViews = () => this.views; @@ -307,9 +310,9 @@ export class CollectionFreeFormView extends CollectionSubView { const [dx, dy] = [this.centeringShiftX, this.centeringShiftY]; const panx: number = -this.props.Document.GetNumber(KeyStore.PanX, 0); const pany: number = -this.props.Document.GetNumber(KeyStore.PanY, 0); - const zoom: number = this.zoomScaling; - const blay = this.backgroundView; - const olay = this.overlayView; + const zoom: number = this.zoomScaling;// needs to be a variable outside of the <Measure> otherwise, reactions won't fire + const backgroundView = this.backgroundView; // needs to be a variable outside of the <Measure> otherwise, reactions won't fire + const overlayView = this.overlayView;// needs to be a variable outside of the <Measure> otherwise, reactions won't fire return ( <Measure onResize={(r: any) => runInAction(() => { this._pwidth = r.entry.width; this._pheight = r.entry.height; })}> @@ -317,25 +320,21 @@ export class CollectionFreeFormView extends CollectionSubView { <div className={`collectionfreeformview-measure`} ref={measureRef}> <div className={`collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`} onPointerDown={this.onPointerDown} onPointerMove={(e) => super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY))} - onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} onWheel={this.onPointerWheel} - style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} ref={this.createDropTarget}> + onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} onWheel={this.onPointerWheel} ref={this.createDropTarget}> <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} - addDocument={this.addDocument} removeDocument={this.props.removeDocument} + addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox} getContainerTransform={this.getContainerTransform} getTransform={this.getTransform}> - <PreviewCursor container={this} addLiveTextDocument={this.addLiveTextBox} - getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} > - <div className="collectionfreeformview" ref={this._canvasRef} - style={{ transform: `translate(${dx}px, ${dy}px) scale(${zoom}, ${zoom}) translate(${panx}px, ${pany}px)` }}> - {blay} - <CollectionFreeFormLinksView {...this.props}> - <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} > - {this.childViews} - </InkingCanvas> - </CollectionFreeFormLinksView> - <CollectionFreeFormRemoteCursors {...this.props} /> - </div> - {olay} - </PreviewCursor> + <div className="collectionfreeformview" ref={this._canvasRef} + style={{ transform: `translate(${dx}px, ${dy}px) scale(${zoom}, ${zoom}) translate(${panx}px, ${pany}px)` }}> + {backgroundView} + <CollectionFreeFormLinksView {...this.props}> + <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} > + {this.childViews} + </InkingCanvas> + </CollectionFreeFormLinksView> + <CollectionFreeFormRemoteCursors {...this.props} /> + </div> + {overlayView} </MarqueeView> </div> </div>)} diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss index 0b406e722..e5ffcec76 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss @@ -1,6 +1,6 @@ .marqueeView { - position: absolute; + position: inherit; top:0; left:0; width:100%; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 1e6faafb3..a4722a6f8 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,4 +1,4 @@ -import { action, computed, observable, trace } from "mobx"; +import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../../fields/Document"; import { FieldWaiting } from "../../../../fields/Field"; @@ -7,10 +7,11 @@ import { KeyStore } from "../../../../fields/KeyStore"; import { Documents } from "../../../documents/Documents"; import { SelectionManager } from "../../../util/SelectionManager"; import { Transform } from "../../../util/Transform"; +import { undoBatch } from "../../../util/UndoManager"; import { InkingCanvas } from "../../InkingCanvas"; +import { PreviewCursor } from "../../PreviewCursor"; import { CollectionFreeFormView } from "./CollectionFreeFormView"; import "./MarqueeView.scss"; -import { PreviewCursor } from "./PreviewCursor"; import React = require("react"); interface MarqueeViewProps { @@ -21,6 +22,7 @@ interface MarqueeViewProps { activeDocuments: () => Document[]; selectDocuments: (docs: Document[]) => void; removeDocument: (doc: Document) => boolean; + addLiveTextDocument: (doc: Document) => void; } @observer @@ -32,6 +34,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps> @observable _downY: number = 0; @observable _used: boolean = false; @observable _visible: boolean = false; + _showOnUp: boolean = false; static DRAG_THRESHOLD = 4; @action @@ -47,11 +50,31 @@ export class MarqueeView extends React.Component<MarqueeViewProps> } @action + onKeyPress = (e: KeyboardEvent) => { + // Mixing events between React and Native is finicky. In FormattedTextBox, we set the + // DASHFormattedTextBoxHandled flag when a text box consumes a key press so that we can ignore + // the keyPress here. + //if not these keys, make a textbox if preview cursor is active! + if (!e.ctrlKey && !e.altKey && !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) { + //make textbox and add it to this collection + let [x, y] = this.props.getTransform().transformPoint(this._downX, this._downY); + let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "typed text" }); + this.props.addLiveTextDocument(newBox); + PreviewCursor.Visible = false; + e.stopPropagation(); + } + } + hideCursor = () => { + document.removeEventListener("keypress", this.onKeyPress, false); + } + @action onPointerDown = (e: React.PointerEvent): void => { if (e.buttons === 1 && !e.altKey && !e.metaKey && this.props.container.props.active()) { this._downX = this._lastX = e.pageX; this._downY = this._lastY = e.pageY; this._used = false; + this._showOnUp = true; + document.removeEventListener("keypress", this.onKeyPress, false); document.addEventListener("pointermove", this.onPointerMove, true); document.addEventListener("pointerup", this.onPointerUp, true); document.addEventListener("keydown", this.marqueeCommand, true); @@ -63,6 +86,10 @@ export class MarqueeView extends React.Component<MarqueeViewProps> this._lastX = e.pageX; this._lastY = e.pageY; if (!e.cancelBubble) { + if (Math.abs(this._downX - e.clientX) > 4 || Math.abs(this._downY - e.clientY) > 4) { + this._showOnUp = false; + PreviewCursor.Visible = false; + } if (!this._used && e.buttons === 1 && !e.altKey && !e.metaKey && (Math.abs(this._lastX - this._downX) > MarqueeView.DRAG_THRESHOLD || Math.abs(this._lastY - this._downY) > MarqueeView.DRAG_THRESHOLD)) { this._visible = true; @@ -76,11 +103,16 @@ export class MarqueeView extends React.Component<MarqueeViewProps> onPointerUp = (e: PointerEvent): void => { this.cleanupInteractions(true); this._visible = false; - let mselect = this.marqueeSelect(); - if (!e.shiftKey) { - SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document); + if (this._showOnUp) { + PreviewCursor.Show(this.hideCursor, this._downX, this._downY); + document.addEventListener("keypress", this.onKeyPress, false); + } else { + let mselect = this.marqueeSelect(); + if (!e.shiftKey) { + SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document); + } + this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]); } - this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]); } intersectRect(r1: { left: number, top: number, width: number, height: number }, @@ -97,6 +129,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps> return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) }; } + @undoBatch @action marqueeCommand = (e: KeyboardEvent) => { if (e.key === "Backspace" || e.key === "Delete") { diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.scss b/src/client/views/collections/collectionFreeForm/PreviewCursor.scss deleted file mode 100644 index 7a67c29bf..000000000 --- a/src/client/views/collections/collectionFreeForm/PreviewCursor.scss +++ /dev/null @@ -1,27 +0,0 @@ - -.previewCursor { - color: black; - position: absolute; - transform-origin: left top; - top: 0; - left:0; - pointer-events: none; -} -.previewCursorView { - top: 0; - left:0; - position: absolute; - width:100%; - height:100%; -} - -//this is an animation for the blinking cursor! -// @keyframes blink { -// 0% {opacity: 0} -// 49%{opacity: 0} -// 50% {opacity: 1} -// } - -// #previewCursor { -// animation: blink 1s infinite; -// }
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx deleted file mode 100644 index 8eabb020a..000000000 --- a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { action, observable, trace, computed, reaction } from "mobx"; -import { observer } from "mobx-react"; -import { Document } from "../../../../fields/Document"; -import { Documents } from "../../../documents/Documents"; -import { Transform } from "../../../util/Transform"; -import { CollectionFreeFormView } from "./CollectionFreeFormView"; -import "./PreviewCursor.scss"; -import React = require("react"); -import { interfaceDeclaration } from "babel-types"; - - -export interface PreviewCursorProps { - getTransform: () => Transform; - getContainerTransform: () => Transform; - container: CollectionFreeFormView; - addLiveTextDocument: (doc: Document) => void; -} - -@observer -export class PreviewCursor extends React.Component<PreviewCursorProps> { - @observable _lastX: number = 0; - @observable _lastY: number = 0; - @observable public _visible: boolean = false; - @observable public DownX: number = 0; - @observable public DownY: number = 0; - _showOnUp: boolean = false; - - @action - cleanupInteractions = () => { - document.removeEventListener("pointerup", this.onPointerUp, true); - document.removeEventListener("pointermove", this.onPointerMove, true); - } - - @action - onPointerDown = (e: React.PointerEvent) => { - if (e.button === 0 && this.props.container.props.active()) { - document.removeEventListener("keypress", this.onKeyPress, false); - this._showOnUp = true; - this.DownX = e.pageX; - this.DownY = e.pageY; - document.addEventListener("pointerup", this.onPointerUp, true); - document.addEventListener("pointermove", this.onPointerMove, true); - } - } - @action - onPointerMove = (e: PointerEvent): void => { - if (Math.abs(this.DownX - e.clientX) > 4 || Math.abs(this.DownY - e.clientY) > 4) { - this._showOnUp = false; - this._visible = false; - } - } - - @action - onPointerUp = (e: PointerEvent): void => { - if (this._showOnUp) { - document.addEventListener("keypress", this.onKeyPress, false); - this._lastX = this.DownX; - this._lastY = this.DownY; - this._visible = true; - } - this.cleanupInteractions(); - } - - @action - onKeyPress = (e: KeyboardEvent) => { - // Mixing events between React and Native is finicky. In FormattedTextBox, we set the - // DASHFormattedTextBoxHandled flag when a text box consumes a key press so that we can ignore - // the keyPress here. - //if not these keys, make a textbox if preview cursor is active! - if (!e.ctrlKey && !e.altKey && !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) { - //make textbox and add it to this collection - let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY); - let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "typed text" }); - this.props.addLiveTextDocument(newBox); - document.removeEventListener("keypress", this.onKeyPress, false); - this._visible = false; - e.stopPropagation(); - } - } - - getPoint = () => this.props.getContainerTransform().transformPoint(this._lastX, this._lastY); - getVisible = () => this._visible; - setVisible = (v: boolean) => { - this._visible = v; - document.removeEventListener("keypress", this.onKeyPress, false); - } - render() { - return ( - <div className="previewCursorView" onPointerDown={this.onPointerDown}> - {this.props.children} - <PreviewCursorPrompt setVisible={this.setVisible} getPoint={this.getPoint} getVisible={this.getVisible} /> - </div> - ); - } -} - -export interface PromptProps { - getPoint: () => number[]; - getVisible: () => boolean; - setVisible: (v: boolean) => void; -} - -@observer -export class PreviewCursorPrompt extends React.Component<PromptProps> { - private _promptRef = React.createRef<HTMLDivElement>(); - - //when focus is lost, this will remove the preview cursor - @action onBlur = (): void => this.props.setVisible(false); - - render() { - let p = this.props.getPoint(); - if (this.props.getVisible() && this._promptRef.current) { - this._promptRef.current.focus(); - } - return <div className="previewCursor" id="previewCursor" onBlur={this.onBlur} tabIndex={0} ref={this._promptRef} - style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, opacity: this.props.getVisible() ? 1 : 0 }}> - I - </div >; - } -}
\ No newline at end of file |
