aboutsummaryrefslogtreecommitdiff
path: root/src/client/views
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views')
-rw-r--r--src/client/views/ContextMenu.tsx9
-rw-r--r--src/client/views/DocumentDecorations.scss89
-rw-r--r--src/client/views/DocumentDecorations.tsx273
-rw-r--r--src/client/views/EditableView.tsx11
-rw-r--r--src/client/views/InkingCanvas.scss173
-rw-r--r--src/client/views/InkingCanvas.tsx205
-rw-r--r--src/client/views/InkingControl.scss135
-rw-r--r--src/client/views/InkingControl.tsx3
-rw-r--r--src/client/views/InkingStroke.tsx20
-rw-r--r--src/client/views/Main.scss7
-rw-r--r--src/client/views/Main.tsx210
-rw-r--r--src/client/views/collections/CollectionDockingView.scss2
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx40
-rw-r--r--src/client/views/collections/CollectionPDFView.scss2
-rw-r--r--src/client/views/collections/CollectionPDFView.tsx2
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx89
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx2
-rw-r--r--src/client/views/collections/CollectionVideoView.scss2
-rw-r--r--src/client/views/collections/CollectionVideoView.tsx108
-rw-r--r--src/client/views/collections/CollectionView.tsx57
-rw-r--r--src/client/views/collections/CollectionViewBase.tsx86
-rw-r--r--src/client/views/collections/MarqueeView.tsx175
-rw-r--r--src/client/views/collections/PreviewCursor.scss18
-rw-r--r--src/client/views/collections/PreviewCursor.tsx73
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss6
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx37
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss12
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx106
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx115
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss (renamed from src/client/views/collections/CollectionFreeFormView.scss)41
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx (renamed from src/client/views/collections/CollectionFreeFormView.tsx)280
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.scss (renamed from src/client/views/collections/MarqueeView.scss)8
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx202
-rw-r--r--src/client/views/collections/collectionFreeForm/PreviewCursor.scss27
-rw-r--r--src/client/views/collections/collectionFreeForm/PreviewCursor.tsx119
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx8
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx12
-rw-r--r--src/client/views/nodes/DocumentView.scss20
-rw-r--r--src/client/views/nodes/DocumentView.tsx379
-rw-r--r--src/client/views/nodes/FieldView.tsx18
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx285
-rw-r--r--src/client/views/nodes/HistogramBox.scss8
-rw-r--r--src/client/views/nodes/HistogramBox.tsx67
-rw-r--r--src/client/views/nodes/LinkBox.tsx23
-rw-r--r--src/client/views/nodes/PDFBox.scss4
-rw-r--r--src/client/views/nodes/PDFBox.tsx53
-rw-r--r--src/client/views/nodes/Sticky.tsx138
-rw-r--r--src/client/views/nodes/VideoBox.tsx29
-rw-r--r--src/client/views/nodes/WebBox.scss2
49 files changed, 2355 insertions, 1435 deletions
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 9109b56bb..cfa8ea7b7 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -49,7 +49,7 @@ export class ContextMenu extends React.Component {
//maxX and maxY will change if the UI/font size changes, but will work for any amount
//of items added to the menu
let maxX = window.innerWidth - 150;
- let maxY = window.innerHeight - (this._items.length * 34 + 30);
+ let maxY = window.innerHeight - ((this._items.length + 1/*for search box*/) * 34 + 30);
this._pageX = x > maxX ? maxX : x;
this._pageY = y > maxY ? maxY : y;
@@ -83,11 +83,8 @@ export class ContextMenu extends React.Component {
return (
<div className="contextMenu-cont" style={style} ref={this.ref}>
<input className="contextMenu-item" type="text" placeholder="Search . . ." value={this._searchString} onChange={this.onChange}></input>
- {this._items.filter(prop => {
- return prop.description.toLowerCase().indexOf(this._searchString.toLowerCase()) !== -1;
- }).map(prop => {
- return <ContextMenuItem {...prop} key={prop.description} />
- })}
+ {this._items.filter(prop => prop.description.toLowerCase().indexOf(this._searchString.toLowerCase()) !== -1).
+ map(prop => <ContextMenuItem {...prop} key={prop.description} />)}
</div>
)
}
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 11595aa01..d7137d7a2 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -1,35 +1,92 @@
@import "global_variables";
+
#documentDecorations-container {
position: absolute;
+ top: 0;
+ left:0;
display: grid;
z-index: 1000;
- grid-template-rows: 8px 1fr 8px 30px;
- grid-template-columns: 8px 1fr 8px;
+ grid-template-rows: 20px 8px 1fr 8px;
+ grid-template-columns: 8px 8px 1fr 8px 8px;
pointer-events: none;
+
#documentDecorations-centerCont {
+ grid-column:3;
background: none;
}
+
.documentDecorations-resizer {
pointer-events: auto;
background: $alt-accent;
opacity: 0.8;
}
+
+ #documentDecorations-topLeftResizer,
+ #documentDecorations-leftResizer,
+ #documentDecorations-bottomLeftResizer {
+ grid-column: 1
+ }
+
+ #documentDecorations-topResizer,
+ #documentDecorations-bottomResizer {
+ grid-column-start: 2;
+ grid-column-end: 5;
+ }
+
+ #documentDecorations-bottomRightResizer,
+ #documentDecorations-topRightResizer,
+ #documentDecorations-rightResizer {
+ grid-column-start:5;
+ grid-column-end:7;
+ }
+
#documentDecorations-topLeftResizer,
#documentDecorations-bottomRightResizer {
cursor: nwse-resize;
}
+
#documentDecorations-topRightResizer,
#documentDecorations-bottomLeftResizer {
cursor: nesw-resize;
}
+
#documentDecorations-topResizer,
#documentDecorations-bottomResizer {
cursor: ns-resize;
}
+
#documentDecorations-leftResizer,
#documentDecorations-rightResizer {
cursor: ew-resize;
}
+ .title{
+ background: lightblue;
+ grid-column-start:3;
+ grid-column-end: 4;
+ pointer-events: auto;
+ }
+}
+
+.documentDecorations-closeButton {
+ background:$alt-accent;
+ opacity: 0.8;
+ grid-column-start: 4;
+ grid-column-end: 6;
+ pointer-events: all;
+ text-align: center;
+}
+.documentDecorations-minimizeButton {
+ background:$alt-accent;
+ opacity: 0.8;
+ grid-column-start: 1;
+ grid-column-end: 3;
+ pointer-events: all;
+ text-align: center;
+}
+.documentDecorations-background {
+ background: lightblue;
+ position: absolute;
+ opacity: 0.1;
}
// position: absolute;
@@ -64,7 +121,8 @@
// }
// }
.linkFlyout {
- grid-column: 1/4
+ grid-column: 1/4;
+ margin-left: 25px;
}
.linkButton-empty:hover {
@@ -79,6 +137,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 47098c3b5..8bf1a42d1 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,36 +1,84 @@
-import { observable, computed, action } from "mobx";
-import React = require("react");
-import { SelectionManager } from "../util/SelectionManager";
+import { action, computed, observable, trace, runInAction } from "mobx";
import { observer } from "mobx-react";
-import './DocumentDecorations.scss'
-import { KeyStore } from '../../fields/KeyStore'
+import { Key } from "../../fields/Key";
+//import ContentEditable from 'react-contenteditable'
+import { KeyStore } from "../../fields/KeyStore";
+import { ListField } from "../../fields/ListField";
import { NumberField } from "../../fields/NumberField";
-import { props } from "bluebird";
+import { Document } from "../../fields/Document";
+import { TextField } from "../../fields/TextField";
import { DragManager } from "../util/DragManager";
+import { SelectionManager } from "../util/SelectionManager";
+import { CollectionView } from "./collections/CollectionView";
+import './DocumentDecorations.scss';
+import { DocumentView } from "./nodes/DocumentView";
import { LinkMenu } from "./nodes/LinkMenu";
-import { ListField } from "../../fields/ListField";
+import React = require("react");
+import { FieldWaiting } from "../../fields/Field";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@observer
-export class DocumentDecorations extends React.Component {
+export class DocumentDecorations extends React.Component<{}, { value: string }> {
static Instance: DocumentDecorations
private _resizer = ""
private _isPointerDown = false;
-
+ private keyinput: React.RefObject<HTMLInputElement>;
+ private _documents: DocumentView[] = SelectionManager.SelectedDocuments();
private _resizeBorderWidth = 16;
+ private _linkBoxHeight = 30;
+ private _titleHeight = 20;
private _linkButton = React.createRef<HTMLDivElement>();
+ private _linkerButton = React.createRef<HTMLDivElement>();
+ //@observable private _title: string = this._documents[0].props.Document.Title;
+ @observable private _title: string = this._documents.length > 0 ? this._documents[0].props.Document.Title : "";
+ @observable private _fieldKey: Key = KeyStore.Title;
@observable private _hidden = false;
+ @observable private _opacity = 1;
+ @observable private _dragging = false;
+
constructor(props: Readonly<{}>) {
super(props)
-
DocumentDecorations.Instance = this
+ this.handleChange = this.handleChange.bind(this);
+ this.keyinput = React.createRef();
+ }
+
+ @action
+ handleChange = (event: any) => {
+ this._title = event.target.value;
+ };
+
+ @action
+ enterPressed = (e: any) => {
+ var key = e.keyCode || e.which;
+ // enter pressed
+ if (key == 13) {
+ var text = e.target.value;
+ if (text[0] == '#') {
+ let command = text.slice(1, text.length);
+ this._fieldKey = new Key(command)
+ // if (command == "Title" || command == "title") {
+ // this._fieldKey = KeyStore.Title;
+ // }
+ // else if (command == "Width" || command == "width") {
+ // this._fieldKey = KeyStore.Width;
+ // }
+ this._title = "changed"
+ // TODO: Change field with switch statement
+ }
+ else {
+ this._title = "changed";
+ }
+ e.target.blur();
+ }
}
@computed
get Bounds(): { x: number, y: number, b: number, r: number } {
+ this._documents = SelectionManager.SelectedDocuments();
return SelectionManager.SelectedDocuments().reduce((bounds, documentView) => {
if (documentView.props.isTopMost) {
return bounds;
@@ -50,6 +98,97 @@ 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();
+ }
+
+ 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) {
+ document.removeEventListener("pointermove", this.onMinimizeMove);
+ document.addEventListener("pointermove", this.onMinimizeMove);
+ document.removeEventListener("pointerup", this.onMinimizeUp);
+ document.addEventListener("pointerup", this.onMinimizeUp);
+ }
+ }
+ onMinimizeMove = (e: PointerEvent): void => {
+ e.stopPropagation();
+ if (e.button === 0) {
+ }
+ }
+ onMinimizeUp = (e: PointerEvent): void => {
+ e.stopPropagation();
+ if (e.button === 0) {
+ SelectionManager.SelectedDocuments().map(dv => dv.minimize());
+ document.removeEventListener("pointermove", this.onMinimizeMove);
+ document.removeEventListener("pointerup", this.onMinimizeUp);
+ }
+ }
+
onPointerDown = (e: React.PointerEvent): void => {
e.stopPropagation();
if (e.button === 0) {
@@ -62,18 +201,40 @@ export class DocumentDecorations extends React.Component {
}
}
- onLinkButtonDown = (e: React.PointerEvent): void => {
- // if ()
- // let linkMenu = new LinkMenu(SelectionManager.SelectedDocuments()[0]);
- // linkMenu.Hidden = false;
- console.log("down");
+ onLinkerButtonDown = (e: React.PointerEvent): void => {
+ e.stopPropagation();
+ document.removeEventListener("pointermove", this.onLinkerButtonMoved)
+ document.addEventListener("pointermove", this.onLinkerButtonMoved);
+ document.removeEventListener("pointerup", this.onLinkerButtonUp)
+ document.addEventListener("pointerup", this.onLinkerButtonUp);
+ }
+ onLinkerButtonUp = (e: PointerEvent): void => {
+ document.removeEventListener("pointermove", this.onLinkerButtonMoved)
+ document.removeEventListener("pointerup", this.onLinkerButtonUp)
+ e.stopPropagation();
+ }
+ onLinkerButtonMoved = (e: PointerEvent): void => {
+ if (this._linkerButton.current != null) {
+ document.removeEventListener("pointermove", this.onLinkerButtonMoved)
+ document.removeEventListener("pointerup", this.onLinkerButtonUp)
+ let dragData = new DragManager.LinkDragData(SelectionManager.SelectedDocuments()[0]);
+ 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 => {
@@ -82,18 +243,35 @@ export class DocumentDecorations extends React.Component {
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)
- 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 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(() => { }),
+ },
+ hideSource: false
+ })
+ }
}
e.stopPropagation();
}
@@ -185,15 +363,29 @@ export class DocumentDecorations extends React.Component {
}
}
+ getValue = (): string => {
+ if (this._title === "changed" && this._documents.length > 0) {
+ let field = this._documents[0].props.Document.Get(this._fieldKey);
+ if (field instanceof TextField) {
+ return (field as TextField).GetValue();
+ }
+ else if (field instanceof NumberField) {
+ return (field as NumberField).GetValue().toString();
+ }
+ }
+ return this._title;
+ }
+
changeFlyoutContent = (): void => {
}
// buttonOnPointerUp = (e: React.PointerEvent): void => {
// e.stopPropagation();
// }
-
render() {
var bounds = this.Bounds;
+ // console.log(this._documents.length)
+ // let test = this._documents[0].props.Document.Title;
if (this.Hidden) {
return (null);
}
@@ -211,19 +403,31 @@ export class DocumentDecorations extends React.Component {
linkButton = (<Flyout
anchorPoint={anchorPoints.RIGHT_TOP}
content={
- <LinkMenu docView={selFirst} changeFlyout={this.changeFlyoutContent}>
- </LinkMenu>
+ <LinkMenu docView={selFirst} changeFlyout={this.changeFlyoutContent} />
}>
<div className={"linkButton-" + (selFirst.props.Document.GetData(KeyStore.LinkedToDocs, ListField, []).length ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} >{linkCount}</div>
</Flyout>);
}
- return (
- <div id="documentDecorations-container" style={{
+ return (<div className="documentDecorations">
+ <div className="documentDecorations-background" style={{
width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
- height: (bounds.b - bounds.y + this._resizeBorderWidth + 30) + "px",
+ height: (bounds.b - bounds.y + this._resizeBorderWidth) + "px",
left: bounds.x - this._resizeBorderWidth / 2,
top: bounds.y - this._resizeBorderWidth / 2,
+ pointerEvents: this._dragging ? "none" : "all",
+ zIndex: SelectionManager.SelectedDocuments().length > 1 ? 1000 : 0,
+ }} onPointerDown={this.onBackgroundDown} onContextMenu={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation() }} >
+ </div>
+ <div id="documentDecorations-container" style={{
+ width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
+ height: (bounds.b - bounds.y + this._resizeBorderWidth + this._linkBoxHeight + this._titleHeight) + "px",
+ left: bounds.x - this._resizeBorderWidth / 2,
+ top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight,
+ opacity: this._opacity
}}>
+ <div className="documentDecorations-minimizeButton" onPointerDown={this.onMinimizeDown}>...</div>
+ <input ref={this.keyinput} className="title" type="text" name="dynbox" value={this.getValue()} onChange={this.handleChange} onPointerDown={this.onPointerDown} onKeyPress={this.enterPressed} />
+ <div className="documentDecorations-closeButton" onPointerDown={this.onCloseDown}>X</div>
<div id="documentDecorations-topLeftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
<div id="documentDecorations-topResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
<div id="documentDecorations-topRightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
@@ -234,9 +438,10 @@ export class DocumentDecorations extends React.Component {
<div id="documentDecorations-bottomResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
<div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
- <div title="View Links" className="linkFlyout" ref={this._linkButton}>{linkButton}</div>
-
+ <div title="View Links" className="linkFlyout" ref={this._linkButton}> {linkButton} </div>
+ <div className="linkButton-linker" ref={this._linkerButton} onPointerDown={this.onLinkerButtonDown}>∞</div>
</div >
+ </div>
)
}
} \ No newline at end of file
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 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<EditableProps> {
@action
onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
- 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/InkingCanvas.scss b/src/client/views/InkingCanvas.scss
index e79b146b9..c5a2a50cb 100644
--- a/src/client/views/InkingCanvas.scss
+++ b/src/client/views/InkingCanvas.scss
@@ -1,149 +1,32 @@
@import "global_variables";
-.inking-canvas {
+
+.inkingCanvas-paths-ink, .inkingCanvas-paths-markers, .inkingCanvas-noSelect, .inkingCanvas-canSelect {
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;
- }
- }
+ top: 0;
+ left:0;
+ width: 8192px;
+ height: 8192px;
+ cursor:"crosshair";
+ pointer-events: auto;
+
+}
+.inkingCanvas-canSelect,
+.inkingCanvas-noSelect {
+ top:-50000px;
+ left:-50000px;
+ width: 100000px;
+ height: 100000px;
+}
+.inkingCanvas-noSelect {
+ pointer-events: none;
+ cursor: "arrow";
+}
+.inkingCanvas-paths-ink, .inkingCanvas-paths-markers {
+ pointer-events: none;
+ z-index: 10000; // overlays ink on top of everything
+ cursor: "arrow";
+}
+.inkingCanvas-paths-markers {
+ mix-blend-mode: multiply;
}
-.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 36a8834a0..45ca52d00 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -1,121 +1,112 @@
+import { action, computed, trace, observable, runInAction } from "mobx";
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 { 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;
Document: Document;
+ children: () => JSX.Element[];
}
@observer
export class InkingCanvas extends React.Component<InkCanvasProps> {
- static InkOffset: number = 50000;
+ maxCanvasDim = 8192 / 2; // 1/2 of the maximum canvas dimension for Chrome
+ @observable inkMidX: number = 0;
+ @observable inkMidY: number = 0;
+ 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 &&
- selRect.top < val.y - InkingCanvas.InkOffset && selRect.top + selRect.height > val.y - InkingCanvas.InkOffset)
- inside = true;
- });
- return inside
+ return stroke.pathData.reduce((inside: boolean, val) => inside ||
+ (selRect.left < val.x && selRect.left + selRect.width > val.x &&
+ selRect.top < val.y && selRect.top + selRect.height > val.y)
+ , false);
}
- private _isDrawing: boolean = false;
- private _idGenerator: string = "";
- constructor(props: Readonly<InkCanvasProps>) {
- super(props);
+ componentDidMount() {
+ this.props.Document.GetTAsync(KeyStore.Ink, InkField, ink => runInAction(() => {
+ if (ink) {
+ let bounds = Array.from(ink.Data).reduce(([mix, max, miy, may], [id, strokeData]) =>
+ strokeData.pathData.reduce(([mix, max, miy, may], p) =>
+ [Math.min(mix, p.x), Math.max(max, p.x), Math.min(miy, p.y), Math.max(may, p.y)],
+ [mix, max, miy, may]),
+ [Number.MAX_VALUE, Number.MIN_VALUE, Number.MAX_VALUE, Number.MIN_VALUE]);
+ this.inkMidX = (bounds[0] + bounds[1]) / 2;
+ this.inkMidY = (bounds[2] + bounds[3]) / 2;
+ }
+ }));
}
@computed
get inkData(): StrokeMap {
let map = this.props.Document.GetT(KeyStore.Ink, InkField);
- if (!map || map === FieldWaiting) {
- return new Map;
- }
- return new Map(map.Data);
+ return !map || map === FieldWaiting ? new Map : new Map(map.Data);
}
set inkData(value: StrokeMap) {
this.props.Document.SetDataOnPrototype(KeyStore.Ink, value, InkField);
}
- componentDidMount() {
- document.addEventListener("mouseup", this.handleMouseUp);
- }
-
- componentWillUnmount() {
- document.removeEventListener("mouseup", this.handleMouseUp);
- }
-
@action
- handleMouseDown = (e: React.PointerEvent): void => {
- if (e.button != 0 ||
- InkingControl.Instance.selectedTool === InkTool.None) {
+ onPointerDown = (e: React.PointerEvent): void => {
+ 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();
- let data = this.inkData;
- data.set(this._idGenerator,
- {
- pathData: [point],
+ document.addEventListener("pointermove", this.onPointerMove, true);
+ document.addEventListener("pointerup", this.onPointerUp, true);
+ e.stopPropagation();
+ e.preventDefault();
+
+ if (InkingControl.Instance.selectedTool != InkTool.Eraser) {
+ // start the new line, saves a uuid to represent the field of the stroke
+ this._currentStrokeId = Utils.GenerateGuid();
+ this.inkData.set(this._currentStrokeId, {
+ pathData: [this.relativeCoordinatesForEvent(e.clientX, e.clientY)],
color: InkingControl.Instance.selectedColor,
width: InkingControl.Instance.selectedWidth,
tool: InkingControl.Instance.selectedTool,
page: this.props.Document.GetNumber(KeyStore.CurPage, -1)
});
- this.inkData = data;
- this._isDrawing = true;
+ }
}
@action
- handleMouseMove = (e: React.PointerEvent): void => {
- if (!this._isDrawing ||
- InkingControl.Instance.selectedTool === InkTool.None) {
- return;
- }
- e.stopPropagation()
- if (InkingControl.Instance.selectedTool === InkTool.Eraser) {
- return
+ onPointerUp = (e: PointerEvent): void => {
+ document.removeEventListener("pointermove", this.onPointerMove, true);
+ document.removeEventListener("pointerup", this.onPointerUp, true);
+ let coord = this.relativeCoordinatesForEvent(e.clientX, e.clientY);
+ if (Math.abs(coord.x - this.inkMidX) > 500 || Math.abs(coord.y - this.inkMidY) > 500) {
+ this.inkMidX = coord.x;
+ this.inkMidY = coord.y;
}
- 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);
- }
-
- this.inkData = data;
+ e.stopPropagation();
+ e.preventDefault();
}
@action
- handleMouseUp = (e: MouseEvent): void => {
- this._isDrawing = false;
+ onPointerMove = (e: PointerEvent): void => {
+ e.stopPropagation()
+ e.preventDefault();
+ if (InkingControl.Instance.selectedTool != InkTool.Eraser) {
+ let data = this.inkData; // add points to new line as it is being drawn
+ let strokeData = data.get(this._currentStrokeId);
+ if (strokeData) {
+ strokeData.pathData.push(this.relativeCoordinatesForEvent(e.clientX, e.clientY));
+ data.set(this._currentStrokeId, strokeData);
+ }
+ this.inkData = data;
+ }
}
- relativeCoordinatesForEvent = (e: React.MouseEvent): { x: number, y: number } => {
- let [x, y] = this.props.getScreenTransform().transformPoint(e.clientX, e.clientY);
- x += InkingCanvas.InkOffset;
- y += InkingCanvas.InkOffset;
+ relativeCoordinatesForEvent = (ex: number, ey: number): { x: number, y: number } => {
+ let [x, y] = this.props.getScreenTransform().transformPoint(ex, ey);
return { x, y };
}
@@ -126,49 +117,35 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
this.inkData = data;
}
- 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 (<div className="inking-canvas" style={canvasStyle}
- // onMouseDown={this.handleMouseDown} onMouseMove={this.handleMouseMove} >
- // <svg>
- // </svg>
- // </div >)
- // }
-
- let lines = this.inkData;
-
- // parse data from server
- let paths: Array<JSX.Element> = []
+ @computed
+ get drawnPaths() {
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(<InkingStroke key={id} id={id}
- line={strokeData.pathData}
- color={strokeData.color}
- width={strokeData.width}
- tool={strokeData.tool}
- deleteCallback={this.removeLine} />)
- })
+ paths.push(<InkingStroke key={id} id={id} line={strokeData.pathData}
+ offsetX={this.maxCanvasDim - this.inkMidX}
+ offsetY={this.maxCanvasDim - this.inkMidY}
+ color={strokeData.color} width={strokeData.width}
+ tool={strokeData.tool} deleteCallback={this.removeLine} />)
+ return paths;
+ }, [] as JSX.Element[]);
+ return [<svg className={`inkingCanvas-paths-markers`} key="Markers"
+ style={{ left: `${this.inkMidX - this.maxCanvasDim}px`, top: `${this.inkMidY - this.maxCanvasDim}px` }} >
+ {paths.filter(path => path.props.tool == InkTool.Highlighter)}
+ </svg>,
+ <svg className={`inkingCanvas-paths-ink`} key="Pens"
+ style={{ left: `${this.inkMidX - this.maxCanvasDim}px`, top: `${this.inkMidY - this.maxCanvasDim}px` }}>
+ {paths.filter(path => path.props.tool != InkTool.Highlighter)}
+ </svg>];
+ }
+ render() {
+ let svgCanvasStyle = InkingControl.Instance.selectedTool != InkTool.None ? "canSelect" : "noSelect";
return (
-
- <div className="inking-canvas" style={canvasStyle}
- onPointerDown={this.handleMouseDown} onPointerMove={this.handleMouseMove} >
- <svg>
- {paths}
- </svg>
+ <div className="inkingCanvas" >
+ <div className={`inkingCanvas-${svgCanvasStyle}`} onPointerDown={this.onPointerDown} />
+ {this.props.children()}
+ {this.drawnPaths}
</div >
)
}
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/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 87b5c43d8..615f8af7e 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -6,6 +6,8 @@ import React = require("react");
interface StrokeProps {
+ offsetX: number;
+ offsetY: number;
id: string;
line: Array<{ x: number, y: number }>;
color: string;
@@ -21,21 +23,14 @@ export class InkingStroke extends React.Component<StrokeProps> {
@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 + this.props.offsetX) + " " + (p.y + this.props.offsetY)).join(" L ");
}
createStyle() {
@@ -50,15 +45,14 @@ export class InkingStroke extends React.Component<StrokeProps> {
}
}
-
render() {
let pathStyle = this.createStyle();
let pathData = this.parseData(this.props.line);
+ let pointerEvents: any = InkingControl.Instance.selectedTool == InkTool.Eraser ? "all" : "none";
return (
- <path className={(this._strokeTool === InkTool.Highlighter) ? "highlight" : ""}
- d={pathData} style={pathStyle} strokeLinejoin="round" strokeLinecap="round"
- onMouseOver={this.deleteStroke} onMouseDown={this.deleteStroke} />
+ <path d={pathData} style={{ ...pathStyle, pointerEvents: pointerEvents }} strokeLinejoin="round" strokeLinecap="round"
+ onPointerOver={this.deleteStroke} onPointerDown={this.deleteStroke} />
)
}
} \ No newline at end of file
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index 698a9c617..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/Main.tsx b/src/client/views/Main.tsx
index 53a5aba6e..6f66f8f38 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -8,6 +8,7 @@ import "./Main.scss";
import { MessageStore } from '../../server/Message';
import { Utils } from '../../Utils';
import * as request from 'request'
+import * as rp from 'request-promise'
import { Documents } from '../documents/Documents';
import { Server } from '../Server';
import { setupDrag } from '../util/DragManager';
@@ -22,7 +23,7 @@ import "./Main.scss";
import { observer } from 'mobx-react';
import { InkingControl } from './InkingControl';
import { RouteStore } from '../../server/RouteStore';
-import { library } from '@fortawesome/fontawesome-svg-core';
+import { library, IconName } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faFont } from '@fortawesome/free-solid-svg-icons';
import { faImage } from '@fortawesome/free-solid-svg-icons';
@@ -40,27 +41,40 @@ import Measure from 'react-measure';
import { DashUserModel } from '../../server/authentication/models/user_model';
import { ServerUtils } from '../../server/ServerUtil';
import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
-import { Field, Opt } from '../../fields/Field';
+import { Field, Opt, FieldWaiting } from '../../fields/Field';
import { ListField } from '../../fields/ListField';
import { Gateway, Settings } from '../northstar/manager/Gateway';
-import { Catalog, Schema, Attribute, AttributeGroup } from '../northstar/model/idea/idea';
+import { Catalog, Schema, Attribute, AttributeGroup, AggregateFunction } from '../northstar/model/idea/idea';
import { ArrayUtil } from '../northstar/utils/ArrayUtil';
import '../northstar/model/ModelExtensions'
import '../northstar/utils/Extensions'
+import { HistogramOperation } from '../northstar/operations/HistogramOperation';
+import { AttributeTransformationModel } from '../northstar/core/attribute/AttributeTransformationModel';
+import { ColumnAttributeModel } from '../northstar/core/attribute/AttributeModel';
@observer
export class Main extends React.Component {
// dummy initializations keep the compiler happy
- @observable private mainContainer?: Document;
@observable private mainfreeform?: Document;
- @observable private userWorkspaces: Document[] = [];
@observable public pwidth: number = 0;
@observable public pheight: number = 0;
- @observable ActiveSchema: Schema | undefined;
- private _northstarColumns: Document[] = [];
+ private _northstarSchemas: Document[] = [];
+
+ @computed private get mainContainer(): Document | undefined {
+ let doc = this.userDocument.GetT(KeyStore.ActiveWorkspace, Document);
+ return doc == FieldWaiting ? undefined : doc;
+ }
+
+ private set mainContainer(doc: Document | undefined) {
+ if (doc) {
+ this.userDocument.Set(KeyStore.ActiveWorkspace, doc);
+ }
+ }
+
+ private get userDocument(): Document {
+ return CurrentUserUtils.UserDocument;
+ }
- public mainDocId: string | undefined;
- private currentUser?: DashUserModel;
public static Instance: Main;
constructor(props: Readonly<{}>) {
@@ -70,12 +84,11 @@ export class Main extends React.Component {
configure({ enforceActions: "observed" });
if (window.location.pathname !== RouteStore.home) {
let pathname = window.location.pathname.split("/");
- this.mainDocId = pathname[pathname.length - 1];
+ if (pathname.length > 1 && pathname[pathname.length - 2] == 'doc') {
+ CurrentUserUtils.MainDocId = pathname[pathname.length - 1];
+ }
};
- let y = "";
- y.ReplaceAll("a", "B");
-
CurrentUserUtils.loadCurrentUser();
library.add(faFont);
@@ -92,7 +105,7 @@ export class Main extends React.Component {
library.add(faTree);
this.initEventListeners();
- Documents.initProtos(() => this.initAuthenticationRouters());
+ this.initAuthenticationRouters();
this.initializeNorthstar();
}
@@ -100,8 +113,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<Field>) => {
+ CurrentUserUtils.MainDocId = pathname[pathname.length - 1];
+ Server.GetField(CurrentUserUtils.MainDocId, action((field: Opt<Field>) => {
if (field instanceof Document) {
this.openWorkspace(field, true);
}
@@ -131,63 +144,50 @@ export class Main extends React.Component {
initAuthenticationRouters = () => {
// Load the user's active workspace, or create a new one if initial session after signup
- request.get(ServerUtils.prepend(RouteStore.getActiveWorkspace), (error, response, body) => {
- if (this.mainDocId || body) {
- Server.GetField(this.mainDocId || body, field => {
- if (field instanceof Document) {
- this.openWorkspace(field);
- this.populateWorkspaces();
- } else {
- this.createNewWorkspace(true, this.mainDocId);
- }
- });
- } else {
- this.createNewWorkspace(true, this.mainDocId);
- }
- });
- }
-
- @action
- createNewWorkspace = (init: boolean, id?: string): void => {
- let mainDoc = Documents.DockDocument(JSON.stringify({ content: [{ type: 'row', content: [] }] }), { title: `Main Container ${this.userWorkspaces.length + 1}` }, id);
- let newId = mainDoc.Id;
- request.post(ServerUtils.prepend(RouteStore.addWorkspace), {
- body: { target: newId },
- json: true
- }, () => { if (init) this.populateWorkspaces(); });
-
- // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
- setTimeout(() => {
- let freeformDoc = Documents.FreeformDocument([], { x: 0, y: 400, title: "mini collection" });
- var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(freeformDoc)] }] };
- mainDoc.SetText(KeyStore.Data, JSON.stringify(dockingLayout));
- mainDoc.Set(KeyStore.ActiveFrame, freeformDoc);
- this.openWorkspace(mainDoc);
- let pendingDocument = Documents.SchemaDocument([], { title: "New Mobile Uploads" })
- mainDoc.Set(KeyStore.OptionalRightCollection, pendingDocument);
- }, 0);
- this.userWorkspaces.push(mainDoc);
+ if (!CurrentUserUtils.MainDocId) {
+ this.userDocument.GetTAsync(KeyStore.ActiveWorkspace, Document).then(doc => {
+ if (doc) {
+ CurrentUserUtils.MainDocId = doc.Id;
+ this.openWorkspace(doc);
+ } else {
+ this.createNewWorkspace();
+ }
+ })
+ } else {
+ Server.GetField(CurrentUserUtils.MainDocId).then(field => {
+ if (field instanceof Document) {
+ this.openWorkspace(field)
+ } else {
+ this.createNewWorkspace(CurrentUserUtils.MainDocId);
+ }
+ })
+ }
}
@action
- populateWorkspaces = () => {
- // retrieve all workspace documents from the server
- request.get(ServerUtils.prepend(RouteStore.getAllWorkspaces), (error, res, body) => {
- let ids = JSON.parse(body) as string[];
- Server.GetFields(ids, action((fields: { [id: string]: Field }) => this.userWorkspaces = ids.map(id => fields[id] as Document)));
- });
+ createNewWorkspace = (id?: string): void => {
+ this.userDocument.GetTAsync<ListField<Document>>(KeyStore.Workspaces, ListField).then(action((list: Opt<ListField<Document>>) => {
+ if (list) {
+ let freeformDoc = Documents.FreeformDocument([], { x: 0, y: 400, title: "mini collection" });
+ var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(freeformDoc)] }] };
+ let mainDoc = Documents.DockDocument(JSON.stringify(dockingLayout), { title: `Main Container ${list.Data.length + 1}` }, id);
+ list.Data.push(mainDoc);
+ CurrentUserUtils.MainDocId = mainDoc.Id;
+ // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
+ setTimeout(() => {
+ this.openWorkspace(mainDoc);
+ let pendingDocument = Documents.SchemaDocument([], { title: "New Mobile Uploads" })
+ mainDoc.Set(KeyStore.OptionalRightCollection, pendingDocument);
+ }, 0);
+ }
+ }));
}
@action
openWorkspace = (doc: Document, fromHistory = false): void => {
- request.post(ServerUtils.prepend(RouteStore.setActiveWorkspace), {
- body: { target: doc.Id },
- json: true
- });
this.mainContainer = doc;
fromHistory || window.history.pushState(null, doc.Title, "/doc/" + doc.Id);
- this.mainContainer.GetTAsync(KeyStore.ActiveFrame, Document, field => this.mainfreeform = field);
- this.mainContainer.GetTAsync(KeyStore.OptionalRightCollection, Document, col => {
+ this.userDocument.GetTAsync(KeyStore.OptionalRightCollection, Document).then(col => {
// if there is a pending doc, and it has new data, show it (syip: we use a timeout to prevent collection docking view from being uninitialized)
setTimeout(() => {
if (col) {
@@ -201,10 +201,15 @@ export class Main extends React.Component {
});
}
+ @observable
+ workspacesShown: boolean = false;
+
+ areWorkspacesShown = () => {
+ return this.workspacesShown;
+ }
+ @action
toggleWorkspaces = () => {
- if (WorkspacesMenu.Instance) {
- WorkspacesMenu.Instance.toggle()
- }
+ this.workspacesShown = !this.workspacesShown;
}
screenToLocalTransform = () => Transform.Identity
@@ -213,7 +218,8 @@ export class Main extends React.Component {
focusDocument = (doc: Document) => { }
noScaling = () => 1;
- get content() {
+ @computed
+ get mainContent() {
return !this.mainContainer ? (null) :
<DocumentView Document={this.mainContainer}
AddDocument={undefined}
@@ -240,14 +246,14 @@ 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._northstarColumns, { width: 200, height: 200, title: "a tree collection" }));
+ 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" }));
let addWebNode = action(() => Documents.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" }));
let addAudioNode = action(() => Documents.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" }))
- let btns = [
+ let btns: [React.RefObject<HTMLDivElement>, IconName, string, () => Document][] = [
[React.createRef<HTMLDivElement>(), "font", "Add Textbox", addTextNode],
[React.createRef<HTMLDivElement>(), "image", "Add Image", addImageNode],
[React.createRef<HTMLDivElement>(), "file-pdf", "Add PDF", addPDFNode],
@@ -259,8 +265,6 @@ export class Main extends React.Component {
[React.createRef<HTMLDivElement>(), "table", "Add Schema", addSchemaNode],
]
- let addClick = (creator: () => Document) => action(() => this.mainfreeform!.GetList<Document>(KeyStore.Data, []).push(creator()));
-
return < div id="add-nodes-menu" >
<input type="checkbox" id="add-menu-toggle" />
<label htmlFor="add-menu-toggle" title="Add Node"><p>+</p></label>
@@ -268,9 +272,9 @@ export class Main extends React.Component {
<div id="add-options-content">
<ul id="add-options-list">
{btns.map(btn =>
- <li key={btn[1] as string} ><div ref={btn[0] as React.RefObject<HTMLDivElement>}>
- <button className="round-button add-button" title={btn[2] as string} onPointerDown={setupDrag(btn[0] as React.RefObject<HTMLDivElement>, btn[3] as any)} onClick={addClick(btn[3] as any)}>
- <FontAwesomeIcon icon={btn[1] as any} size="sm" />
+ <li key={btn[1]} ><div ref={btn[0]}>
+ <button className="round-button add-button" title={btn[2]} onPointerDown={setupDrag(btn[0], btn[3])}>
+ <FontAwesomeIcon icon={btn[1]} size="sm" />
</button>
</div></li>)}
</ul>
@@ -300,23 +304,29 @@ export class Main extends React.Component {
}
render() {
+ let workspaceMenu: any = null;
+ let workspaces = this.userDocument.GetT<ListField<Document>>(KeyStore.Workspaces, ListField);
+ if (workspaces && workspaces !== FieldWaiting) {
+ workspaceMenu = <WorkspacesMenu active={this.mainContainer} open={this.openWorkspace} new={this.createNewWorkspace} allWorkspaces={workspaces.Data}
+ isShown={this.areWorkspacesShown} toggle={this.toggleWorkspaces} />
+ }
return (
<div id="main-div">
+ <DocumentDecorations />
<Measure onResize={(r: any) => runInAction(() => {
this.pwidth = r.entry.width;
this.pheight = r.entry.height;
})}>
{({ measureRef }) =>
<div ref={measureRef} id="mainContent-div">
- {this.content}
+ {this.mainContent}
</div>
}
</Measure>
- <DocumentDecorations />
<ContextMenu />
{this.nodesMenu}
{this.miscButtons}
- <WorkspacesMenu active={this.mainContainer} open={this.openWorkspace} new={this.createNewWorkspace} allWorkspaces={this.userWorkspaces} />
+ {workspaceMenu}
<InkingControl />
</div>
);
@@ -325,9 +335,27 @@ export class Main extends React.Component {
// --------------- Northstar hooks ------------- /
@action SetNorthstarCatalog(ctlog: Catalog) {
+ CurrentUserUtils.NorthstarDBCatalog = ctlog;
if (ctlog && ctlog.schemas) {
- this.ActiveSchema = ArrayUtil.FirstOrDefault<Schema>(ctlog.schemas!, (s: Schema) => s.displayName === "mimic");
- this._northstarColumns = this.GetAllNorthstarColumnAttributes().map(a => Documents.HistogramDocument({ width: 200, height: 200, title: a.displayName! }));
+ this._northstarSchemas = ctlog.schemas.map(schema => {
+ let schemaDoc = Documents.TreeDocument([], { width: 50, height: 100, title: schema.displayName! });
+ let schemaDocuments = schemaDoc.GetList(KeyStore.Data, [] as Document[]);
+ CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => {
+ Server.GetField(attr.displayName! + ".alias", action((field: Opt<Field>) => {
+ if (field instanceof Document) {
+ schemaDocuments.push(field);
+ } else {
+ var atmod = new ColumnAttributeModel(attr);
+ let histoOp = new HistogramOperation(schema!.displayName!,
+ new AttributeTransformationModel(atmod, AggregateFunction.None),
+ new AttributeTransformationModel(atmod, AggregateFunction.Count),
+ new AttributeTransformationModel(atmod, AggregateFunction.Count));
+ schemaDocuments.push(Documents.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName! }, undefined, attr.displayName! + ".alias"));
+ }
+ }));
+ });
+ return schemaDoc;
+ })
}
}
async initializeNorthstar(): Promise<void> {
@@ -342,22 +370,10 @@ export class Main extends React.Component {
let cat = Gateway.Instance.ClearCatalog();
cat.then(async () => this.SetNorthstarCatalog(await Gateway.Instance.GetCatalog()));
}
- public GetAllNorthstarColumnAttributes() {
- if (!this.ActiveSchema || !this.ActiveSchema.rootAttributeGroup) {
- return [];
- }
- const recurs = (attrs: Attribute[], g: AttributeGroup) => {
- if (g.attributes) {
- attrs.push.apply(attrs, g.attributes);
- if (g.attributeGroups) {
- g.attributeGroups.forEach(ng => recurs(attrs, ng));
- }
- }
- };
- const allAttributes: Attribute[] = new Array<Attribute>();
- recurs(allAttributes, this.ActiveSchema.rootAttributeGroup);
- return allAttributes;
- }
}
-ReactDOM.render(<Main />, document.getElementById('root'));
+Documents.initProtos().then(() => {
+ return CurrentUserUtils.loadCurrentUser()
+}).then(() => {
+ ReactDOM.render(<Main />, document.getElementById('root'));
+});
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/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index fd0810242..39e0dd989 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -17,6 +17,8 @@ import { COLLECTION_BORDER_WIDTH } from "./CollectionView";
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<SubCollectionViewProps> {
@@ -45,9 +47,10 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
(window as any).React = React;
(window as any).ReactDOM = ReactDOM;
}
- public StartOtherDrag(dragDoc: Document, e: any) {
- this.AddRightSplit(dragDoc, true).contentItems[0].tab._dragListener.
- onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: () => { }, 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
@@ -190,6 +193,22 @@ 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)) {
+ 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<Field>) => {
+ if (f instanceof Document)
+ DragManager.StartDocumentDrag([tab], new DragManager.DocumentDragData([f as Document]),
+ {
+ handlers: {
+ dragComplete: action(() => { }),
+ },
+ hideSource: false
+ })
+ }));
+ }
if (className == "lm_drag_handle" || className == "lm_close" || className == "lm_maximise" || className == "lm_minimise" || className == "lm_close_tab") {
this._flush = true;
}
@@ -208,6 +227,21 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
this.stateChanged();
}
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;
+ }
tab.closeElement.off('click') //unbind the current click handler
.click(function () {
tab.contentItem.remove();
diff --git a/src/client/views/collections/CollectionPDFView.scss b/src/client/views/collections/CollectionPDFView.scss
index 0144625c1..0eca3f1cd 100644
--- a/src/client/views/collections/CollectionPDFView.scss
+++ b/src/client/views/collections/CollectionPDFView.scss
@@ -9,6 +9,8 @@
width: 100%;
height: 100%;
position: absolute;
+ top: 0;
+ left:0;
}
.collectionPdfView-backward {
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index 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<CollectionViewProps> {
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/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 34b019244..0ff6c3b40 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
@@ -85,12 +86,31 @@ export class CollectionSchemaView extends CollectionViewBase {
)
let reference = React.createRef<HTMLDivElement>();
let onItemDown = setupDrag(reference, () => props.doc, (containingCollection: CollectionView) => this.props.removeDocument(props.doc));
+ let applyToDoc = (doc: Document, value: string) => {
+ let script = CompileScript(value, { this: doc }, true);
+ if (!script.compiled) {
+ return false;
+ }
+ let field = script();
+ 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 (
- <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} style={{ height: "36px" }} key={props.doc.Id} ref={reference}>
+ <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} style={{ height: "56px" }} key={props.doc.Id} ref={reference}>
<EditableView
display={"inline"}
contents={contents}
- height={36} GetValue={() => {
+ height={56}
+ GetValue={() => {
let field = props.doc.Get(props.fieldKey);
if (field && field instanceof Field) {
return field.ToScriptString();
@@ -98,22 +118,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<ListField<Document>>(this.props.fieldKey, ListField).then((val) => {
+ if (val) {
+ val.Data.forEach(doc => applyToDoc(doc, value));
}
- }
- return false;
+ })
}}>
</EditableView>
</div>
@@ -238,23 +250,52 @@ export class CollectionSchemaView extends CollectionViewBase {
}
}
+ @action
+ addColumn = () => {
+ this.columns.push(new Key(this.newKeyName));
+ this.newKeyName = "";
+ }
+
+ @observable
+ newKeyName: string = "";
+
+ @action
+ newKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this.newKeyName = e.currentTarget.value;
+ }
+ onWheel = (e: React.WheelEvent): void => {
+ if (this.props.active())
+ e.stopPropagation();
+ }
+
@observable _optionsActivated: number = 0;
@action
OptionsMenuDown = (e: React.PointerEvent) => {
this._optionsActivated++;
}
+
+ @observable previewScript: string = "this";
+ @action
+ onPreviewScriptChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this.previewScript = e.currentTarget.value;
+ }
+
render() {
library.add(faCog);
+ library.add(faPlus);
const columns = this.columns;
const children = this.props.Document.GetList<Document>(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) : (
<Measure onResize={this.setScaling}>
{({ measureRef }) =>
<div className="collectionSchemaView-content" ref={measureRef}>
- <DocumentView Document={selected}
+ {doc instanceof Document ? <DocumentView Document={doc}
AddDocument={this.props.addDocument} RemoveDocument={this.props.removeDocument}
isTopMost={false}
SelectOnLoad={false}
@@ -264,7 +305,9 @@ export class CollectionSchemaView extends CollectionViewBase {
PanelHeight={this.getPanelHeight}
ContainingCollectionView={this.props.CollectionView}
focus={this.focusDocument}
- />
+ /> : null}
+ <input value={this.previewScript} onChange={this.onPreviewScriptChange}
+ style={{ position: 'absolute', bottom: '0px' }} />
</div>
}
</Measure>
@@ -286,6 +329,8 @@ export class CollectionSchemaView extends CollectionViewBase {
return (<KeyToggle checked={allKeys[item]} key={item} keyId={item} toggle={this.toggleKey} />)
})}
</ul>
+ <input value={this.newKeyName} onChange={this.newKeyChange} />
+ <button onClick={this.addColumn}><FontAwesomeIcon style={{ color: "white" }} icon="plus" size="lg" /></button>
</div>
</div>
}>
@@ -293,7 +338,7 @@ export class CollectionSchemaView extends CollectionViewBase {
</Flyout>);
return (
- <div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} ref={this._mainCont} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} >
+ <div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} onWheel={this.onWheel} ref={this._mainCont} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} >
<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/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 (
- <div id="body" className="collectionTreeView-dropTarget" 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} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}>
<div className="coll-title">
<EditableView
contents={this.props.Document.Title}
diff --git a/src/client/views/collections/CollectionVideoView.scss b/src/client/views/collections/CollectionVideoView.scss
index cbb981b13..ed56ad268 100644
--- a/src/client/views/collections/CollectionVideoView.scss
+++ b/src/client/views/collections/CollectionVideoView.scss
@@ -3,6 +3,8 @@
width: 100%;
height: 100%;
position: absolute;
+ top: 0;
+ left:0;
}
.collectionVideoView-time{
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index 05f759967..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<CollectionViewProps> {
+ 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<HTMLDivElement>();
-
private get uIButtons() {
let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().transformDirection(1, 1)[0]);
return ([
<div className="collectionVideoView-time" key="time" onPointerDown={this.onResetDown} style={{ transform: `scale(${scaling}, ${scaling})` }}>
- <span>{"" + Math.round(this.ctime)}</span>
- <span style={{ fontSize: 8 }}>{" " + Math.round((this.ctime - Math.trunc(this.ctime)) * 100)}</span>
+ <span>{"" + Math.round(this._currentTimecode)}</span>
+ <span style={{ fontSize: 8 }}>{" " + Math.round((this._currentTimecode - Math.trunc(this._currentTimecode)) * 100)}</span>
</div>,
<div className="collectionVideoView-play" key="play" onPointerDown={this.onPlayDown} style={{ transform: `scale(${scaling}, ${scaling})` }}>
- {this.playing ? "\"" : ">"}
+ {this._isPlaying ? "\"" : ">"}
</div>,
<div className="collectionVideoView-full" key="full" onPointerDown={this.onFullDown} style={{ transform: `scale(${scaling}, ${scaling})` }}>
F
@@ -37,64 +39,54 @@ export class CollectionVideoView extends React.Component<CollectionViewProps> {
]);
}
-
- // "inherited" CollectionView API starts here...
-
- @observable
- public SelectedDocs: FieldId[] = []
- public active: () => boolean = () => CollectionView.Active(this);
-
- addDocument = (doc: Document, allowDuplicates: boolean): void => { 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<CollectionViewProps> {
@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 (<div className="collectionVideoView-cont" ref={this._mainCont} onContextMenu={this.specificContextMenu}>
+ trace();
+ return (<div className="collectionVideoView-cont" ref={this.mainCont} onContextMenu={this.specificContextMenu}>
{this.subView}
{this.props.isSelected() ? this.uIButtons : (null)}
</div>)
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 7e1d31018..014aa1d8f 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -7,13 +7,13 @@ 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";
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,
@@ -37,7 +37,7 @@ export class CollectionView extends React.Component<CollectionViewProps> {
@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,49 @@ export class CollectionView extends React.Component<CollectionViewProps> {
return isSelected || childSelected || topMost;
}
+ static createsCycle(documentToAdd: Document, containerDocument: Document): boolean {
+ let data = documentToAdd.GetList<Document>(KeyStore.Data, []);
+ for (let i = 0; i < data.length; i++) {
+ if (CollectionView.createsCycle(data[i], containerDocument))
+ return true;
+ }
+ let annots = documentToAdd.GetList<Document>(KeyStore.Annotations, []);
+ for (let i = 0; i < annots.length; i++) {
+ if (CollectionView.createsCycle(annots[i], containerDocument))
+ return true;
+ }
+ for (let containerProto: any = containerDocument; containerProto && containerProto != FieldWaiting; containerProto = containerProto.GetPrototype()) {
+ if (containerProto.Id == documentToAdd.Id)
+ return true;
+ }
+ return false;
+ }
+
@action
- public static AddDocument(props: CollectionViewProps, doc: Document, allowDuplicates: boolean) {
- doc.SetNumber(KeyStore.Page, props.Document.GetNumber(KeyStore.CurPage, -1));
+ public static AddDocument(props: CollectionViewProps, doc: Document, allowDuplicates: boolean): boolean {
+ var curPage = props.Document.GetNumber(KeyStore.CurPage, -1);
+ doc.SetOnPrototype(KeyStore.Page, new NumberField(curPage));
+ if (curPage >= 0) {
+ doc.SetOnPrototype(KeyStore.AnnotationOn, props.Document);
+ }
if (props.Document.Get(props.fieldKey) instanceof Field) {
//TODO This won't create the field if it doesn't already exist
const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>())
- if (!value.some(v => v.Id == doc.Id) || allowDuplicates)
- value.push(doc);
+ 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;
}
@action
@@ -72,11 +104,16 @@ export class CollectionView extends React.Component<CollectionViewProps> {
break;
}
}
+ doc.GetTAsync(KeyStore.AnnotationOn, Document).then((annotationOn) => {
+ if (annotationOn == props.Document) {
+ doc.Set(KeyStore.AnnotationOn, undefined, true);
+ }
+ })
if (index !== -1) {
value.splice(index, 1)
- SelectionManager.DeselectAll()
+ //SelectionManager.DeselectAll()
ContextMenu.Instance.clearItems()
return true;
}
@@ -96,7 +133,7 @@ export class CollectionView extends React.Component<CollectionViewProps> {
}
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/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx
index f33007196..458bae7ab 100644
--- a/src/client/views/collections/CollectionViewBase.tsx
+++ b/src/client/views/collections/CollectionViewBase.tsx
@@ -14,9 +14,11 @@ import { RouteStore } from "../../../server/RouteStore";
import { TupleField } from "../../../fields/TupleField";
import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
import { NumberField } from "../../../fields/NumberField";
-import { DocumentManager } from "../../util/DocumentManager";
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;
@@ -33,7 +35,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;
}
@@ -59,41 +61,58 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
let email = CurrentUserUtils.email;
if (id && email) {
let textInfo: [string, string] = [id, email];
- doc.GetOrCreateAsync<ListField<CursorEntry>>(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<ListField<CursorEntry>>(KeyStore.Cursors, ListField, action((field: ListField<CursorEntry>) => {
+ 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);
+ }
+ }))
})
-
-
}
}
- protected getCursors(): CursorEntry[] {
- let doc = this.props.Document;
- let id = CurrentUserUtils.id;
- let cursors = doc.GetList<CursorEntry>(KeyStore.Cursors, []);
- let notMe = cursors.filter(entry => entry.Data[0][0] !== id);
- return id ? notMe : [];
- }
-
@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<NumberField>) => f ? de.data.droppedDocument.SetNumber(key, f.Data) : null));
- } else if (de.data.removeDocument) {
+ de.data.draggedDocuments.map((draggedDocument: Document, i: number) =>
+ draggedDocument.GetTAsync(key, NumberField, (f: Opt<NumberField>) => f ? de.data.droppedDocuments[i].SetNumber(key, f.Data) : null)));
+ }
+ let added = de.data.droppedDocuments.reduce((added, d) => this.props.addDocument(d, false), true);
+ if (added && de.data.removeDocument && !de.data.aliasOnDrop) {
de.data.removeDocument(this.props.CollectionView);
}
- this.props.addDocument(de.data.droppedDocument, false);
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;
}
protected getDocumentFromType(type: string, path: string, options: DocumentOptions): Opt<Document> {
@@ -109,10 +128,26 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
}
if (type.indexOf("pdf") !== -1) {
ctor = Documents.PdfDocument;
+ options.nativeWidth = 1200;
}
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, };
+ options = { height: options.width, ...options, title: path };
}
return ctor ? ctor(path, options) : undefined;
}
@@ -130,7 +165,7 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
e.stopPropagation()
e.preventDefault()
- if (html && html.indexOf("<img") != 0) {
+ if (html && html.indexOf("<img") != 0 && !html.startsWith("<a")) {
console.log("not good");
let htmlDoc = Documents.HtmlDocument(html, { ...options, width: 300, height: 300 });
htmlDoc.SetText(KeyStore.DocumentText, text);
@@ -143,7 +178,6 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
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/MarqueeView.tsx b/src/client/views/collections/MarqueeView.tsx
deleted file mode 100644
index 8c2f3443c..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) => void;
- activeDocuments: () => Document[];
- selectDocuments: (docs: Document[]) => void;
- removeDocument: (doc: Document) => boolean;
-}
-
-@observer
-export class MarqueeView extends React.Component<MarqueeViewProps>
-{
- private _reactionDisposer: Opt<IReactionDisposer>;
-
- @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) : <div className="marqueeView" style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}` }} />);
- }
-} \ 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 cbf36cf9e..000000000
--- a/src/client/views/collections/PreviewCursor.tsx
+++ /dev/null
@@ -1,73 +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<PreviewCursorProps> {
- private _reactionDisposer: Opt<IReactionDisposer>;
-
- @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, true);
- }
-
- @action
- onCursorPlaced = (visible: boolean, downX: number, downY: number): void => {
- if (visible) {
- document.addEventListener("keypress", this.onKeyPress, true);
- this._lastX = downX;
- this._lastY = downY;
- } else
- this.cleanupInteractions();
- }
-
- @action
- onKeyPress = (e: KeyboardEvent) => {
- //if not these keys, make a textbox if preview cursor is active!
- if (!e.ctrlKey && !e.altKey && !e.defaultPrevented) {
- //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" });
- 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) :
- <div className="previewCursor" id="previewCursor" style={{ transform: `translate(${x}px, ${y}px)` }}>I</div>)
-
- }
-} \ 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..3dfd74ec8
--- /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<CollectionFreeFormLinkViewProps> {
+
+ 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.GetBoolean(KeyStore.Minimized, false) ? 5 : a.GetNumber(KeyStore.Width, 0) / 2);
+ let y1 = a.GetNumber(KeyStore.Y, 0) + (a.GetBoolean(KeyStore.Minimized, false) ? 5 : a.GetNumber(KeyStore.Height, 0) / 2);
+ let x2 = b.GetNumber(KeyStore.X, 0) + (b.GetBoolean(KeyStore.Minimized, false) ? 5 : b.GetNumber(KeyStore.Width, 0) / 2);
+ let y2 = b.GetNumber(KeyStore.Y, 0) + (b.GetBoolean(KeyStore.Minimized, false) ? 5 : b.GetNumber(KeyStore.Height, 0) / 2);
+ return (
+ <line key={Utils.GenerateGuid()} className="collectionfreeformlinkview-linkLine" onPointerDown={this.onPointerDown}
+ style={{ strokeWidth: `${l.length * 5}` }}
+ x1={`${x1}`} y1={`${y1}`}
+ x2={`${x2}`} y2={`${y2}`} />
+ )
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
new file mode 100644
index 000000000..30e158603
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
@@ -0,0 +1,12 @@
+.collectionfreeformlinksview-svgCanvas{
+ transform: translate(-10000px,-10000px);
+ position: absolute;
+ top: 0;
+ left: 0;
+ 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
new file mode 100644
index 000000000..eb20b3100
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -0,0 +1,106 @@
+import { computed, reaction, runInAction, trace } from "mobx";
+import { observer } from "mobx-react";
+import { Document } from "../../../../fields/Document";
+import { FieldWaiting } from "../../../../fields/Field";
+import { KeyStore } from "../../../../fields/KeyStore";
+import { ListField } from "../../../../fields/ListField";
+import { Utils } from "../../../../Utils";
+import { DocumentManager } from "../../../util/DocumentManager";
+import { DocumentView } from "../../nodes/DocumentView";
+import { CollectionViewProps } from "../CollectionViewBase";
+import "./CollectionFreeFormLinksView.scss";
+import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView";
+import React = require("react");
+import v5 = require("uuid/v5");
+
+@observer
+export class CollectionFreeFormLinksView extends React.Component<CollectionViewProps> {
+
+ componentDidMount() {
+ reaction(() => {
+ return DocumentManager.Instance.getAllDocumentViews(this.props.Document).map(dv => dv.props.Document.GetNumber(KeyStore.X, 0))
+ }, () => {
+ let views = DocumentManager.Instance.getAllDocumentViews(this.props.Document);
+ for (let i = 0; i < views.length; i++) {
+ for (let j = i + 1; j < views.length; j++) {
+ let srcDoc = views[j].props.Document;
+ let dstDoc = views[i].props.Document;
+ let x1 = srcDoc.GetNumber(KeyStore.X, 0);
+ let x1w = srcDoc.GetNumber(KeyStore.Width, -1);
+ let x2 = dstDoc.GetNumber(KeyStore.X, 0);
+ let x2w = dstDoc.GetNumber(KeyStore.Width, -1);
+ if (x1w < 0 || x2w < 0)
+ continue;
+ dstDoc.GetTAsync(KeyStore.Prototype, Document).then((protoDest) =>
+ srcDoc.GetTAsync(KeyStore.Prototype, Document).then((protoSrc) => runInAction(() => {
+ let dstTarg = (protoDest ? protoDest : dstDoc);
+ let srcTarg = (protoSrc ? protoSrc : srcDoc);
+ let findBrush = (field: ListField<Document>) => field.Data.findIndex(brush => {
+ let bdocs = brush.GetList(KeyStore.BrushingDocs, [] as Document[]);
+ return (bdocs.length == 0 || (bdocs[0] == dstTarg && bdocs[1] == srcTarg) || (bdocs[0] == srcTarg && bdocs[1] == dstTarg))
+ });
+ let brushAction = (field: ListField<Document>) => {
+ let found = findBrush(field);
+ if (found != -1)
+ field.Data.splice(found, 1);
+ };
+ if (Math.abs(x1 + x1w - x2) < 20 || Math.abs(x2 + x2w - x1) < 20) {
+ let linkDoc: Document = new Document();
+ linkDoc.SetText(KeyStore.Title, "Histogram Brush");
+ linkDoc.SetText(KeyStore.LinkDescription, "Brush between " + srcTarg.Title + " and " + dstTarg.Title);
+ linkDoc.SetData(KeyStore.BrushingDocs, [dstTarg, srcTarg], ListField);
+
+ brushAction = brushAction = (field: ListField<Document>) => (findBrush(field) == -1) && field.Data.push(linkDoc);
+ }
+ dstTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, brushAction);
+ srcTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, brushAction);
+ }
+ )))
+ }
+ }
+ })
+ }
+ 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() {
+ 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, }[] = [];
+ 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[] }[]);
+ return connections.map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />);
+ }
+
+ render() {
+ return (
+ <div className="collectionfreeformlinksview-container">
+ <svg className="collectionfreeformlinksview-svgCanvas">
+ {this.uniqueConnections}
+ </svg>
+ {this.props.children}
+ </div>
+ );
+ }
+} \ No newline at end of file
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<CollectionViewProps> {
+ protected getCursors(): CursorEntry[] {
+ let doc = this.props.Document;
+ let id = CurrentUserUtils.id;
+ let cursors = doc.GetList<CursorEntry>(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 (
+ <div
+ key={id}
+ style={{
+ position: "absolute",
+ transform: `translate(${point[0] - 10}px, ${point[1] - 10}px)`,
+ zIndex: 10000,
+ transformOrigin: 'center center',
+ }}
+ >
+ <canvas
+ ref={(el) => { if (el) this.crosshairs = el }}
+ width={20}
+ height={20}
+ style={{
+ position: 'absolute',
+ width: "20px",
+ height: "20px",
+ opacity: 0.5,
+ borderRadius: "50%",
+ border: "2px solid black"
+ }}
+ />
+ <p
+ style={{
+ fontSize: 14,
+ color: "black",
+ // fontStyle: "italic",
+ marginLeft: -12,
+ marginTop: 4
+ }}
+ >{email[0].toUpperCase()}</p>
+ </div>
+ );
+ }
+ })
+ }
+
+ render() {
+ return this.sharedCursors;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index bdc597a25..79d520069 100644
--- a/src/client/views/collections/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -1,5 +1,13 @@
-@import "../global_variables";
+@import "../../global_variables";
+.collectionfreeformview {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ transform-origin: left top;
+}
.collectionfreeformview-container {
.collectionfreeformview > .jsx-parser {
position: absolute;
@@ -7,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),
@@ -21,22 +26,12 @@
border: 0px solid $light-color-secondary;
border-radius: $border-radius;
box-sizing: border-box;
- position: relative;
+ position: absolute;
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 {
@@ -46,30 +41,18 @@
.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;
+ position:absolute;
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;
}
diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index da9f7b392..1ddb84a99 100644
--- a/src/client/views/collections/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,24 +1,26 @@
-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";
-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 { 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 } from "../CollectionViewBase";
+import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";
import "./CollectionFreeFormView.scss";
-import { COLLECTION_BORDER_WIDTH } from "./CollectionView";
-import { CollectionViewBase } from "./CollectionViewBase";
import { MarqueeView } from "./MarqueeView";
-import { PreviewCursor } from "./PreviewCursor";
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 {
@@ -26,12 +28,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[]) => {
@@ -41,23 +46,15 @@ export class CollectionFreeFormView extends CollectionViewBase {
public getActiveDocuments = () => {
var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
- const lvalue = this.props.Document.GetT<ListField<Document>>(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;
@@ -66,7 +63,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); }
@@ -76,15 +73,36 @@ 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);
+ if (super.drop(e, de)) {
+ 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.droppedDocuments[0];
+ let dragX = dragDoc.GetNumber(KeyStore.X, 0);
+ let dragY = dragDoc.GetNumber(KeyStore.Y, 0);
+ droppedDocs.map(async d => {
+ let docX = d.GetNumber(KeyStore.X, 0);
+ let docY = d.GetNumber(KeyStore.Y, 0);
+ d.SetNumber(KeyStore.X, x + (docX - dragX));
+ d.SetNumber(KeyStore.Y, y + (docY - dragY));
+ let docW = await d.GetTAsync(KeyStore.Width, NumberField);
+ let docH = await d.GetTAsync(KeyStore.Height, NumberField);
+ if (!docW) {
+ d.SetNumber(KeyStore.Width, 300);
+ }
+ if (!docH) {
+ d.SetNumber(KeyStore.Height, 300);
+ }
+ this.bringToFront(d);
+ })
+ }
+ return true;
}
+ return false;
}
@@ -92,19 +110,19 @@ 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) {
+ 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();
}
}
@@ -112,28 +130,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);
@@ -150,7 +153,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) {
@@ -256,150 +258,62 @@ export class CollectionFreeFormView extends CollectionViewBase {
@computed
get views() {
var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
- const lvalue = this.props.Document.GetT<ListField<Document>>(this.props.fieldKey, ListField);
- if (lvalue && lvalue != FieldWaiting) {
- return lvalue.Data.map(doc => {
- var page = doc.GetNumber(KeyStore.Page, 0);
- return (page != curPage && page != 0) ? (null) :
- (<CollectionFreeFormDocumentView key={doc.Id} {...this.getDocumentViewProps(doc)} />);
- })
- }
- 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(<CollectionFreeFormDocumentView key={doc.Id} {...this.getDocumentViewProps(doc)} />);
+ return prev;
+ }, [] as JSX.Element[])
}
@computed
get backgroundView() {
return !this.backgroundLayout ? (null) :
(<DocumentContentsView {...this.getDocumentViewProps(this.props.Document)}
- layoutKey={KeyStore.BackgroundLayout} isSelected={() => false} select={() => { }} />);
+ layoutKey={KeyStore.BackgroundLayout} isTopMost={this.props.isTopMost} isSelected={() => false} select={() => { }} />);
}
@computed
get overlayView() {
return !this.overlayLayout ? (null) :
(<DocumentContentsView {...this.getDocumentViewProps(this.props.Document)}
- layoutKey={KeyStore.OverlayLayout} isSelected={() => 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())
- 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) => {
- 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 (
<div className={`collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`}
- onPointerDown={this.onPointerDown}
- onPointerMove={(e) => 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}>
- <div className="collectionfreeformview"
- style={{ transformOrigin: "left top", transform: `translate(${dx}px, ${dy}px) scale(${this.zoomScaling}, ${this.zoomScaling}) translate(${panx}px, ${pany}px)` }}
- ref={this._canvasRef}>
- {this.backgroundView}
- <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} />
- <PreviewCursor container={this} addLiveTextDocument={this.addLiveTextBox} getTransform={this.getTransform} />
- {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 (
- <div
- key={id}
- style={{
- position: "absolute",
- transform: `translate(${point[0] - 10}px, ${point[1] - 10}px)`,
- zIndex: 10000,
- transformOrigin: 'center center',
- }}
- >
- <canvas
- ref={(el) => { if (el) this.crosshairs = el }}
- width={20}
- height={20}
- style={{
- position: 'absolute',
- width: "20px",
- height: "20px",
- opacity: 0.5,
- borderRadius: "50%",
- border: "2px solid black"
- }}
- />
- <p
- style={{
- fontSize: 14,
- color: "black",
- // fontStyle: "italic",
- marginLeft: -12,
- marginTop: 4
- }}
- >{email[0].toUpperCase()}</p>
- </div>
- );
- }
- })}
- </div>
+ onPointerDown={this.onPointerDown} onPointerMove={(e) => super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY))}
+ onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} onWheel={this.onPointerWheel}
+ style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} ref={this.createDropTarget}>
<MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments}
- addDocument={this.props.addDocument} removeDocument={this.props.removeDocument}
- getMarqueeTransform={this.getMarqueeTransform} getTransform={this.getTransform} />
- {this.overlayView}
-
+ addDocument={this.addDocument} removeDocument={this.props.removeDocument}
+ getContainerTransform={this.getContainerTransform} getTransform={this.getTransform}>
+ <PreviewCursor container={this} addLiveTextDocument={this.addLiveTextBox}
+ getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} >
+ <div className="collectionfreeformview" ref={this._canvasRef}
+ style={{ transform: `translate(${dx}px, ${dy}px) scale(${this.zoomScaling}, ${this.zoomScaling}) translate(${panx}px, ${pany}px)` }}>
+ {this.backgroundView}
+ <CollectionFreeFormLinksView {...this.props}>
+ <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} >
+ {this.childViews}
+ </InkingCanvas>
+ </CollectionFreeFormLinksView>
+ <CollectionFreeFormRemoteCursors {...this.props} />
+ </div>
+ {this.overlayView}
+ </PreviewCursor>
+ </MarqueeView>
</div>
);
}
diff --git a/src/client/views/collections/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
index 6d9a79344..0b406e722 100644
--- a/src/client/views/collections/MarqueeView.scss
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
@@ -1,8 +1,16 @@
.marqueeView {
+ position: absolute;
+ top:0;
+ left:0;
+ 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
new file mode 100644
index 000000000..df150a045
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -0,0 +1,202 @@
+import { action, computed, observable, trace } from "mobx";
+import { observer } from "mobx-react";
+import { Document } from "../../../../fields/Document";
+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");
+
+interface MarqueeViewProps {
+ getContainerTransform: () => 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<MarqueeViewProps>
+{
+ @observable _lastX: number = 0;
+ @observable _lastY: number = 0;
+ @observable _downX: number = 0;
+ @observable _downY: number = 0;
+ @observable _used: boolean = false;
+ @observable _visible: boolean = false;
+ static DRAG_THRESHOLD = 4;
+
+ @action
+ 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 = (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);
+ }
+ }
+
+ @action
+ 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(true);
+ this._visible = false;
+ 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]);
+ }
+
+ 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);
+ }
+
+ @computed
+ 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));
+ let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField);
+ if (ink && ink != FieldWaiting) {
+ this.marqueeInkDelete(ink.Data);
+ }
+ 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, -1);
+ d.SetText(KeyStore.Title, "" + d.GetNumber(KeyStore.Width, 0) + " " + d.GetNumber(KeyStore.Height, 0));
+ return d;
+ });
+ let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField);
+ 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();
+ SelectionManager.DeselectAll();
+ }
+ }
+ @action
+ marqueeInkSelect(ink: Map<any, any>) {
+ 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<any, any>) {
+ // 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));
+
+ 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() {
+ 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;
+ }
+
+ @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 <div className="marquee" style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}` }} />
+ }
+
+ render() {
+ return <div className="marqueeView" onPointerDown={this.onPointerDown}>
+ {this.props.children}
+ {!this._visible ? (null) : this.marqueeDiv}
+ </div>;
+ }
+} \ 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..7a67c29bf
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/PreviewCursor.scss
@@ -0,0 +1,27 @@
+
+.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
new file mode 100644
index 000000000..93c98f7b0
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx
@@ -0,0 +1,119 @@
+import { action, observable, trace, computed, reaction } from "mobx";
+import { observer } from "mobx-react";
+import { Document } from "../../../../fields/Document";
+import { Documents } from "../../../documents/Documents";
+import { Transform } from "../../../util/Transform";
+import { CollectionFreeFormView } from "./CollectionFreeFormView";
+import "./PreviewCursor.scss";
+import React = require("react");
+import { interfaceDeclaration } from "babel-types";
+
+
+export interface PreviewCursorProps {
+ getTransform: () => Transform;
+ getContainerTransform: () => Transform;
+ container: CollectionFreeFormView;
+ addLiveTextDocument: (doc: Document) => void;
+}
+
+@observer
+export class PreviewCursor extends React.Component<PreviewCursorProps> {
+ @observable _lastX: number = 0;
+ @observable _lastY: number = 0;
+ @observable public _visible: boolean = false;
+ @observable public DownX: number = 0;
+ @observable public DownY: number = 0;
+ _showOnUp: boolean = false;
+
+ @action
+ cleanupInteractions = () => {
+ document.removeEventListener("pointerup", this.onPointerUp, true);
+ document.removeEventListener("pointermove", this.onPointerMove, true);
+ }
+
+ @action
+ onPointerDown = (e: React.PointerEvent) => {
+ if (e.button == 0 && this.props.container.props.active()) {
+ document.removeEventListener("keypress", this.onKeyPress, false);
+ this._showOnUp = true;
+ this.DownX = e.pageX;
+ this.DownY = e.pageY;
+ document.addEventListener("pointerup", this.onPointerUp, true);
+ document.addEventListener("pointermove", this.onPointerMove, true);
+ }
+ }
+ @action
+ onPointerMove = (e: PointerEvent): void => {
+ if (Math.abs(this.DownX - e.clientX) > 4 || Math.abs(this.DownY - e.clientY) > 4) {
+ this._showOnUp = false;
+ this._visible = false;
+ }
+ }
+
+ @action
+ onPointerUp = (e: PointerEvent): void => {
+ if (this._showOnUp) {
+ document.addEventListener("keypress", this.onKeyPress, false);
+ this._lastX = this.DownX;
+ this._lastY = this.DownY;
+ this._visible = true;
+ }
+ this.cleanupInteractions();
+ }
+
+ @action
+ onKeyPress = (e: KeyboardEvent) => {
+ // Mixing events between React and Native is finicky. In FormattedTextBox, we set the
+ // DASHFormattedTextBoxHandled flag when a text box consumes a key press so that we can ignore
+ // the keyPress here.
+ //if not these keys, make a textbox if preview cursor is active!
+ if (!e.ctrlKey && !e.altKey && !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) {
+ //make textbox and add it to this collection
+ let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY);
+ let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "typed text" });
+ this.props.addLiveTextDocument(newBox);
+ document.removeEventListener("keypress", this.onKeyPress, false);
+ this._visible = false;
+ e.stopPropagation();
+ }
+ }
+
+ getPoint = () => this.props.getContainerTransform().transformPoint(this._lastX, this._lastY);
+ getVisible = () => this._visible;
+ setVisible = (v: boolean) => {
+ this._visible = v;
+ document.removeEventListener("keypress", this.onKeyPress, false);
+ }
+ render() {
+ return (
+ <div className="previewCursorView" onPointerDown={this.onPointerDown}>
+ {this.props.children}
+ <PreviewCursorPrompt setVisible={this.setVisible} getPoint={this.getPoint} getVisible={this.getVisible} />
+ </div>
+ )
+ }
+}
+
+export interface PromptProps {
+ getPoint: () => number[];
+ getVisible: () => boolean;
+ setVisible: (v: boolean) => void;
+}
+
+@observer
+export class PreviewCursorPrompt extends React.Component<PromptProps> {
+ private _promptRef = React.createRef<HTMLDivElement>();
+
+ //when focus is lost, this will remove the preview cursor
+ @action onBlur = (): void => this.props.setVisible(false);
+
+ render() {
+ let p = this.props.getPoint();
+ if (this.props.getVisible() && this._promptRef.current)
+ this._promptRef.current.focus();
+ return <div className="previewCursor" id="previewCursor" onBlur={this.onBlur} tabIndex={0} ref={this._promptRef}
+ style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, opacity: this.props.getVisible() ? 1 : 0 }}>
+ I
+ </div >;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 50dc5a619..1a0f0cbbd 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,4 +1,4 @@
-import { computed, trace } from "mobx";
+import { computed } from "mobx";
import { observer } from "mobx-react";
import { KeyStore } from "../../../fields/KeyStore";
import { NumberField } from "../../../fields/NumberField";
@@ -6,6 +6,7 @@ import { Transform } from "../../util/Transform";
import { DocumentView, DocumentViewProps } from "./DocumentView";
import "./DocumentView.scss";
import React = require("react");
+import { thisExpression } from "babel-types";
@observer
@@ -65,14 +66,19 @@ export class CollectionFreeFormDocumentView extends React.Component<DocumentView
return <DocumentView {...this.props}
ContentScaling={this.contentScaling}
ScreenToLocalTransform={this.getTransform}
+ PanelWidth={this.panelWidth}
+ PanelHeight={this.panelHeight}
/>
}
+ panelWidth = () => this.props.Document.GetBoolean(KeyStore.Minimized, false) ? 10 : this.props.PanelWidth();
+ panelHeight = () => this.props.Document.GetBoolean(KeyStore.Minimized, false) ? 10 : this.props.PanelHeight();
render() {
return (
<div className="collectionFreeFormDocumentView-container" ref={this._mainCont} style={{
transformOrigin: "left top",
transform: this.transform,
+ pointerEvents: "all",
width: this.width,
height: this.height,
position: "absolute",
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 2f0459f88..77551649c 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";
@@ -19,7 +19,7 @@ import { KeyValueBox } from "./KeyValueBox";
import { PDFBox } from "./PDFBox";
import { VideoBox } from "./VideoBox";
import { WebBox } from "./WebBox";
-import { HistogramBox } from "./HistogramBox";
+import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox";
import React = require("react");
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
@@ -34,6 +34,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
@computed get layoutKeys(): Key[] { return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array<Key>()); }
@computed get layoutFields(): Key[] { return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array<Key>()); }
+
CreateBindings(): JsxBindings {
let bindings: JsxBindings = { ...this.props, };
for (const key of this.layoutKeys) {
@@ -47,8 +48,13 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
}
render() {
+ let lkeys = this.props.Document.GetT(KeyStore.LayoutKeys, ListField);
+ if (!lkeys || lkeys === FieldWaiting) {
+ return <p>Error loading layout keys</p>;
+ }
return <JsxParser
- components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }} bindings={this.CreateBindings()}
+ components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
+ bindings={this.CreateBindings()}
jsx={this.layout}
showWarnings={true}
onError={(test: any) => { console.log(test) }}
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 85a115f1c..5126e69f9 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -1,23 +1,43 @@
@import "../global_variables";
+
.documentView-node {
position: absolute;
+ top: 0;
+ left:0;
background: $light-color; //overflow: hidden;
+
&.minimized {
width: 30px;
height: 30px;
}
+
.top {
background: #232323;
height: 20px;
cursor: pointer;
}
+
.content {
padding: 20px 20px;
height: auto;
box-sizing: border-box;
}
+
.scroll-box {
overflow-y: scroll;
height: calc(100% - 20px);
}
}
+
+.minimized-box {
+ height: 10px;
+ width: 10px;
+ border-radius: 2px;
+ background: $dark-color
+}
+
+.minimized-box:hover {
+ background: $main-accent;
+ transform: scale(1.15);
+ cursor: pointer;
+} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index fec451b09..b9329f269 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,11 +1,13 @@
import { action, computed, IReactionDisposer, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import { Document } from "../../../fields/Document";
-import { Field, Opt, FieldWaiting } from "../../../fields/Field";
+import { Field, FieldWaiting, Opt } from "../../../fields/Field";
import { Key } from "../../../fields/Key";
import { KeyStore } from "../../../fields/KeyStore";
import { ListField } from "../../../fields/ListField";
+import { BooleanField } from "../../../fields/BooleanField";
import { TextField } from "../../../fields/TextField";
+import { ServerUtils } from "../../../server/ServerUtil";
import { Utils } from "../../../Utils";
import { Documents } from "../../documents/Documents";
import { DocumentManager } from "../../util/DocumentManager";
@@ -19,11 +21,10 @@ import { DocumentContentsView } from "./DocumentContentsView";
import "./DocumentView.scss";
import React = require("react");
-
export interface DocumentViewProps {
ContainingCollectionView: Opt<CollectionView>;
Document: Document;
- AddDocument?: (doc: Document, allowDuplicates: boolean) => void;
+ AddDocument?: (doc: Document, allowDuplicates: boolean) => boolean;
RemoveDocument?: (doc: Document) => boolean;
ScreenToLocalTransform: () => Transform;
isTopMost: boolean;
@@ -34,8 +35,8 @@ export interface DocumentViewProps {
SelectOnLoad: boolean;
}
export interface JsxArgs extends DocumentViewProps {
- Keys: { [name: string]: Key }
- Fields: { [name: string]: Field }
+ Keys: { [name: string]: Key };
+ Fields: { [name: string]: Field };
}
/*
@@ -54,16 +55,16 @@ Example usage of this function:
}
*/
export function FakeJsxArgs(keys: string[], fields: string[] = []): JsxArgs {
- let Keys: { [name: string]: any } = {}
- let Fields: { [name: string]: any } = {}
+ let Keys: { [name: string]: any } = {};
+ let Fields: { [name: string]: any } = {};
for (const key of keys) {
- let fn = () => { }
- Object.defineProperty(fn, "name", { value: key + "Key" })
+ let fn = () => { };
+ Object.defineProperty(fn, "name", { value: key + "Key" });
Keys[key] = fn;
}
for (const field of fields) {
- let fn = () => { }
- Object.defineProperty(fn, "name", { value: field })
+ let fn = () => { };
+ Object.defineProperty(fn, "name", { value: field });
Fields[field] = fn;
}
let args: JsxArgs = {
@@ -84,58 +85,94 @@ export interface JsxBindings {
[prop: string]: any;
}
-
-
@observer
export class DocumentView extends React.Component<DocumentViewProps> {
private _mainCont = React.createRef<HTMLDivElement>();
private _downX: number = 0;
private _downY: number = 0;
+
private _reactionDisposer: Opt<IReactionDisposer>;
- @computed get active(): boolean { return SelectionManager.IsSelected(this) || !this.props.ContainingCollectionView || this.props.ContainingCollectionView.active(); }
- @computed get topMost(): boolean { return !this.props.ContainingCollectionView || this.props.ContainingCollectionView.collectionViewType == CollectionViewType.Docking; }
- @computed get layout(): string { return this.props.Document.GetText(KeyStore.Layout, "<p>Error loading layout data</p>"); }
- @computed get layoutKeys(): Key[] { return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array<Key>()); }
- @computed get layoutFields(): Key[] { return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array<Key>()); }
- screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect();
+ @computed get active(): boolean {
+ return (
+ SelectionManager.IsSelected(this) ||
+ !this.props.ContainingCollectionView ||
+ this.props.ContainingCollectionView.active()
+ );
+ }
+ @computed get topMost(): boolean {
+ return (
+ !this.props.ContainingCollectionView ||
+ this.props.ContainingCollectionView.collectionViewType ==
+ CollectionViewType.Docking
+ );
+ }
+ @computed get layout(): string {
+ return this.props.Document.GetText(
+ KeyStore.Layout,
+ "<p>Error loading layout data</p>"
+ );
+ }
+ @computed get layoutKeys(): Key[] {
+ return this.props.Document.GetData(
+ KeyStore.LayoutKeys,
+ ListField,
+ new Array<Key>()
+ );
+ }
+ @computed get layoutFields(): Key[] {
+ return this.props.Document.GetData(
+ KeyStore.LayoutFields,
+ ListField,
+ new Array<Key>()
+ );
+ }
+ screenRect = (): ClientRect | DOMRect =>
+ this._mainCont.current
+ ? this._mainCont.current.getBoundingClientRect()
+ : new DOMRect();
onPointerDown = (e: React.PointerEvent): void => {
this._downX = e.clientX;
this._downY = e.clientY;
if (e.shiftKey && e.buttons === 2) {
if (this.props.isTopMost) {
this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey);
- }
- else CollectionDockingView.Instance.StartOtherDrag(this.props.Document, e);
+ } else
+ CollectionDockingView.Instance.StartOtherDrag([this.props.Document], e);
e.stopPropagation();
} else {
if (this.active && !e.isDefaultPrevented()) {
e.stopPropagation();
- document.removeEventListener("pointermove", this.onPointerMove)
+ document.removeEventListener("pointermove", this.onPointerMove);
document.addEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp)
+ document.removeEventListener("pointerup", this.onPointerUp);
document.addEventListener("pointerup", this.onPointerUp);
}
}
- }
+ };
private dropDisposer?: DragManager.DragDropDisposer;
- protected createDropTarget = (ele: HTMLDivElement) => {
-
- }
componentDidMount() {
if (this._mainCont.current) {
- this.dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } });
+ 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(),
+ () =>
+ this.props.ContainingCollectionView &&
+ this.props.ContainingCollectionView.SelectedDocs.slice(),
() => {
- if (this.props.ContainingCollectionView && this.props.ContainingCollectionView.SelectedDocs.indexOf(this.props.Document.Id) != -1)
+ if (
+ this.props.ContainingCollectionView &&
+ this.props.ContainingCollectionView.SelectedDocs.indexOf(
+ this.props.Document.Id
+ ) != -1
+ )
SelectionManager.SelectDoc(this, true);
- });
+ }
+ );
}
componentDidUpdate() {
@@ -143,7 +180,9 @@ export class DocumentView extends React.Component<DocumentViewProps> {
this.dropDisposer();
}
if (this._mainCont.current) {
- this.dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } });
+ this.dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, {
+ handlers: { drop: this.drop.bind(this) }
+ });
}
}
@@ -151,10 +190,12 @@ export class DocumentView extends React.Component<DocumentViewProps> {
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();
}
@@ -162,22 +203,28 @@ export class DocumentView extends React.Component<DocumentViewProps> {
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);
+ const [left, top] = this.props
+ .ScreenToLocalTransform()
+ .inverse()
+ .transformPoint(0, 0);
+ let dragData = new DragManager.DocumentDragData([this.props.Document]);
dragData.aliasOnDrop = dropAliasOfDraggedDoc;
dragData.xOffset = x - left;
dragData.yOffset = y - top;
dragData.removeDocument = (dropCollectionView: CollectionView) => {
- if (this.props.RemoveDocument && this.props.ContainingCollectionView !== dropCollectionView) {
+ if (
+ this.props.RemoveDocument &&
+ this.props.ContainingCollectionView !== dropCollectionView
+ ) {
this.props.RemoveDocument(this.props.Document);
}
- }
- DragManager.StartDocumentDrag(this._mainCont.current, dragData, {
+ };
+ DragManager.StartDocumentDrag([this._mainCont.current], dragData, {
handlers: {
- dragComplete: action(() => { }),
+ dragComplete: action(() => { })
},
hideSource: !dropAliasOfDraggedDoc
- })
+ });
}
}
@@ -185,8 +232,11 @@ export class DocumentView extends React.Component<DocumentViewProps> {
if (e.cancelBubble) {
return;
}
- if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
- document.removeEventListener("pointermove", this.onPointerMove)
+ if (
+ Math.abs(this._downX - e.clientX) > 3 ||
+ Math.abs(this._downY - e.clientY) > 3
+ ) {
+ document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
if (!this.topMost || e.buttons == 2 || e.altKey) {
this.startDragging(e.x, e.y, e.ctrlKey || e.altKey);
@@ -194,43 +244,65 @@ export class DocumentView extends React.Component<DocumentViewProps> {
}
e.stopPropagation();
e.preventDefault();
- }
+ };
onPointerUp = (e: PointerEvent): void => {
- document.removeEventListener("pointermove", this.onPointerMove)
- document.removeEventListener("pointerup", this.onPointerUp)
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
e.stopPropagation();
- if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientY - this._downY) < 4) {
+ if (!SelectionManager.IsSelected(this) &&
+ Math.abs(e.clientX - this._downX) < 4 &&
+ Math.abs(e.clientY - this._downY) < 4
+ ) {
SelectionManager.SelectDoc(this, e.ctrlKey);
}
- }
+ };
stopPropogation = (e: React.SyntheticEvent) => {
e.stopPropagation();
- }
+ };
deleteClicked = (): void => {
if (this.props.RemoveDocument) {
this.props.RemoveDocument(this.props.Document);
}
- }
+ };
fieldsClicked = (e: React.MouseEvent): void => {
if (this.props.AddDocument) {
- this.props.AddDocument(Documents.KVPDocument(this.props.Document, { width: 300, height: 300 }), false);
+ this.props.AddDocument(
+ Documents.KVPDocument(this.props.Document, { width: 300, height: 300 }),
+ false
+ );
}
- }
+ };
fullScreenClicked = (e: React.MouseEvent): void => {
CollectionDockingView.Instance.OpenFullScreen(this.props.Document);
ContextMenu.Instance.clearItems();
- ContextMenu.Instance.addItem({ description: "Close Full Screen", event: this.closeFullScreenClicked });
- ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
- }
+ ContextMenu.Instance.addItem({
+ description: "Close Full Screen",
+ event: this.closeFullScreenClicked
+ });
+ ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
+ };
closeFullScreenClicked = (e: React.MouseEvent): void => {
CollectionDockingView.Instance.CloseFullScreen();
ContextMenu.Instance.clearItems();
- ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked })
- ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
- }
+ ContextMenu.Instance.addItem({
+ description: "Full Screen",
+ event: this.fullScreenClicked
+ });
+ ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
+ };
+
+ @action
+ public minimize = (): void => {
+ this.props.Document.SetData(
+ KeyStore.Minimized,
+ true as boolean,
+ BooleanField
+ );
+ SelectionManager.DeselectAll();
+ };
@action
drop = (e: Event, de: DragManager.DropEvent) => {
@@ -242,18 +314,37 @@ export class DocumentView extends React.Component<DocumentViewProps> {
}
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<Document>).Data.push(linkDoc) });
- linkDoc.Set(KeyStore.LinkedToDocs, destDoc);
- destDoc.GetOrCreateAsync(KeyStore.LinkedFromDocs, ListField, field => { (field as ListField<Document>).Data.push(linkDoc) });
- linkDoc.Set(KeyStore.LinkedFromDocs, sourceDoc);
+ destDoc.GetTAsync(KeyStore.Prototype, Document).then(protoDest =>
+ sourceDoc.GetTAsync(KeyStore.Prototype, Document).then(protoSrc =>
+ runInAction(() => {
+ linkDoc.Set(KeyStore.Title, new TextField("New Link"));
+ linkDoc.Set(KeyStore.LinkDescription, new TextField(""));
+ linkDoc.Set(KeyStore.LinkTags, new TextField("Default"));
+ let dstTarg = protoDest ? protoDest : destDoc;
+ let srcTarg = protoSrc ? protoSrc : sourceDoc;
+ linkDoc.Set(KeyStore.LinkedToDocs, dstTarg);
+ linkDoc.Set(KeyStore.LinkedFromDocs, srcTarg);
+ dstTarg.GetOrCreateAsync(
+ KeyStore.LinkedFromDocs,
+ ListField,
+ field => {
+ (field as ListField<Document>).Data.push(linkDoc);
+ }
+ );
+ srcTarg.GetOrCreateAsync(
+ KeyStore.LinkedToDocs,
+ ListField,
+ field => {
+ (field as ListField<Document>).Data.push(linkDoc);
+ }
+ );
+ })
+ )
+ );
e.stopPropagation();
}
- }
+ };
onDrop = (e: React.DragEvent) => {
if (e.isDefaultPrevented()) {
@@ -267,22 +358,49 @@ export class DocumentView extends React.Component<DocumentViewProps> {
e.stopPropagation();
e.preventDefault();
}
- }
+ };
@action
onContextMenu = (e: React.MouseEvent): void => {
e.stopPropagation();
- let moved = Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3;
+ let moved =
+ Math.abs(this._downX - e.clientX) > 3 ||
+ Math.abs(this._downY - e.clientY) > 3;
if (moved || e.isDefaultPrevented()) {
- e.preventDefault()
+ e.preventDefault();
return;
}
- e.preventDefault()
+ e.preventDefault();
- ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked })
- ContextMenu.Instance.addItem({ description: "Fields", event: this.fieldsClicked })
- ContextMenu.Instance.addItem({ description: "Center", event: () => this.props.focus(this.props.Document) })
- ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document) })
+ if (!this.isMinimized()) {
+ ContextMenu.Instance.addItem({
+ description: "Minimize",
+ event: this.minimize
+ });
+ }
+ ContextMenu.Instance.addItem({
+ description: "Full Screen",
+ event: this.fullScreenClicked
+ });
+ ContextMenu.Instance.addItem({
+ description: "Fields",
+ event: this.fieldsClicked
+ });
+ ContextMenu.Instance.addItem({
+ description: "Center",
+ event: () => this.props.focus(this.props.Document)
+ });
+ ContextMenu.Instance.addItem({
+ description: "Open Right",
+ event: () =>
+ CollectionDockingView.Instance.AddRightSplit(this.props.Document)
+ });
+ ContextMenu.Instance.addItem({
+ description: "Copy URL",
+ event: () => {
+ Utils.CopyText(ServerUtils.prepend("/doc/" + this.props.Document.Id));
+ }
+ });
ContextMenu.Instance.addItem({
description: "Copy ID",
event: () => {
@@ -290,52 +408,95 @@ export class DocumentView extends React.Component<DocumentViewProps> {
}
});
//ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) })
- ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
+ ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
if (!this.topMost) {
// DocumentViews should stop propagation of this event
e.stopPropagation();
}
- ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked })
- ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
+ ContextMenu.Instance.addItem({
+ description: "Delete",
+ event: this.deleteClicked
+ });
+ ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
SelectionManager.SelectDoc(this, e.ctrlKey);
- }
+ };
+ isMinimized = () => {
+ let field = this.props.Document.GetT(KeyStore.Minimized, BooleanField);
+ if (field && field !== FieldWaiting) {
+ return field.Data;
+ }
+ };
+
+ @action
+ expand = () => {
+ this.props.Document.SetData(
+ KeyStore.Minimized,
+ false as boolean,
+ BooleanField
+ );
+ };
isSelected = () => {
return SelectionManager.IsSelected(this);
- }
+ };
select = (ctrlPressed: boolean) => {
- SelectionManager.SelectDoc(this, ctrlPressed)
- }
+ SelectionManager.SelectDoc(this, ctrlPressed);
+ };
render() {
if (!this.props.Document) {
- return (null);
- }
- let lkeys = this.props.Document.GetT(KeyStore.LayoutKeys, ListField);
- if (!lkeys || lkeys === FieldWaiting) {
- return <p>Error loading layout keys</p>;
+ return null;
}
+
var scaling = this.props.ContentScaling();
var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0);
- var backgroundcolor = this.props.Document.GetText(KeyStore.BackgroundColor, "");
- return (
- <div className="documentView-node" ref={this._mainCont}
- style={{
- background: backgroundcolor,
- width: nativeWidth > 0 ? nativeWidth.toString() + "px" : "100%",
- height: nativeHeight > 0 ? nativeHeight.toString() + "px" : "100%",
- transformOrigin: "left top",
- transform: `scale(${scaling} , ${scaling})`
- }}
- onDrop={this.onDrop}
- onContextMenu={this.onContextMenu}
- onPointerDown={this.onPointerDown} >
- <DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} layoutKey={KeyStore.Layout} />
- </div >
- )
+
+ if (this.isMinimized()) {
+ return (
+ <div
+ className="minimized-box"
+ ref={this._mainCont}
+ style={{
+ transformOrigin: "left top",
+ transform: `scale(${scaling} , ${scaling})`
+ }}
+ onClick={this.expand}
+ onDrop={this.onDrop}
+ onPointerDown={this.onPointerDown}
+ />
+ );
+ } else {
+ var backgroundcolor = this.props.Document.GetText(
+ KeyStore.BackgroundColor,
+ ""
+ );
+ return (
+ <div
+ className="documentView-node"
+ ref={this._mainCont}
+ style={{
+ background: backgroundcolor,
+ width: nativeWidth > 0 ? nativeWidth.toString() + "px" : "100%",
+ height: nativeHeight > 0 ? nativeHeight.toString() + "px" : "100%",
+ transformOrigin: "left top",
+ transform: `scale(${scaling} , ${scaling})`
+ }}
+ onDrop={this.onDrop}
+ onContextMenu={this.onContextMenu}
+ onPointerDown={this.onPointerDown}
+ >
+ <DocumentContentsView
+ {...this.props}
+ isSelected={this.isSelected}
+ select={this.select}
+ layoutKey={KeyStore.Layout}
+ />
+ </div>
+ );
+ }
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 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<FieldViewProps> {
return <AudioBox {...this.props} />
}
else if (field instanceof Document) {
- return <div>{field.Title}</div>
+ return (<DocumentContentsView Document={field}
+ AddDocument={undefined}
+ RemoveDocument={undefined}
+ ScreenToLocalTransform={() => Transform.Identity}
+ ContentScaling={() => 1}
+ PanelWidth={() => 100}
+ PanelHeight={() => 100}
+ isTopMost={true}
+ SelectOnLoad={false}
+ focus={() => { }}
+ isSelected={() => false}
+ select={() => false}
+ layoutKey={KeyStore.Layout}
+ ContainingCollectionView={undefined} />)
}
else if (field instanceof ListField) {
return (<div>
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index ba9bd9566..0c0efc437 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -3,28 +3,25 @@ import { baseKeymap } from "prosemirror-commands";
import { history, redo, undo } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
import { schema } from "../../util/RichTextSchema";
-import { EditorState, Transaction, } from "prosemirror-state";
+import { EditorState, Transaction } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { Opt, FieldWaiting } from "../../../fields/Field";
import "./FormattedTextBox.scss";
-import React = require("react")
+import React = require("react");
import { RichTextField } from "../../../fields/RichTextField";
import { FieldViewProps, FieldView } from "./FieldView";
-import { Plugin } from 'prosemirror-state'
-import { Decoration, DecorationSet } from 'prosemirror-view'
-import { TooltipTextMenu } from "../../util/TooltipTextMenu"
+import { Plugin } from "prosemirror-state";
+import { Decoration, DecorationSet } from "prosemirror-view";
+import { TooltipTextMenu } from "../../util/TooltipTextMenu";
import { ContextMenu } from "../../views/ContextMenu";
import { inpRules } from "../../util/RichTextRules";
const { buildMenuItems } = require("prosemirror-example-setup");
const { menuBar } = require("prosemirror-menu");
-
-
-
// FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document
//
// HTML Markup: <FormattedTextBox Doc={Document's ID} FieldKey={Key's name + "Key"}
-//
+//
// In Code, the node's HTML is specified in the document's parameterized structure as:
// document.SetField(KeyStore.Layout, "<FormattedTextBox doc={doc} fieldKey={<KEYNAME>Key} />");
// and the node's binding to the specified document KEYNAME as:
@@ -33,141 +30,161 @@ const { menuBar } = require("prosemirror-menu");
// 'fieldKey' property to the Key stored in LayoutKeys
// and 'doc' property to the document that is being rendered
//
-// When rendered() by React, this extracts the TextController from the Document stored at the
-// specified Key and assigns it to an HTML input node. When changes are made to this node,
+// When rendered() by React, this extracts the TextController from the Document stored at the
+// specified Key and assigns it to an HTML input node. When changes are made to this node,
// this will edit the document and assign the new value to that field.
//]
export class FormattedTextBox extends React.Component<FieldViewProps> {
-
- public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(FormattedTextBox, fieldStr) }
- private _ref: React.RefObject<HTMLDivElement>;
- private _editorView: Opt<EditorView>;
- private _reactionDisposer: Opt<IReactionDisposer>;
-
- constructor(props: FieldViewProps) {
- super(props);
-
- this._ref = React.createRef();
- this.onChange = this.onChange.bind(this);
- }
-
- dispatchTransaction = (tx: Transaction) => {
- if (this._editorView) {
- const state = this._editorView.state.apply(tx);
- this._editorView.updateState(state);
- const { doc, fieldKey } = this.props;
- doc.SetDataOnPrototype(fieldKey, JSON.stringify(state.toJSON()), RichTextField);
- // doc.SetData(fieldKey, JSON.stringify(state.toJSON()), RichTextField);
- }
- }
-
- componentDidMount() {
- let state: EditorState;
- const config = {
- schema,
- inpRules, //these currently don't do anything, but could eventually be helpful
- plugins: [
- history(),
- keymap({ "Mod-z": undo, "Mod-y": redo }),
- keymap(baseKeymap),
- this.tooltipMenuPlugin()
- ]
- };
-
- let field = this.props.doc.GetT(this.props.fieldKey, RichTextField);
- if (field && field != FieldWaiting && field.Data) {
- state = EditorState.fromJSON(config, JSON.parse(field.Data));
- } else {
- state = EditorState.create(config);
- }
- if (this._ref.current) {
- this._editorView = new EditorView(this._ref.current, {
- state,
- dispatchTransaction: this.dispatchTransaction
- });
- }
-
- this._reactionDisposer = reaction(() => {
- const field = this.props.doc.GetT(this.props.fieldKey, RichTextField);
- return field && field != FieldWaiting ? field.Data : undefined;
- }, (field) => {
- if (field && this._editorView) {
- this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field)));
- }
- })
- if (this.props.selectOnLoad) {
- this.props.select();
- this._editorView!.focus();
- }
+ public static LayoutString(fieldStr: string = "DataKey") {
+ return FieldView.LayoutString(FormattedTextBox, fieldStr);
+ }
+ private _ref: React.RefObject<HTMLDivElement>;
+ private _editorView: Opt<EditorView>;
+ private _reactionDisposer: Opt<IReactionDisposer>;
+
+ constructor(props: FieldViewProps) {
+ super(props);
+
+ this._ref = React.createRef();
+ this.onChange = this.onChange.bind(this);
+ }
+
+ dispatchTransaction = (tx: Transaction) => {
+ if (this._editorView) {
+ const state = this._editorView.state.apply(tx);
+ this._editorView.updateState(state);
+ const { doc, fieldKey } = this.props;
+ doc.SetDataOnPrototype(
+ fieldKey,
+ JSON.stringify(state.toJSON()),
+ RichTextField
+ );
+ // doc.SetData(fieldKey, JSON.stringify(state.toJSON()), RichTextField);
}
-
- componentWillUnmount() {
- if (this._editorView) {
- this._editorView.destroy();
- }
- if (this._reactionDisposer) {
- this._reactionDisposer();
- }
+ };
+
+ componentDidMount() {
+ let state: EditorState;
+ const config = {
+ schema,
+ inpRules, //these currently don't do anything, but could eventually be helpful
+ plugins: [
+ history(),
+ keymap({ "Mod-z": undo, "Mod-y": redo }),
+ keymap(baseKeymap),
+ this.tooltipMenuPlugin()
+ ]
+ };
+
+ let field = this.props.doc.GetT(this.props.fieldKey, RichTextField);
+ if (field && field != FieldWaiting && field.Data) {
+ state = EditorState.fromJSON(config, JSON.parse(field.Data));
+ } else {
+ state = EditorState.create(config);
}
-
- shouldComponentUpdate() {
- return false;
+ if (this._ref.current) {
+ this._editorView = new EditorView(this._ref.current, {
+ state,
+ dispatchTransaction: this.dispatchTransaction
+ });
}
- @action
- onChange(e: React.ChangeEvent<HTMLInputElement>) {
- const { fieldKey, doc } = this.props;
- doc.SetOnPrototype(fieldKey, new RichTextField(e.target.value))
- // doc.SetData(fieldKey, e.target.value, RichTextField);
- }
- onPointerDown = (e: React.PointerEvent): void => {
- if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
- e.stopPropagation();
+ this._reactionDisposer = reaction(
+ () => {
+ const field = this.props.doc.GetT(this.props.fieldKey, RichTextField);
+ return field && field != FieldWaiting ? field.Data : undefined;
+ },
+ field => {
+ if (field && this._editorView) {
+ this._editorView.updateState(
+ EditorState.fromJSON(config, JSON.parse(field))
+ );
}
+ }
+ );
+ if (this.props.selectOnLoad) {
+ this.props.select();
+ this._editorView!.focus();
}
+ }
- //REPLACE THIS WITH CAPABILITIES SPECIFIC TO THIS TYPE OF NODE
- textCapability = (e: React.MouseEvent): void => {
+ componentWillUnmount() {
+ if (this._editorView) {
+ this._editorView.destroy();
}
-
- specificContextMenu = (e: React.MouseEvent): void => {
- ContextMenu.Instance.addItem({ description: "Text Capability", event: this.textCapability });
- // ContextMenu.Instance.addItem({
- // description: "Submenu",
- // items: [
- // {
- // description: "item 1", event:
- // },
- // {
- // description: "item 2", event:
- // }
- // ]
- // })
- // e.stopPropagation()
-
- }
-
- onPointerWheel = (e: React.WheelEvent): void => {
- e.stopPropagation();
- }
-
- tooltipMenuPlugin() {
- return new Plugin({
- view(_editorView) {
- return new TooltipTextMenu(_editorView)
- }
- })
- }
-
- onKeyPress(e: React.KeyboardEvent) {
- e.stopPropagation();
+ if (this._reactionDisposer) {
+ this._reactionDisposer();
}
- render() {
- return (<div className="formattedTextBox-cont"
- onKeyPress={this.onKeyPress}
- onPointerDown={this.onPointerDown}
- onContextMenu={this.specificContextMenu}
- onWheel={this.onPointerWheel}
- ref={this._ref} />)
+ }
+
+ shouldComponentUpdate() {
+ return false;
+ }
+
+ @action
+ onChange(e: React.ChangeEvent<HTMLInputElement>) {
+ const { fieldKey, doc } = this.props;
+ doc.SetOnPrototype(fieldKey, new RichTextField(e.target.value));
+ // doc.SetData(fieldKey, e.target.value, RichTextField);
+ }
+ onPointerDown = (e: React.PointerEvent): void => {
+ if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
+ e.stopPropagation();
}
-} \ No newline at end of file
+ };
+
+ //REPLACE THIS WITH CAPABILITIES SPECIFIC TO THIS TYPE OF NODE
+ textCapability = (e: React.MouseEvent): void => {};
+
+ specificContextMenu = (e: React.MouseEvent): void => {
+ ContextMenu.Instance.addItem({
+ description: "Text Capability",
+ event: this.textCapability
+ });
+
+ // ContextMenu.Instance.addItem({
+ // description: "Submenu",
+ // items: [
+ // {
+ // description: "item 1", event:
+ // },
+ // {
+ // description: "item 2", event:
+ // }
+ // ]
+ // })
+ // e.stopPropagation()
+ };
+
+ onPointerWheel = (e: React.WheelEvent): void => {
+ e.stopPropagation();
+ };
+
+ tooltipMenuPlugin() {
+ return new Plugin({
+ view(_editorView) {
+ return new TooltipTextMenu(_editorView);
+ }
+ });
+ }
+ 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 (
+ <div
+ className="formattedTextBox-cont"
+ onKeyDown={this.onKeyPress}
+ onKeyPress={this.onKeyPress}
+ onPointerDown={this.onPointerDown}
+ onContextMenu={this.specificContextMenu}
+ // tfs: do we need this event handler
+ onWheel={this.onPointerWheel}
+ ref={this._ref}
+ />
+ );
+ }
+}
diff --git a/src/client/views/nodes/HistogramBox.scss b/src/client/views/nodes/HistogramBox.scss
deleted file mode 100644
index 04bf1d732..000000000
--- a/src/client/views/nodes/HistogramBox.scss
+++ /dev/null
@@ -1,8 +0,0 @@
-.histogrambox-container {
- padding: 0vw;
- position: relative;
- text-align: center;
- width: 100%;
- height: 100%;
- }
- \ No newline at end of file
diff --git a/src/client/views/nodes/HistogramBox.tsx b/src/client/views/nodes/HistogramBox.tsx
deleted file mode 100644
index 223fdf0d8..000000000
--- a/src/client/views/nodes/HistogramBox.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import React = require("react")
-import { observer } from "mobx-react";
-import { FieldView, FieldViewProps } from './FieldView';
-import "./VideoBox.scss";
-import { observable, reaction } from "mobx";
-import { HistogramOperation } from "../../northstar/operations/HistogramOperation";
-import { Main } from "../Main";
-import { ColumnAttributeModel } from "../../northstar/core/attribute/AttributeModel";
-import { AttributeTransformationModel } from "../../northstar/core/attribute/AttributeTransformationModel";
-import { AggregateFunction, HistogramResult, DoubleValueAggregateResult } from "../../northstar/model/idea/idea";
-import { ModelHelpers } from "../../northstar/model/ModelHelpers";
-
-@observer
-export class HistogramBox extends React.Component<FieldViewProps> {
-
- public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(HistogramBox, fieldStr) }
-
- constructor(props: FieldViewProps) {
- super(props);
- }
-
- @observable _histoResult?: HistogramResult;
- _histoOp?: HistogramOperation;
-
- componentDidMount() {
- Main.Instance.GetAllNorthstarColumnAttributes().map(a => {
- if (a.displayName == this.props.doc.Title) {
- var atmod = new ColumnAttributeModel(a);
- this._histoOp = new HistogramOperation(new AttributeTransformationModel(atmod, AggregateFunction.None),
- new AttributeTransformationModel(atmod, AggregateFunction.Count),
- new AttributeTransformationModel(atmod, AggregateFunction.Count));
- reaction(() => [this._histoOp && this._histoOp.Result],
- () => this._histoResult = this._histoOp ? this._histoOp.Result as HistogramResult : undefined
- );
- this._histoOp.Update();
- }
- })
- }
-
- twoString() {
- let str = "";
- if (this._histoResult && !this._histoResult.isEmpty) {
- for (let key in this._histoResult.bins) {
- if (this._histoResult.bins.hasOwnProperty(key)) {
- let bin = this._histoResult.bins[key];
- str += JSON.stringify(bin.binIndex!.toJSON()) + " = ";
- let valueAggregateKey = ModelHelpers.CreateAggregateKey(this._histoOp!.V, this._histoResult, ModelHelpers.AllBrushIndex(this._histoResult));
- let value = ModelHelpers.GetAggregateResult(bin, valueAggregateKey) as DoubleValueAggregateResult;
- if (value && value.hasResult && value.result) {
- str += value.result;
- }
- }
- }
- }
- return str;
- }
-
- render() {
- if (!this._histoResult)
- return (null);
- return (
- <div className="histogrambox-container">
- `HISTOGRAM RESULT : ${this.twoString()}`
- </div>
- )
- }
-} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index dd2f71b59..e81f8fec7 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);
@@ -39,9 +41,26 @@ export class LinkBox extends React.Component<Props> {
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 {
- CollectionDockingView.Instance.AddRightSplit(this.props.pairedDoc)
+ this.props.pairedDoc.GetAsync(KeyStore.AnnotationOn, (contextDoc: any) => {
+ if (!contextDoc) {
+ 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) => {
+ 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.scss b/src/client/views/nodes/PDFBox.scss
index ad947afd5..830dfe6c6 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -1,12 +1,16 @@
.react-pdf__Page {
transform-origin: left top;
position: absolute;
+ top: 0;
+ left:0;
}
.react-pdf__Document {
position: absolute;
}
.pdfBox-buttonTray {
position:absolute;
+ top: 0;
+ left:0;
z-index: 25;
}
.pdfBox-contentContainer {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 3a0ef2d32..f9f5bc8f8 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, 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,7 +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 { RouteStore } from "../../../server/RouteStore";
+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,10 +58,11 @@ export class PDFBox extends React.Component<FieldViewProps> {
private _mainDiv = React.createRef<HTMLDivElement>()
private _pdf = React.createRef<HTMLCanvasElement>();
+ @observable private _renderAsSvg = true;
+
//very useful for keeping track of X and y position throughout the PDF Canvas
private initX: number = 0;
private initY: number = 0;
- private initPage: boolean = false;
//checks if tool is on
private _toolOn: boolean = false; //checks if tool is on
@@ -87,18 +89,16 @@ export class PDFBox extends React.Component<FieldViewProps> {
@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() {
this._reactionDisposer = reaction(
- () => this.curPage,
+ () => [SelectionManager.SelectedDocuments().slice()],
() => {
- if (this.curPage && this.initPage) {
+ if (this.curPage > 0 && this.thumbnailPage > 0 && this.curPage != this.thumbnailPage && !this.props.isSelected()) {
this.saveThumbnail();
this._interactive = true;
- } else {
- if (this.curPage > 0)
- this.initPage = true;
}
},
{ fireImmediately: true });
@@ -378,17 +378,21 @@ export class PDFBox extends React.Component<FieldViewProps> {
@action
saveThumbnail = () => {
+ this._renderAsSvg = false;
setTimeout(() => {
var me = this;
- htmlToImage.toPng(this._mainDiv.current!,
- { width: me.props.doc.GetNumber(KeyStore.NativeWidth, 0), height: me.props.doc.GetNumber(KeyStore.NativeHeight, 0), quality: 0.5 })
- .then(function (dataUrl: string) {
+ 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(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
@@ -428,21 +432,16 @@ export class PDFBox extends React.Component<FieldViewProps> {
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
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;
return <div className="pdfBox-contentContainer" key="container" style={{ transform: `scale(${xf}, ${xf})` }}>
- <Document file={window.origin + RouteStore.corsProxy + `/${pdfUrl}`}>
+ <Document file={window.origin + RouteStore.corsProxy + `/${pdfUrl}`} renderMode={this._renderAsSvg ? "svg" : ""}>
<Measure onResize={this.setScaling}>
{({ measureRef }) =>
<div className="pdfBox-page" ref={measureRef}>
@@ -464,19 +463,17 @@ export class PDFBox extends React.Component<FieldViewProps> {
return [
this._pageInfo.area.filter(() => this._pageInfo.area).map((element: any) => element),
this._currAnno.map((element: any) => element),
- <div key="pdfBox-contentShell">
- {this.pdfContent}
- {proxy}
- </div>
+ this.pdfContent,
+ proxy
];
}
@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 <img src={path} width="100%" />;
}
return (null);
diff --git a/src/client/views/nodes/Sticky.tsx b/src/client/views/nodes/Sticky.tsx
index d57dd5c0b..4a4d69e90 100644
--- a/src/client/views/nodes/Sticky.tsx
+++ b/src/client/views/nodes/Sticky.tsx
@@ -1,83 +1,83 @@
-import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
-import React = require("react")
-import { observer } from "mobx-react"
-import 'react-pdf/dist/Page/AnnotationLayer.css'
+import "react-image-lightbox/style.css"; // This only needs to be imported once in your app
+import React = require("react");
+import { observer } from "mobx-react";
+import "react-pdf/dist/Page/AnnotationLayer.css";
interface IProps {
- Height: number;
- Width: number;
- X: number;
- Y: number;
+ Height: number;
+ Width: number;
+ X: number;
+ Y: number;
}
/**
- * Sticky, also known as area highlighting, is used to highlight large selection of the PDF file.
- * Improvements that could be made: maybe store line array and store that somewhere for future rerendering.
- *
- * Written By: Andrew Kim
+ * Sticky, also known as area highlighting, is used to highlight large selection of the PDF file.
+ * Improvements that could be made: maybe store line array and store that somewhere for future rerendering.
+ *
+ * Written By: Andrew Kim
*/
@observer
export class Sticky extends React.Component<IProps> {
+ private initX: number = 0;
+ private initY: number = 0;
- private initX: number = 0;
- private initY: number = 0;
+ private _ref = React.createRef<HTMLCanvasElement>();
+ private ctx: any; //context that keeps track of sticky canvas
- private _ref = React.createRef<HTMLCanvasElement>();
- private ctx: any; //context that keeps track of sticky canvas
-
- /**
- * drawing. Registers the first point that user clicks when mouse button is pressed down on canvas
- */
- drawDown = (e: React.PointerEvent) => {
- if (this._ref.current) {
- this.ctx = this._ref.current.getContext("2d");
- let mouse = e.nativeEvent;
- this.initX = mouse.offsetX;
- this.initY = mouse.offsetY;
- this.ctx.beginPath();
- this.ctx.lineTo(this.initX, this.initY);
- this.ctx.strokeStyle = "black";
- document.addEventListener("pointermove", this.drawMove);
- document.addEventListener("pointerup", this.drawUp);
- }
+ /**
+ * drawing. Registers the first point that user clicks when mouse button is pressed down on canvas
+ */
+ drawDown = (e: React.PointerEvent) => {
+ if (this._ref.current) {
+ this.ctx = this._ref.current.getContext("2d");
+ let mouse = e.nativeEvent;
+ this.initX = mouse.offsetX;
+ this.initY = mouse.offsetY;
+ this.ctx.beginPath();
+ this.ctx.lineTo(this.initX, this.initY);
+ this.ctx.strokeStyle = "black";
+ document.addEventListener("pointermove", this.drawMove);
+ document.addEventListener("pointerup", this.drawUp);
}
+ };
- //when user drags
- drawMove = (e: PointerEvent): void => {
- //x and y mouse movement
- let x = this.initX += e.movementX,
- y = this.initY += e.movementY;
- //connects the point
- this.ctx.lineTo(x, y);
- this.ctx.stroke();
-
- }
+ //when user drags
+ drawMove = (e: PointerEvent): void => {
+ //x and y mouse movement
+ let x = (this.initX += e.movementX),
+ y = (this.initY += e.movementY);
+ //connects the point
+ this.ctx.lineTo(x, y);
+ this.ctx.stroke();
+ };
- /**
- * when user lifts the mouse, the drawing ends
- */
- drawUp = (e: PointerEvent) => {
- this.ctx.closePath();
- console.log(this.ctx);
- document.removeEventListener("pointermove", this.drawMove);
- }
+ /**
+ * when user lifts the mouse, the drawing ends
+ */
+ drawUp = (e: PointerEvent) => {
+ this.ctx.closePath();
+ console.log(this.ctx);
+ document.removeEventListener("pointermove", this.drawMove);
+ };
- render() {
- return (
- <div onPointerDown={this.drawDown}>
- <canvas ref={this._ref} height={this.props.Height} width={this.props.Width}
- style={{
- position: "absolute",
- top: "20px",
- left: "0px",
- zIndex: 1,
- background: "yellow",
- transform: `translate(${this.props.X}px, ${this.props.Y}px)`,
- opacity: 0.4
- }}
- />
-
- </div>
- );
- }
-} \ No newline at end of file
+ render() {
+ return (
+ <div onPointerDown={this.drawDown}>
+ <canvas
+ ref={this._ref}
+ height={this.props.Height}
+ width={this.props.Width}
+ style={{
+ position: "absolute",
+ top: "20px",
+ left: "0px",
+ zIndex: 1,
+ background: "yellow",
+ transform: `translate(${this.props.X}px, ${this.props.Y}px)`,
+ opacity: 0.4
+ }}
+ />
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 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<FieldViewProps> {
+ private _reactionDisposer: Opt<IReactionDisposer>;
+ private _videoRef = React.createRef<HTMLVideoElement>()
public static LayoutString() { return FieldView.LayoutString(VideoBox) }
constructor(props: FieldViewProps) {
super(props);
}
+ @computed private get curPage() { return this.props.doc.GetNumber(KeyStore.CurPage, -1); }
+
_loaded: boolean = false;
@@ -39,7 +43,17 @@ export class VideoBox extends React.Component<FieldViewProps> {
}
}
+ 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<FieldViewProps> {
return <div>Loading</div>
}
let path = field.Data.href;
-
- //setTimeout(action(() => this._loaded = true), 500);
+ trace();
return (
<Measure onResize={this.setScaling}>
{({ measureRef }) =>
- <video className="videobox-cont" onClick={() => { }} ref={measureRef}>
- <source src={path} type="video/mp4" />
- Not supported.
+ <div style={{ width: "100%", height: "auto" }} ref={measureRef}>
+ <video className="videobox-cont" onClick={() => { }} ref={this.setVideoRef}>
+ <source src={path} type="video/mp4" />
+ Not supported.
</video>
+ </div>
}
</Measure>
)
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;