aboutsummaryrefslogtreecommitdiff
path: root/src/client/views
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views')
-rw-r--r--src/client/views/ContextMenu.tsx11
-rw-r--r--src/client/views/DocumentDecorations.scss38
-rw-r--r--src/client/views/DocumentDecorations.tsx7
-rw-r--r--src/client/views/EditableView.scss6
-rw-r--r--src/client/views/EditableView.tsx3
-rw-r--r--src/client/views/InkingCanvas.scss266
-rw-r--r--src/client/views/InkingCanvas.tsx28
-rw-r--r--src/client/views/InkingControl.tsx16
-rw-r--r--src/client/views/InkingStroke.tsx2
-rw-r--r--src/client/views/Main.scss4
-rw-r--r--src/client/views/Main.tsx59
-rw-r--r--src/client/views/_global_variables.scss20
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx10
-rw-r--r--src/client/views/collections/CollectionFreeFormView.scss28
-rw-r--r--src/client/views/collections/CollectionFreeFormView.tsx206
-rw-r--r--src/client/views/collections/CollectionPDFView.scss27
-rw-r--r--src/client/views/collections/CollectionPDFView.tsx26
-rw-r--r--src/client/views/collections/CollectionSchemaView.scss12
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx30
-rw-r--r--src/client/views/collections/CollectionTreeView.scss59
-rw-r--r--src/client/views/collections/CollectionVideoView.scss40
-rw-r--r--src/client/views/collections/CollectionVideoView.tsx118
-rw-r--r--src/client/views/collections/CollectionView.tsx8
-rw-r--r--src/client/views/collections/CollectionViewBase.tsx31
-rw-r--r--src/client/views/collections/MarqueeView.scss8
-rw-r--r--src/client/views/collections/MarqueeView.tsx164
-rw-r--r--src/client/views/collections/PreviewCursor.scss18
-rw-r--r--src/client/views/collections/PreviewCursor.tsx78
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx35
-rw-r--r--src/client/views/nodes/DocumentView.tsx95
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx6
-rw-r--r--src/client/views/nodes/ImageBox.scss4
-rw-r--r--src/client/views/nodes/ImageBox.tsx4
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx69
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx31
-rw-r--r--src/client/views/nodes/LinkBox.scss42
-rw-r--r--src/client/views/nodes/LinkBox.tsx19
-rw-r--r--src/client/views/nodes/LinkEditor.scss22
-rw-r--r--src/client/views/nodes/LinkMenu.scss1
-rw-r--r--src/client/views/nodes/LinkMenu.tsx4
-rw-r--r--src/client/views/nodes/PDFBox.scss2
-rw-r--r--src/client/views/nodes/PDFBox.tsx8
-rw-r--r--src/client/views/nodes/VideoBox.scss2
-rw-r--r--src/client/views/nodes/VideoBox.tsx58
44 files changed, 1211 insertions, 514 deletions
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index d68dc6e66..9109b56bb 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -46,11 +46,13 @@ export class ContextMenu extends React.Component {
@action
displayMenu(x: number, y: number) {
- this._pageX = x
- this._pageY = window.innerHeight - y >= 100 ? y : window.innerHeight - y;
+ //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);
- // y pos absolute to top only when there's enough space
- this._yRelativeToTop = window.innerHeight - y >= 100;
+ this._pageX = x > maxX ? maxX : x;
+ this._pageY = y > maxY ? maxY : y;
this._searchString = "";
@@ -77,6 +79,7 @@ export class ContextMenu extends React.Component {
let style = this._yRelativeToTop ? { left: this._pageX, top: this._pageY, display: this._display } :
{ left: this._pageX, bottom: this._pageY, display: this._display };
+
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>
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index a29bf36fa..11595aa01 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -67,14 +67,35 @@
grid-column: 1/4
}
+.linkButton-empty:hover {
+ background: $main-accent;
+ transform: scale(1.05);
+ cursor: pointer;
+}
+
+.linkButton-nonempty:hover {
+ background: $main-accent;
+ transform: scale(1.05);
+ cursor: pointer;
+}
+
.linkButton-empty {
height: 20px;
width: 20px;
margin-top: 10px;
border-radius: 50%;
- opacity: 0.6;
+ opacity: 0.9;
pointer-events: auto;
- background-color: #2B6091;
+ background-color: $dark-color;
+ color: $light-color;
+ 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-nonempty {
@@ -82,7 +103,16 @@
width: 20px;
margin-top: 10px;
border-radius: 50%;
- opacity: 0.6;
+ opacity: 0.9;
pointer-events: auto;
- background-color: rgb(35, 165, 42);
+ background-color: $dark-color;
+ color: $light-color;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ font-size: 75%;
+ transition: transform 0.2s;
+ text-align: center;
+ display: flex;
+ justify-content: center;
+ align-items: center;
} \ No newline at end of file
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index b75644ecd..3bdb7d5b3 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -208,13 +208,16 @@ export class DocumentDecorations extends React.Component {
let linkButton = null;
if (SelectionManager.SelectedDocuments().length > 0) {
let selFirst = SelectionManager.SelectedDocuments()[0];
+ let linkToSize = selFirst.props.Document.GetData(KeyStore.LinkedToDocs, ListField, []).length;
+ let linkFromSize = selFirst.props.Document.GetData(KeyStore.LinkedFromDocs, ListField, []).length;
+ let linkCount = linkToSize + linkFromSize;
linkButton = (<Flyout
anchorPoint={anchorPoints.RIGHT_TOP}
content={
<LinkMenu docView={selFirst} changeFlyout={this.changeFlyoutContent}>
</LinkMenu>
}>
- <div className={"linkButton-" + (selFirst.props.Document.GetData(KeyStore.LinkedToDocs, ListField, []).length ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} ref={this._linkButton} />
+ <div className={"linkButton-" + (selFirst.props.Document.GetData(KeyStore.LinkedToDocs, ListField, []).length ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} ref={this._linkButton}>{linkCount}</div>
</Flyout>);
}
return (
@@ -234,7 +237,7 @@ 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 className="linkFlyout">{linkButton}</div>
+ <div title="View Links" className="linkFlyout">{linkButton}</div>
</div >
)
diff --git a/src/client/views/EditableView.scss b/src/client/views/EditableView.scss
new file mode 100644
index 000000000..be3c5069a
--- /dev/null
+++ b/src/client/views/EditableView.scss
@@ -0,0 +1,6 @@
+.editableView-container-editing {
+ overflow-wrap: break-word;
+ word-wrap: break-word;
+ hyphens: auto;
+ max-width: 300px;
+} \ No newline at end of file
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 84b1b91c3..3b54c0dbb 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -1,6 +1,7 @@
import React = require('react')
import { observer } from 'mobx-react';
import { observable, action } from 'mobx';
+import "./EditableView.scss"
export interface EditableProps {
/**
@@ -49,7 +50,7 @@ export class EditableView extends React.Component<EditableProps> {
style={{ display: "inline" }}></input>
} else {
return (
- <div className="editableView-container-editing" style={{ display: "inline", height: "100%", maxHeight: `${this.props.height}` }}
+ <div className="editableView-container-editing" style={{ display: "inline", height: "auto", maxHeight: `${this.props.height}` }}
onClick={action(() => this.editing = true)}>
{this.props.contents}
</div>
diff --git a/src/client/views/InkingCanvas.scss b/src/client/views/InkingCanvas.scss
index d132504fc..e79b146b9 100644
--- a/src/client/views/InkingCanvas.scss
+++ b/src/client/views/InkingCanvas.scss
@@ -1,147 +1,149 @@
@import "global_variables";
.inking-canvas {
- position: fixed;
- top: -50000px;
- left: -50000px; // z-index: 99; //overlays ink on top of everything
- svg {
- width: 100000px;
- height: 100000px;
- .highlight {
- mix-blend-mode: multiply;
+ 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;
+ }
}
- }
}
.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 3px;
- background: transparent;
- color: $light-color;
- }
- .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;
+ position: absolute;
+ left: 70px;
+ bottom: 70px;
+ margin: 0;
padding: 0;
- button {
- height: 36px;
- padding: 0px;
- padding-bottom: 3px;
- margin-left: 10px;
- background-color: transparent;
- color: $intermediate-color;
+ display: flex;
+ label,
+ input,
+ option {
+ font-size: 12px;
}
- button:hover {
- transform: scale(1.15);
+ 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;
+ }
}
- }
- .ink-size {
- display: flex;
- justify-content: space-between;
input[type="text"] {
- width: 42px;
+ border: none;
+ padding: 0 0px;
+ background: transparent;
+ color: $dark-color;
+ font-size: 12px;
+ margin-top: 4px;
}
- > * {
- margin-right: 6px;
- &:last-child {
- margin-right: 0;
- }
+ .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-color {
- display: flex;
- position: relative;
- padding-right: 0;
- label {
- margin-right: 6px;
+ .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-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-size {
+ display: flex;
+ justify-content: space-between;
+ input[type="text"] {
+ width: 42px;
+ }
+ >* {
+ margin-right: 6px;
+ &:last-child {
+ margin-right: 0;
+ }
+ }
}
- .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;
+ .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 0d87c1239..84c47f616 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -1,4 +1,5 @@
import { observer } from "mobx-react";
+import { observable } from "mobx";
import { action, computed } from "mobx";
import { InkingControl } from "./InkingControl";
import React = require("react");
@@ -6,14 +7,10 @@ import { Transform } from "../util/Transform";
import { Document } from "../../fields/Document";
import { KeyStore } from "../../fields/KeyStore";
import { InkField, InkTool, StrokeData, StrokeMap } from "../../fields/InkField";
-import { JsxArgs } from "./nodes/DocumentView";
import { InkingStroke } from "./InkingStroke";
import "./InkingCanvas.scss"
-import { CollectionDockingView } from "./collections/CollectionDockingView";
import { Utils } from "../../Utils";
import { FieldWaiting } from "../../fields/Field";
-import { getMapLikeKeys } from "mobx/lib/internal";
-
interface InkCanvasProps {
getScreenTransform: () => Transform;
@@ -22,7 +19,16 @@ interface InkCanvasProps {
@observer
export class InkingCanvas extends React.Component<InkCanvasProps> {
-
+ static InkOffset: number = 50000;
+ 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
+ }
private _isDrawing: boolean = false;
private _idGenerator: string = "";
@@ -51,7 +57,6 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
document.removeEventListener("mouseup", this.handleMouseUp);
}
-
@action
handleMouseDown = (e: React.PointerEvent): void => {
if (e.button != 0 ||
@@ -62,7 +67,6 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
if (InkingControl.Instance.selectedTool === InkTool.Eraser) {
return
}
- e.stopPropagation()
const point = this.relativeCoordinatesForEvent(e);
// start the new line, saves a uuid to represent the field of the stroke
@@ -74,7 +78,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
color: InkingControl.Instance.selectedColor,
width: InkingControl.Instance.selectedWidth,
tool: InkingControl.Instance.selectedTool,
- page: this.props.Document.GetNumber(KeyStore.CurPage, 0)
+ page: this.props.Document.GetNumber(KeyStore.CurPage, -1)
});
this.inkData = data;
this._isDrawing = true;
@@ -110,8 +114,8 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
relativeCoordinatesForEvent = (e: React.MouseEvent): { x: number, y: number } => {
let [x, y] = this.props.getScreenTransform().transformPoint(e.clientX, e.clientY);
- x += 50000
- y += 50000
+ x += InkingCanvas.InkOffset;
+ y += InkingCanvas.InkOffset;
return { x, y };
}
@@ -145,11 +149,11 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
// parse data from server
let paths: Array<JSX.Element> = []
- let curPage = this.props.Document.GetNumber(KeyStore.CurPage, 0)
+ let curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1)
Array.from(lines).map(item => {
let id = item[0];
let strokeData = item[1];
- if (strokeData.page == 0 || strokeData.page == curPage)
+ if (strokeData.page == -1 || strokeData.page == curPage)
paths.push(<InkingStroke key={id} id={id}
line={strokeData.pathData}
color={strokeData.color}
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index bf633b034..fb75ef2a5 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -78,14 +78,14 @@ export class InkingControl extends React.Component {
<ul className="inking-control" style={this._open ? { display: "flex" } : { display: "none" }}>
<li className="ink-tools ink-panel">
<div className="ink-tool-buttons">
- <button onClick={() => this.switchTool(InkTool.Pen)} style={this.selected(InkTool.Pen)}><FontAwesomeIcon icon="pen" size="2x" /></button>
- <button onClick={() => this.switchTool(InkTool.Highlighter)} style={this.selected(InkTool.Highlighter)}><FontAwesomeIcon icon="highlighter" size="2x" /></button>
- <button onClick={() => this.switchTool(InkTool.Eraser)} style={this.selected(InkTool.Eraser)}><FontAwesomeIcon icon="eraser" size="2x" /></button>
- <button onClick={() => this.switchTool(InkTool.None)} style={this.selected(InkTool.None)}><FontAwesomeIcon icon="ban" size="2x" /></button>
+ <button onClick={() => this.switchTool(InkTool.Pen)} style={this.selected(InkTool.Pen)}><FontAwesomeIcon icon="pen" size="lg" title="Pen" /></button>
+ <button onClick={() => this.switchTool(InkTool.Highlighter)} style={this.selected(InkTool.Highlighter)}><FontAwesomeIcon icon="highlighter" size="lg" title="Highlighter" /></button>
+ <button onClick={() => this.switchTool(InkTool.Eraser)} style={this.selected(InkTool.Eraser)}><FontAwesomeIcon icon="eraser" size="lg" title="Eraser" /></button>
+ <button onClick={() => this.switchTool(InkTool.None)} style={this.selected(InkTool.None)}><FontAwesomeIcon icon="ban" size="lg" title="Pointer" /></button>
</div>
</li>
<li className="ink-color ink-panel">
- <label>Color: </label>
+ <label>COLOR: </label>
<div className="ink-color-display" style={{ backgroundColor: this._selectedColor }}
onClick={() => this.toggleColorPicker()}>
{/* {this._colorPickerDisplay ? <span>&#9660;</span> : <span>&#9650;</span>} */}
@@ -95,9 +95,9 @@ export class InkingControl extends React.Component {
</div>
</li>
<li className="ink-size ink-panel">
- <label htmlFor="stroke-width">Size: </label>
- {/* <input type="text" min="1" max="100" value={this._selectedWidth} name="stroke-width"
- onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.switchWidth(e.target.value)} /> */}
+ <label htmlFor="stroke-width">SIZE: </label>
+ <input type="text" min="1" max="100" value={this._selectedWidth} name="stroke-width"
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.switchWidth(e.target.value)} />
<input type="range" min="1" max="100" value={this._selectedWidth} name="stroke-width"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.switchWidth(e.target.value)} />
</li>
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index d724421d3..87b5c43d8 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -21,8 +21,6 @@ export class InkingStroke extends React.Component<StrokeProps> {
@observable private _strokeColor: string = this.props.color;
@observable private _strokeWidth: string = this.props.width;
- private _canvasColor: string = "#cdcdcd";
-
deleteStroke = (e: React.MouseEvent): void => {
if (InkingControl.Instance.selectedTool === InkTool.Eraser && e.buttons === 1) {
this.props.deleteCallback(this.props.id);
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index af16e055d..bb42db202 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -38,6 +38,10 @@ h1 {
user-select: none;
}
+.jsx-parser {
+ width:100%
+}
+
p {
margin: 0px;
padding: 0px;
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 687c765ec..a128a0e73 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -1,4 +1,4 @@
-import { action, configure } from 'mobx';
+import { action, configure, observable } from 'mobx';
import "normalize.css";
import * as React from 'react';
import * as ReactDOM from 'react-dom';
@@ -31,6 +31,9 @@ import { faRedoAlt } from '@fortawesome/free-solid-svg-icons';
import { faPenNib } from '@fortawesome/free-solid-svg-icons';
import { faFilm } from '@fortawesome/free-solid-svg-icons';
import { faMusic } from '@fortawesome/free-solid-svg-icons';
+import Measure from 'react-measure';
+import { withContentRect } from 'react-measure'
+
configure({ enforceActions: "observed" }); // causes errors to be generated when modifying an observable outside of an action
@@ -41,11 +44,16 @@ document.addEventListener("pointerdown", action(function (e: PointerEvent) {
ContextMenu.Instance.clearItems()
}
}), true)
-
-const mainDocId = "mainDoc";
+const pathname = window.location.pathname.split("/");
+const mainDocId = pathname[pathname.length - 1];
let mainContainer: Document;
let mainfreeform: Document;
+class mainDocFrame {
+ @observable public static pwidth: number = 0;
+ @observable public static pheight: number = 0;
+}
+
library.add(faFont);
library.add(faImage);
library.add(faFilePdf);
@@ -82,15 +90,14 @@ Documents.initProtos(mainDocId, (res?: Document) => {
let audiourl = "http://techslides.com/demos/samples/sample.mp3";
let videourl = "http://techslides.com/demos/sample-videos/small.mp4";
let clearDatabase = action(() => Utils.Emit(Server.Socket, MessageStore.DeleteAll, {}))
- let addTextNode = action(() => Documents.TextDocument({ width: 230, height: 130, title: "a text note" }))
- let addColNode = action(() => Documents.FreeformDocument([], { width: 300, height: 300, title: "a freeform collection" }));
- let addSchemaNode = action(() => Documents.SchemaDocument([Documents.TextDocument()], { width: 450, height: 200, title: "a schema collection" }));
- let addPDFNode = action(() => Documents.PdfDocument(pdfurl, { width: 300, height: 400, title: "a pdf" }));
- let addImageNode = action(() => Documents.ImageDocument(imgurl, { width: 200, height: 200, title: "an image of a cat" }));
- let addWebNode = action(() => Documents.WebDocument(weburl, { width: 300, height: 400, title: "a sample web page" }));
- let addVideoNode = action(() => Documents.VideoDocument(videourl, { width: 300, height: 250, title: "video node" }));
- let addAudioNode = action(() => Documents.AudioDocument(audiourl, { width: 250, height: 100, title: "audio node" }));
-
+ 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([Documents.TextDocument()], { width: 200, height: 200, title: "a schema collection" }));
+ let addVideoNode = action(() => Documents.VideoDocument(videourl, { width: 200, title: "video node" }));
+ let addPDFNode = action(() => Documents.PdfDocument(pdfurl, { width: 200, title: "a schema collection" }));
+ let addImageNode = action(() => Documents.ImageDocument(imgurl, { width: 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 addClick = (creator: () => Document) => action(() =>
mainfreeform.GetList<Document>(KeyStore.Data, []).push(creator())
);
@@ -107,16 +114,24 @@ Documents.initProtos(mainDocId, (res?: Document) => {
ReactDOM.render((
<div style={{ position: "absolute", width: "100%", height: "100%" }}>
{/* <div id="dash-title">— DASH —</div> */}
-
- <DocumentView Document={mainContainer}
- AddDocument={undefined} RemoveDocument={undefined} ScreenToLocalTransform={() => Transform.Identity}
- ContentScaling={() => 1}
- PanelWidth={() => 0}
- PanelHeight={() => 0}
- isTopMost={true}
- SelectOnLoad={false}
- focus={() => { }}
- ContainingCollectionView={undefined} />
+ <Measure onResize={(r: any) => {
+ mainDocFrame.pwidth = r.entry.width;
+ mainDocFrame.pheight = r.entry.height;
+ }}>
+ {({ measureRef }) =>
+ <div ref={measureRef} style={{ position: "absolute", width: "100%", height: "100%" }}>
+ <DocumentView Document={mainContainer}
+ AddDocument={undefined} RemoveDocument={undefined} ScreenToLocalTransform={() => Transform.Identity}
+ ContentScaling={() => 1}
+ PanelWidth={() => mainDocFrame.pwidth}
+ PanelHeight={() => mainDocFrame.pheight}
+ isTopMost={true}
+ SelectOnLoad={false}
+ focus={() => { }}
+ ContainingCollectionView={undefined} />
+ </div>
+ }
+ </Measure>
<DocumentDecorations />
<ContextMenu />
<button className="clear-db-button" onClick={clearDatabase}>Clear Database</button>
diff --git a/src/client/views/_global_variables.scss b/src/client/views/_global_variables.scss
index a832d9614..44a819b79 100644
--- a/src/client/views/_global_variables.scss
+++ b/src/client/views/_global_variables.scss
@@ -1,23 +1,17 @@
-@import url("https://fonts.googleapis.com/css?family=Roboto+Slab:300,700|Crimson+Text:400,400i,700");
+@import url("https://fonts.googleapis.com/css?family=Noto+Sans:400,700|Crimson+Text:400,400i,700");
// colors
$light-color: #fcfbf7;
-$light-color-secondary: rgb(241,
-239,
-235);
+$light-color-secondary: rgb(241, 239, 235);
$main-accent: #61aaa3;
// $alt-accent: #cdd5ec;
// $alt-accent: #cdeceb;
-$alt-accent: #f9e1db;
-$lighter-alt-accent: rgb(207,
-220,
-240);
+$alt-accent: #59dff7;
+$lighter-alt-accent: rgb(207, 220, 240);
$intermediate-color: #9c9396;
$dark-color: #121721;
// fonts
-$sans-serif: "Noto Sans",
-sans-serif;
+$sans-serif: "Noto Sans", sans-serif;
// $sans-serif: "Roboto Slab", sans-serif;
-$serif: "Crimson Text",
-serif;
+$serif: "Crimson Text", serif;
// misc values
-$border-radius: 0.3em; \ No newline at end of file
+$border-radius: 0.3em;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 6a0404663..94005a4c0 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -150,9 +150,13 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
}
}
componentWillUnmount: () => void = () => {
- this._goldenLayout.unbind('itemDropped', this.itemDropped);
- this._goldenLayout.unbind('tabCreated', this.tabCreated);
- this._goldenLayout.unbind('stackCreated', this.stackCreated);
+ try {
+ this._goldenLayout.unbind('itemDropped', this.itemDropped);
+ this._goldenLayout.unbind('tabCreated', this.tabCreated);
+ this._goldenLayout.unbind('stackCreated', this.stackCreated);
+ } catch (e) {
+
+ }
this._goldenLayout.destroy();
this._goldenLayout = null;
window.removeEventListener('resize', this.onResize);
diff --git a/src/client/views/collections/CollectionFreeFormView.scss b/src/client/views/collections/CollectionFreeFormView.scss
index ffd440310..9c7a03b52 100644
--- a/src/client/views/collections/CollectionFreeFormView.scss
+++ b/src/client/views/collections/CollectionFreeFormView.scss
@@ -9,9 +9,9 @@
//nested freeform views
.collectionfreeformview-container {
- background-image: linear-gradient(to right, $light-color-secondary 1px, transparent 1px),
- linear-gradient(to bottom, $light-color-secondary 1px, transparent 1px);
- background-size: 30px 30px;
+ // background-image: linear-gradient(to right, $light-color-secondary 1px, transparent 1px),
+ // linear-gradient(to bottom, $light-color-secondary 1px, transparent 1px);
+ // background-size: 30px 30px;
}
border: 0px solid $light-color-secondary;
@@ -32,13 +32,6 @@
height: 100%;
}
}
-.collectionfreeformview-marquee{
- border-style: dashed;
- box-sizing: border-box;
- position: absolute;
- border-width: 1px;
- border-color: black;
-}
.collectionfreeformview-overlay {
.collectionfreeformview > .jsx-parser {
position: absolute;
@@ -48,16 +41,27 @@
background: $light-color-secondary;
}
+ position:absolute;
border: 0px solid transparent;
border-radius: $border-radius;
+ overflow: hidden;
box-sizing: border-box;
- position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
- overflow: hidden;
.collectionfreeformview {
+ .collectionfreeformview > .jsx-parser{
+ position:absolute;
+ height: 100%;
+ }
+ .formattedTextBox-cont {
+ background:yellow;
+ }
+
+ // overflow: hidden;
+ // border-style: solid;
+ // box-sizing: border-box;
position: absolute;
top: 0;
left: 0;
diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx
index b0cd7e017..9dc1ae847 100644
--- a/src/client/views/collections/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/CollectionFreeFormView.tsx
@@ -5,46 +5,74 @@ import { FieldWaiting } from "../../../fields/Field";
import { KeyStore } from "../../../fields/KeyStore";
import { ListField } from "../../../fields/ListField";
import { TextField } from "../../../fields/TextField";
-import { Documents } from "../../documents/Documents";
import { DragManager } from "../../util/DragManager";
import { Transform } from "../../util/Transform";
import { undoBatch } from "../../util/UndoManager";
import { CollectionDockingView } from "../collections/CollectionDockingView";
import { CollectionPDFView } from "../collections/CollectionPDFView";
import { CollectionSchemaView } from "../collections/CollectionSchemaView";
+import { CollectionVideoView } from "../collections/CollectionVideoView";
import { CollectionView } from "../collections/CollectionView";
import { InkingCanvas } from "../InkingCanvas";
+import { AudioBox } from "../nodes/AudioBox";
import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
import { DocumentView } from "../nodes/DocumentView";
import { FormattedTextBox } from "../nodes/FormattedTextBox";
import { ImageBox } from "../nodes/ImageBox";
import { KeyValueBox } from "../nodes/KeyValueBox";
import { PDFBox } from "../nodes/PDFBox";
+import { VideoBox } from "../nodes/VideoBox";
import { WebBox } from "../nodes/WebBox";
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 { SelectionManager } from "../../util/SelectionManager";
const JsxParser = require('react-jsx-parser').default;//TODO Why does this need to be imported like this?
@observer
export class CollectionFreeFormView extends CollectionViewBase {
- private _canvasRef = React.createRef<HTMLDivElement>();
- @observable
- private _lastX: number = 0;
- @observable
- private _lastY: number = 0;
+ public _canvasRef = React.createRef<HTMLDivElement>();
private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type)
- @observable
- private _downX: number = 0;
- @observable
- private _downY: number = 0;
+ public addLiveTextBox = (newBox: Document) => {
+ // mark this collection so that when the text box is created we can send it the SelectOnLoad prop to focus itself
+ this._selectOnLoaded = newBox.Id;
+ //set text to be the typed key and get focus on text box
+ this.props.addDocument(newBox);
+ //remove cursor from screen
+ this.PreviewCursorVisible = false;
+ }
+
+ public selectDocuments = (docs: Document[]) => {
+ this.props.CollectionView.SelectedDocs.length = 0;
+ docs.map(d => this.props.CollectionView.SelectedDocs.push(d.Id));
+ }
+
+ public getActiveDocuments = () => {
+ var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
+ const lvalue = this.props.Document.GetT<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;
+ }
//determines whether the blinking cursor for indicating whether a text will be made on key down is visible
- @observable
- private _previewCursorVisible: boolean = false;
+ @observable public PreviewCursorVisible: boolean = false;
+ @observable public MarqueeVisible = false;
+ @observable public DownX: number = 0;
+ @observable public DownY: number = 0;
+ @observable private _lastX: number = 0;
+ @observable private _lastY: number = 0;
@computed get panX(): number { return this.props.Document.GetNumber(KeyStore.PanX, 0) }
@computed get panY(): number { return this.props.Document.GetNumber(KeyStore.PanY, 0) }
@@ -72,117 +100,68 @@ export class CollectionFreeFormView extends CollectionViewBase {
}
}
- @observable
- _marquee = false;
+
+ @action
+ cleanupInteractions = () => {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ this.MarqueeVisible = false;
+ }
@action
onPointerDown = (e: React.PointerEvent): void => {
- if (((e.button === 2 && this.props.active()) || !e.defaultPrevented) && !e.shiftKey &&
- (!this.isAnnotationOverlay || this.zoomScaling != 1 || e.button == 0)) {
+ this.PreviewCursorVisible = false;
+ if ((e.button === 2 && this.props.active() && (!this.isAnnotationOverlay || this.zoomScaling != 1)) || e.button == 0) {
document.removeEventListener("pointermove", this.onPointerMove);
document.addEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
document.addEventListener("pointerup", this.onPointerUp);
- this._lastX = e.pageX;
- this._lastY = e.pageY;
- this._downX = e.pageX;
- this._downY = e.pageY;
+ this._lastX = this.DownX = e.pageX;
+ this._lastY = this.DownY = e.pageY;
}
}
@action
onPointerUp = (e: PointerEvent): void => {
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
e.stopPropagation();
- if (this._marquee) {
- if (!e.shiftKey) {
- SelectionManager.DeselectAll();
- }
- var selectedDocs = this.marqueeSelect();
- selectedDocs.map(s => this.props.CollectionView.SelectedDocs.push(s.Id));
- this._marquee = false;
- }
- else if (!this._marquee && Math.abs(this._downX - e.clientX) < 3 && Math.abs(this._downY - e.clientY) < 3) {
+ if (Math.abs(this.DownX - e.clientX) < 4 && Math.abs(this.DownY - e.clientY) < 4) {
//show preview text cursor on tap
- this._previewCursorVisible = true;
+ this.PreviewCursorVisible = true;
//select is not already selected
if (!this.props.isSelected()) {
this.props.select(false);
}
}
-
- }
-
- intersectRect(r1: { left: number, right: number, top: number, bottom: number },
- r2: { left: number, right: number, top: number, bottom: number }) {
- return !(r2.left > r1.right ||
- r2.right < r1.left ||
- r2.top > r1.bottom ||
- r2.bottom < r1.top);
- }
-
- marqueeSelect() {
- this.props.CollectionView.SelectedDocs.length = 0;
- var curPage = this.props.Document.GetNumber(KeyStore.CurPage, 1);
- let p = this.getTransform().transformPoint(this._downX, this._downY);
- let v = this.getTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
- let selRect = { left: p[0], top: p[1], right: p[0] + v[0], bottom: p[1] + v[1] }
-
- var curPage = this.props.Document.GetNumber(KeyStore.CurPage, 1);
- const lvalue = this.props.Document.GetT<ListField<Document>>(this.props.fieldKey, ListField);
- let selection: Document[] = [];
- if (lvalue && lvalue != FieldWaiting) {
- lvalue.Data.map(doc => {
- var page = doc.GetNumber(KeyStore.Page, 0);
- if (page == curPage || page == 0) {
- 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, right: x + w, bottom: y + h }, selRect))
- selection.push(doc)
- }
- })
- }
- return selection;
+ this.cleanupInteractions();
}
@action
onPointerMove = (e: PointerEvent): void => {
if (!e.cancelBubble && this.props.active()) {
- e.stopPropagation();
- e.preventDefault();
- let wasMarquee = this._marquee;
- this._marquee = e.buttons != 2;
- if (this._marquee && !wasMarquee) {
- document.addEventListener("keydown", this.marqueeCommand);
+ if (e.buttons == 1 && !e.altKey && !e.metaKey) {
+ this.MarqueeVisible = true;
}
-
- if (!this._marquee) {
+ if (this.MarqueeVisible) {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ else if ((!this.isAnnotationOverlay || this.zoomScaling != 1) && !e.shiftKey) {
let x = this.props.Document.GetNumber(KeyStore.PanX, 0);
let y = this.props.Document.GetNumber(KeyStore.PanY, 0);
let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
- this._previewCursorVisible = false;
this.SetPan(x - dx, y - dy);
+ this._lastX = e.pageX;
+ this._lastY = e.pageY;
+ e.stopPropagation();
+ e.preventDefault();
}
}
- this._lastX = e.pageX;
- this._lastY = e.pageY;
- }
-
- @action
- marqueeCommand = (e: KeyboardEvent) => {
- if (e.key == "Backspace") {
- this.marqueeSelect().map(d => this.props.removeDocument(d));
- }
- if (e.key == "c") {
- }
}
@action
onPointerWheel = (e: React.WheelEvent): void => {
+ this.props.select(false);
e.stopPropagation();
e.preventDefault();
let coefficient = 1000;
@@ -218,7 +197,6 @@ export class CollectionFreeFormView extends CollectionViewBase {
@action
private SetPan(panX: number, panY: number) {
var x1 = this.getLocalTransform().inverse().Scale;
- var x2 = this.getTransform().inverse().Scale;
const newPanX = Math.min((1 - 1 / x1) * this.nativeWidth, Math.max(0, panX));
const newPanY = Math.min((1 - 1 / x1) * this.nativeHeight, Math.max(0, panY));
this.props.Document.SetNumber(KeyStore.PanX, this.isAnnotationOverlay ? newPanX : panX);
@@ -235,24 +213,6 @@ export class CollectionFreeFormView extends CollectionViewBase {
}
@action
- onKeyDown = (e: React.KeyboardEvent<Element>) => {
- //if not these keys, make a textbox if preview cursor is active!
- if (!e.ctrlKey && !e.altKey) {
- if (this._previewCursorVisible) {
- //make textbox and add it to this collection
- let [x, y] = this.getTransform().transformPoint(this._downX, this._downY); (this._downX, this._downY);
- let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "new" });
- // mark this collection so that when the text box is created we can send it the SelectOnLoad prop to focus itself
- this._selectOnLoaded = newBox.Id;
- //set text to be the typed key and get focus on text box
- this.props.CollectionView.addDocument(newBox);
- //remove cursor from screen
- this._previewCursorVisible = false;
- }
- }
- }
-
- @action
bringToFront(doc: Document) {
const { fieldKey: fieldKey, Document: Document } = this.props;
@@ -293,7 +253,7 @@ export class CollectionFreeFormView extends CollectionViewBase {
@computed
get views() {
- var curPage = this.props.Document.GetNumber(KeyStore.CurPage, 1);
+ 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 => {
@@ -320,7 +280,7 @@ export class CollectionFreeFormView extends CollectionViewBase {
get backgroundView() {
return !this.backgroundLayout ? (null) :
(<JsxParser
- components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, WebBox, KeyValueBox, PDFBox }}
+ components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox }}
bindings={this.props.bindings}
jsx={this.backgroundLayout}
showWarnings={true}
@@ -331,7 +291,7 @@ export class CollectionFreeFormView extends CollectionViewBase {
get overlayView() {
return !this.overlayLayout ? (null) :
(<JsxParser
- components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, WebBox, KeyValueBox, PDFBox }}
+ components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox }}
bindings={this.props.bindings}
jsx={this.overlayLayout}
showWarnings={true}
@@ -340,28 +300,17 @@ export class CollectionFreeFormView extends CollectionViewBase {
}
getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform())
+ getMarqueeTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH)
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 = (e: React.FocusEvent<HTMLDivElement>): void => {
- this._previewCursorVisible = false;
+ onBlur = (): void => {
+ this.PreviewCursorVisible = false;
}
render() {
- //determines whether preview text cursor should be visible (ie when user taps this collection it should)
- let cursor = null;
- if (this._previewCursorVisible) {
- //get local position and place cursor there!
- let [x, y] = this.getTransform().transformPoint(this._downX, this._downY);
- cursor = <div id="prevCursor" onKeyPress={this.onKeyDown} style={{ color: "black", position: "absolute", transformOrigin: "left top", transform: `translate(${x}px, ${y}px)` }}>I</div>
- }
-
- let p = this.getTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY);
- let v = this.getTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
- var marquee = this._marquee ? <div className="collectionfreeformview-marquee" style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}` }}></div> : (null);
-
let [dx, dy] = [this.centeringShiftX, this.centeringShiftY];
const panx: number = -this.props.Document.GetNumber(KeyStore.PanX, 0);
@@ -370,7 +319,6 @@ export class CollectionFreeFormView extends CollectionViewBase {
return (
<div className={`collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`}
onPointerDown={this.onPointerDown}
- onKeyPress={this.onKeyDown}
onWheel={this.onPointerWheel}
onDrop={this.onDrop.bind(this)}
onDragOver={this.onDragOver}
@@ -383,12 +331,14 @@ export class CollectionFreeFormView extends CollectionViewBase {
ref={this._canvasRef}>
{this.backgroundView}
<InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} />
- {cursor}
+ <PreviewCursor container={this} addLiveTextDocument={this.addLiveTextBox} getTransform={this.getTransform} />
{this.views}
- {marquee}
</div>
+ <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}
</div>
);
}
-}
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionPDFView.scss b/src/client/views/collections/CollectionPDFView.scss
new file mode 100644
index 000000000..0144625c1
--- /dev/null
+++ b/src/client/views/collections/CollectionPDFView.scss
@@ -0,0 +1,27 @@
+.collectionPdfView-buttonTray {
+ top : 25px;
+ left : 20px;
+ position: relative;
+ transform-origin: left top;
+ position: absolute;
+}
+.collectionPdfView-cont{
+ width: 100%;
+ height: 100%;
+ position: absolute;
+
+}
+.collectionPdfView-backward {
+ color : white;
+ top :0px;
+ left : 0px;
+ position: absolute;
+ background-color: rgba(50, 50, 50, 0.2);
+}
+.collectionPdfView-forward {
+ color : white;
+ top :0px;
+ left : 35px;
+ position: absolute;
+ background-color: rgba(50, 50, 50, 0.2);
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index f22c07060..124d82c8b 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -1,10 +1,11 @@
-import { action, computed } from "mobx";
+import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
import { Document } from "../../../fields/Document";
import { KeyStore } from "../../../fields/KeyStore";
import { ContextMenu } from "../ContextMenu";
import { CollectionView, CollectionViewType } from "./CollectionView";
import { CollectionViewProps } from "./CollectionViewBase";
+import "./CollectionPDFView.scss"
import React = require("react");
import { FieldId } from "../../../fields/Field";
@@ -18,22 +19,23 @@ export class CollectionPDFView extends React.Component<CollectionViewProps> {
isTopMost={isTopMost} SelectOnLoad={selectOnLoad} BackgroundView={BackgroundView} focus={focus}/>`;
}
- public SelectedDocs: FieldId[] = []
- @action onPageBack = () => this.curPage > 1 ? this.props.Document.SetNumber(KeyStore.CurPage, this.curPage - 1) : 0;
- @action onPageForward = () => this.curPage < this.numPages ? this.props.Document.SetNumber(KeyStore.CurPage, this.curPage + 1) : 0;
+ private get curPage() { return this.props.Document.GetNumber(KeyStore.CurPage, -1); }
+ private get numPages() { return this.props.Document.GetNumber(KeyStore.NumPages, 0); }
+ @action onPageBack = () => this.curPage > 1 ? this.props.Document.SetNumber(KeyStore.CurPage, this.curPage - 1) : -1;
+ @action onPageForward = () => this.curPage < this.numPages ? this.props.Document.SetNumber(KeyStore.CurPage, this.curPage + 1) : -1;
- @computed private get curPage() { return this.props.Document.GetNumber(KeyStore.CurPage, 0); }
- @computed private get numPages() { return this.props.Document.GetNumber(KeyStore.NumPages, 0); }
- @computed private get uIButtons() {
+ private get uIButtons() {
+ let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().transformDirection(1, 1)[0]);
return (
- <div className="pdfBox-buttonTray" key="tray">
- <button className="pdfButton" onClick={this.onPageBack}>{"<"}</button>
- <button className="pdfButton" onClick={this.onPageForward}>{">"}</button>
+ <div className="collectionPdfView-buttonTray" key="tray" style={{ transform: `scale(${scaling}, ${scaling})` }}>
+ <button className="collectionPdfView-backward" onClick={this.onPageBack}>{"<"}</button>
+ <button className="collectionPdfView-forward" onClick={this.onPageForward}>{">"}</button>
</div>);
}
// "inherited" CollectionView API starts here...
-
+ @observable
+ public SelectedDocs: FieldId[] = []
public active: () => boolean = () => CollectionView.Active(this);
addDocument = (doc: Document): void => { CollectionView.AddDocument(this.props, doc); }
@@ -49,7 +51,7 @@ export class CollectionPDFView extends React.Component<CollectionViewProps> {
get subView(): any { return CollectionView.SubView(this); }
render() {
- return (<div className="collectionView-cont" onContextMenu={this.specificContextMenu}>
+ return (<div className="collectionPdfView-cont" onContextMenu={this.specificContextMenu}>
{this.subView}
{this.props.isSelected() ? this.uIButtons : (null)}
</div>)
diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss
index b66fc7981..0d615dc01 100644
--- a/src/client/views/collections/CollectionSchemaView.scss
+++ b/src/client/views/collections/CollectionSchemaView.scss
@@ -9,6 +9,12 @@
height: 100%;
overflow: hidden;
+.collectionSchemaView-content {
+ position: absolute;
+ height:100%;
+ width:100%;
+ overflow:auto;
+}
.collectionSchemaView-previewRegion {
position: relative;
background: $light-color;
@@ -51,7 +57,6 @@
overflow-y: auto;
overflow-x: auto;
height: 100%;
-
display: -webkit-inline-box;
direction: ltr;
// direction:rtl;
@@ -77,6 +82,11 @@
max-width: 100%;
height: 100%;
}
+ .videobox-cont {
+ object-fit: contain;
+ width:auto;
+ height: 100%;
+ }
}
}
.ReactTable .rt-thead.-header {
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 37620cd7c..8c1aeef2c 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -1,5 +1,5 @@
import React = require("react")
-import { action, observable } from "mobx";
+import { action, observable, computed } from "mobx";
import { observer } from "mobx-react";
import Measure from "react-measure";
import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults } from "react-table";
@@ -31,7 +31,7 @@ export class CollectionSchemaView extends CollectionViewBase {
@observable _panelWidth = 0;
@observable _panelHeight = 0;
@observable _selectedIndex = 0;
- @observable _splitPercentage: number = 100;
+ @computed get splitPercentage() { return this.props.Document.GetNumber(KeyStore.SchemaSplitPercentage, 0); }
renderCell = (rowProps: CellInfo) => {
let props: FieldViewProps = {
@@ -49,7 +49,7 @@ export class CollectionSchemaView extends CollectionViewBase {
let reference = React.createRef<HTMLDivElement>();
let onItemDown = setupDrag(reference, () => props.doc);
return (
- <div onPointerDown={onItemDown} key={props.doc.Id} ref={reference}>
+ <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} style={{ height: "36px" }} key={props.doc.Id} ref={reference}>
<EditableView contents={contents}
height={36} GetValue={() => {
let field = props.doc.Get(props.fieldKey);
@@ -89,7 +89,8 @@ export class CollectionSchemaView extends CollectionViewBase {
return {
onClick: action((e: React.MouseEvent, handleOriginal: Function) => {
that._selectedIndex = rowInfo.index;
- this._splitPercentage += 0.05; // bcz - ugh - needed to force Measure to do its thing and call onResize
+ // bcz - ugh - needed to force Measure to do its thing and call onResize
+ this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage - 0.05)
if (handleOriginal) {
handleOriginal()
@@ -106,18 +107,18 @@ export class CollectionSchemaView extends CollectionViewBase {
@action
onDividerMove = (e: PointerEvent): void => {
let nativeWidth = this._mainCont.current!.getBoundingClientRect();
- this._splitPercentage = Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100);
+ this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, 100 - Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100));
}
@action
onDividerUp = (e: PointerEvent): void => {
document.removeEventListener("pointermove", this.onDividerMove);
document.removeEventListener('pointerup', this.onDividerUp);
- if (this._startSplitPercent == this._splitPercentage) {
- this._splitPercentage = this._splitPercentage == 1 ? 66 : 100;
+ if (this._startSplitPercent == this.splitPercentage) {
+ this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage == 0 ? 33 : 0);
}
}
onDividerDown = (e: React.PointerEvent) => {
- this._startSplitPercent = this._splitPercentage;
+ this._startSplitPercent = this.splitPercentage;
e.stopPropagation();
e.preventDefault();
document.addEventListener("pointermove", this.onDividerMove);
@@ -134,12 +135,12 @@ export class CollectionSchemaView extends CollectionViewBase {
e.preventDefault();
document.removeEventListener("pointermove", this.onExpanderMove);
document.removeEventListener('pointerup', this.onExpanderUp);
- if (this._startSplitPercent == this._splitPercentage) {
- this._splitPercentage = this._splitPercentage == 100 ? 66 : 100;
+ if (this._startSplitPercent == this.splitPercentage) {
+ this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage == 0 ? 33 : 0);
}
}
onExpanderDown = (e: React.PointerEvent) => {
- this._startSplitPercent = this._splitPercentage;
+ this._startSplitPercent = this.splitPercentage;
e.stopPropagation();
e.preventDefault();
document.addEventListener("pointermove", this.onExpanderMove);
@@ -203,7 +204,7 @@ export class CollectionSchemaView extends CollectionViewBase {
)
let previewHandle = !this.props.active() ? (null) : (
<div className="collectionSchemaView-previewHandle" onPointerDown={this.onExpanderDown} />);
- let dividerDragger = this._splitPercentage == 100 ? (null) :
+ let dividerDragger = this.splitPercentage == 0 ? (null) :
<div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} style={{ width: `${this.DIVIDER_WIDTH}px` }} />
return (
<div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} ref={this._mainCont} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} >
@@ -213,7 +214,8 @@ export class CollectionSchemaView extends CollectionViewBase {
this._panelHeight = r.entry.height;
})}>
{({ measureRef }) =>
- <div ref={measureRef} className="collectionSchemaView-tableContainer" style={{ width: `${this._splitPercentage}%` }}>
+ <div ref={measureRef} className="collectionSchemaView-tableContainer"
+ style={{ width: `calc(100% - ${this.splitPercentage}%)` }}>
<ReactTable
data={children}
pageSize={children.length}
@@ -235,7 +237,7 @@ export class CollectionSchemaView extends CollectionViewBase {
}
</Measure>
{dividerDragger}
- <div className="collectionSchemaView-previewRegion" style={{ width: `calc(${100 - this._splitPercentage}% - ${this.DIVIDER_WIDTH}px)` }}>
+ <div className="collectionSchemaView-previewRegion" style={{ width: `calc(${this.props.Document.GetNumber(KeyStore.SchemaSplitPercentage, 0)}% - ${this.DIVIDER_WIDTH}px)` }}>
{content}
</div>
{previewHandle}
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index c2b376a34..fa0f1c761 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -1,62 +1,61 @@
@import "../global_variables";
-
#body {
- padding: 20px;
- background: $light-color-secondary;
- font-size: 13px;
- overflow: scroll;
+ padding: 20px;
+ background: $light-color-secondary;
+ font-size: 13px;
+ overflow: scroll;
}
ul {
- list-style: none;
- padding-left: 20px;
+ list-style: none;
+ padding-left: 20px;
}
li {
- margin: 5px 0;
+ margin: 5px 0;
}
.collection-child {
- margin-top: 10px;
- margin-bottom: 10px;
+ margin-top: 10px;
+ margin-bottom: 10px;
}
.no-indent {
- padding-left: 0;
+ padding-left: 0;
}
.bullet {
- width: 1.5em;
- display: inline-block;
- color: $intermediate-color;
+ width: 1.5em;
+ display: inline-block;
+ color: $intermediate-color;
}
.coll-title {
- font-size: 24px;
- margin-bottom: 20px;
+ font-size: 24px;
+ margin-bottom: 20px;
}
.collectionTreeView-dropTarget {
- border: 0px solid transparent;
- border-radius: $border-radius;
- box-sizing: border-box;
- height: 100%;
+ border: 0px solid transparent;
+ border-radius: $border-radius;
+ box-sizing: border-box;
+ height: 100%;
}
.docContainer {
- display: inline-table;
+ display: inline-table;
}
.docContainer:hover {
- .delete-button {
- display: inline;
- }
+ .delete-button {
+ display: inline;
+ }
}
.delete-button {
- color: $intermediate-color;
- float: right;
- margin-left: 15px;
- margin-top: 3px;
- display: none;
-}
+ color: $intermediate-color;
+ float: right;
+ margin-left: 15px;
+ margin-top: 3px;
+ display: none;
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionVideoView.scss b/src/client/views/collections/CollectionVideoView.scss
new file mode 100644
index 000000000..cbb981b13
--- /dev/null
+++ b/src/client/views/collections/CollectionVideoView.scss
@@ -0,0 +1,40 @@
+
+.collectionVideoView-cont{
+ width: 100%;
+ height: 100%;
+ position: absolute;
+
+}
+.collectionVideoView-time{
+ color : white;
+ top :25px;
+ left : 25px;
+ position: absolute;
+ background-color: rgba(50, 50, 50, 0.2);
+ transform-origin: left top;
+}
+.collectionVideoView-play {
+ width: 25px;
+ height: 20px;
+ bottom: 25px;
+ left : 25px;
+ position: absolute;
+ color : white;
+ background-color: rgba(50, 50, 50, 0.2);
+ border-radius: 4px;
+ text-align: center;
+ transform-origin: left bottom;
+}
+.collectionVideoView-full {
+ width: 25px;
+ height: 20px;
+ bottom: 25px;
+ right : 25px;
+ position: absolute;
+ color : white;
+ background-color: rgba(50, 50, 50, 0.2);
+ border-radius: 4px;
+ text-align: center;
+ transform-origin: right bottom;
+
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
new file mode 100644
index 000000000..b64ef3c07
--- /dev/null
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -0,0 +1,118 @@
+import { action, computed, observable } from "mobx";
+import { observer } from "mobx-react";
+import { Document } from "../../../fields/Document";
+import { KeyStore } from "../../../fields/KeyStore";
+import { ContextMenu } from "../ContextMenu";
+import { CollectionView, CollectionViewType } from "./CollectionView";
+import { CollectionViewProps } from "./CollectionViewBase";
+import React = require("react");
+import { FieldId } from "../../../fields/Field";
+import "./CollectionVideoView.scss"
+
+
+@observer
+export class CollectionVideoView extends React.Component<CollectionViewProps> {
+
+ 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>
+ </div>,
+ <div className="collectionVideoView-play" key="play" onPointerDown={this.onPlayDown} style={{ transform: `scale(${scaling}, ${scaling})` }}>
+ {this.playing ? "\"" : ">"}
+ </div>,
+ <div className="collectionVideoView-full" key="full" onPointerDown={this.onFullDown} style={{ transform: `scale(${scaling}, ${scaling})` }}>
+ F
+ </div>
+ ]);
+ }
+
+
+ // "inherited" CollectionView API starts here...
+
+ @observable
+ public SelectedDocs: FieldId[] = []
+ public active: () => boolean = () => CollectionView.Active(this);
+
+ addDocument = (doc: Document): void => { CollectionView.AddDocument(this.props, doc); }
+ 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); }
+
+ componentDidMount() {
+ this.updateTimecode();
+ }
+
+ get player(): HTMLVideoElement | undefined {
+ return this._mainCont.current ? this._mainCont.current.getElementsByTagName("video")[0] : undefined;
+ }
+
+ @action
+ updateTimecode = () => {
+ if (this.player) {
+ this.ctime = this.player.currentTime;
+ this.props.Document.SetNumber(KeyStore.CurPage, Math.round(this.ctime));
+ }
+ 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;
+ } else {
+ this.player.pause();
+ this.playing = false;
+ }
+ }
+ }
+ @action
+ onFullDown = (e: React.PointerEvent) => {
+ if (this.player) {
+ this.player.requestFullscreen();
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ }
+
+ @action
+ onResetDown = () => {
+ if (this.player) {
+ this.player.pause();
+ this.player.currentTime = 0;
+ }
+
+ }
+
+ render() {
+ return (<div className="collectionVideoView-cont" ref={this._mainCont} onContextMenu={this.specificContextMenu}>
+ {this.subView}
+ {this.uIButtons}
+ </div>)
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 7320e873f..d9b2722a6 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -27,15 +27,14 @@ export const COLLECTION_BORDER_WIDTH = 1;
@observer
export class CollectionView extends React.Component<CollectionViewProps> {
- @observable
- public SelectedDocs: FieldId[] = [];
-
public static LayoutString(fieldKey: string = "DataKey") {
return `<${CollectionView.name} Document={Document}
ScreenToLocalTransform={ScreenToLocalTransform} fieldKey={${fieldKey}} panelWidth={PanelWidth} panelHeight={PanelHeight} isSelected={isSelected} select={select} bindings={bindings}
isTopMost={isTopMost} SelectOnLoad={selectOnLoad} BackgroundView={BackgroundView} focus={focus}/>`;
}
+ @observable
+ public SelectedDocs: FieldId[] = [];
public active: () => boolean = () => CollectionView.Active(this);
addDocument = (doc: Document): void => { CollectionView.AddDocument(this.props, doc); }
removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); }
@@ -50,7 +49,7 @@ export class CollectionView extends React.Component<CollectionViewProps> {
@action
public static AddDocument(props: CollectionViewProps, doc: Document) {
- doc.SetNumber(KeyStore.Page, props.Document.GetNumber(KeyStore.CurPage, 0));
+ doc.SetNumber(KeyStore.Page, props.Document.GetNumber(KeyStore.CurPage, -1));
if (props.Document.Get(props.fieldKey) instanceof Field) {
//TODO This won't create the field if it doesn't already exist
const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>())
@@ -99,7 +98,6 @@ export class CollectionView extends React.Component<CollectionViewProps> {
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) })
- ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) })
}
}
diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx
index b126b40a9..d9598aa72 100644
--- a/src/client/views/collections/CollectionViewBase.tsx
+++ b/src/client/views/collections/CollectionViewBase.tsx
@@ -47,6 +47,7 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
protected drop(e: Event, de: DragManager.DropEvent) {
const docView: DocumentView = de.data["documentView"];
const doc: Document = de.data["document"];
+
if (docView && (!docView.props.ContainingCollectionView || docView.props.ContainingCollectionView !== this.props.CollectionView)) {
if (docView.props.RemoveDocument) {
docView.props.RemoveDocument(docView.props.Document);
@@ -61,12 +62,17 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
@action
protected onDrop(e: React.DragEvent, options: DocumentOptions): void {
- e.stopPropagation()
- e.preventDefault()
let that = this;
let html = e.dataTransfer.getData("text/html");
let text = e.dataTransfer.getData("text/plain");
+
+ if (text && text.startsWith("<div")) {
+ return;
+ }
+ e.stopPropagation()
+ e.preventDefault()
+
if (html && html.indexOf("<img") != 0) {
console.log("not good");
let htmlDoc = Documents.HtmlDocument(html, { ...options, width: 300, height: 300 });
@@ -81,21 +87,7 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
const upload = window.location.origin + "/upload";
let item = e.dataTransfer.items[i];
if (item.kind === "string" && item.type.indexOf("uri") != -1) {
- e.dataTransfer.items[i].getAsString(function (s) {
- action(() => {
- var img = Documents.ImageDocument(s, { ...options, nativeWidth: 300, width: 300, })
-
- let docs = that.props.Document.GetT(KeyStore.Data, ListField);
- if (docs != FieldWaiting) {
- if (!docs) {
- docs = new ListField<Document>();
- that.props.Document.Set(KeyStore.Data, docs)
- }
- docs.Data.push(img);
- }
- })()
-
- })
+ e.dataTransfer.items[i].getAsString(action((s: string) => this.props.addDocument(Documents.WebDocument(s, options))))
}
let type = item.type
console.log(type)
@@ -122,7 +114,7 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
var doc: any;
if (type.indexOf("image") !== -1) {
- doc = Documents.ImageDocument(path, { ...options, nativeWidth: 300, width: 300, })
+ doc = Documents.ImageDocument(path, { ...options, nativeWidth: 200, width: 200, })
}
if (type.indexOf("video") !== -1) {
doc = Documents.VideoDocument(path, { ...options, nativeWidth: 300, width: 300, })
@@ -130,6 +122,9 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
if (type.indexOf("audio") !== -1) {
doc = Documents.AudioDocument(path, { ...options, nativeWidth: 300, width: 300, })
}
+ if (type.indexOf("pdf") !== -1) {
+ doc = Documents.PdfDocument(path, { ...options, nativeWidth: 300, width: 300, })
+ }
let docs = that.props.Document.GetT(KeyStore.Data, ListField);
if (docs != FieldWaiting) {
if (!docs) {
diff --git a/src/client/views/collections/MarqueeView.scss b/src/client/views/collections/MarqueeView.scss
new file mode 100644
index 000000000..6d9a79344
--- /dev/null
+++ b/src/client/views/collections/MarqueeView.scss
@@ -0,0 +1,8 @@
+
+.marqueeView {
+ border-style: dashed;
+ box-sizing: border-box;
+ position: absolute;
+ border-width: 1px;
+ border-color: black;
+} \ No newline at end of file
diff --git a/src/client/views/collections/MarqueeView.tsx b/src/client/views/collections/MarqueeView.tsx
new file mode 100644
index 000000000..65aaa837f
--- /dev/null
+++ b/src/client/views/collections/MarqueeView.tsx
@@ -0,0 +1,164 @@
+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) => 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(() => {
+ this.props.addDocument(Documents.FreeformDocument(selected, { x: bounds.left, y: bounds.top, panx: 0, pany: 0, width: bounds.width, backgroundColor: "Transparent", height: bounds.height, ink: liftedInk, title: "a nested collection" }));
+ // }, 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) {
+ 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
new file mode 100644
index 000000000..a797411f6
--- /dev/null
+++ b/src/client/views/collections/PreviewCursor.scss
@@ -0,0 +1,18 @@
+
+.previewCursor {
+ color: black;
+ position: absolute;
+ transform-origin: left top;
+ pointer-events: none;
+}
+
+//this is an animation for the blinking cursor!
+@keyframes blink {
+ 0% {opacity: 0}
+ 49%{opacity: 0}
+ 50% {opacity: 1}
+}
+
+#previewCursor {
+ animation: blink 1s infinite;
+} \ No newline at end of file
diff --git a/src/client/views/collections/PreviewCursor.tsx b/src/client/views/collections/PreviewCursor.tsx
new file mode 100644
index 000000000..a1411250a
--- /dev/null
+++ b/src/client/views/collections/PreviewCursor.tsx
@@ -0,0 +1,78 @@
+import { trace } from "mobx";
+import "./PreviewCursor.scss";
+import React = require("react");
+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 { ListField } from "../../../fields/ListField";
+import { Documents } from "../../documents/Documents";
+import { SelectionManager } from "../../util/SelectionManager";
+import { Transform } from "../../util/Transform";
+import { CollectionFreeFormView } from "./CollectionFreeFormView";
+
+
+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();
+ e.preventDefault();
+ }
+ }
+
+ 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/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
new file mode 100644
index 000000000..55b4938a0
--- /dev/null
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -0,0 +1,35 @@
+import { Document } from "../../../fields/Document";
+import { CollectionFreeFormView } from "../collections/CollectionFreeFormView";
+import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { CollectionSchemaView } from "../collections/CollectionSchemaView";
+import { CollectionView, CollectionViewType } from "../collections/CollectionView";
+import { CollectionPDFView } from "../collections/CollectionPDFView";
+import { CollectionVideoView } from "../collections/CollectionVideoView";
+import { FormattedTextBox } from "../nodes/FormattedTextBox";
+import { ImageBox } from "../nodes/ImageBox";
+import { VideoBox } from "../nodes/VideoBox";
+import { AudioBox } from "../nodes/AudioBox";
+import { KeyValueBox } from "./KeyValueBox"
+import { WebBox } from "../nodes/WebBox";
+import { PDFBox } from "../nodes/PDFBox";
+import "./DocumentView.scss";
+import React = require("react");
+const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
+
+interface JsxBindings {
+ Document: Document;
+ layout: string;
+ [prop: string]: any;
+}
+
+export class DocumentContentsView extends React.PureComponent<JsxBindings> {
+ render() {
+ return <JsxParser
+ components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox }}
+ bindings={this.props}
+ jsx={this.props.layout}
+ showWarnings={true}
+ onError={(test: any) => { console.log(test) }}
+ />
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index dc793c16d..7a43c34d0 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,31 +1,23 @@
-import { action, computed, IReactionDisposer, runInAction, reaction } from "mobx";
+import { action, computed, IReactionDisposer, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import { Document } from "../../../fields/Document";
import { Field, FieldWaiting, Opt } from "../../../fields/Field";
import { Key } from "../../../fields/Key";
import { KeyStore } from "../../../fields/KeyStore";
import { ListField } from "../../../fields/ListField";
+import { TextField } from "../../../fields/TextField";
+import { Documents } from "../../documents/Documents";
+import { DocumentManager } from "../../util/DocumentManager";
import { DragManager } from "../../util/DragManager";
import { SelectionManager } from "../../util/SelectionManager";
import { Transform } from "../../util/Transform";
import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { CollectionFreeFormView } from "../collections/CollectionFreeFormView";
-import { CollectionSchemaView } from "../collections/CollectionSchemaView";
import { CollectionView, CollectionViewType } from "../collections/CollectionView";
-import { CollectionPDFView } from "../collections/CollectionPDFView";
import { ContextMenu } from "../ContextMenu";
-import { FormattedTextBox } from "../nodes/FormattedTextBox";
-import { ImageBox } from "../nodes/ImageBox";
-import { VideoBox } from "../nodes/VideoBox";
-import { AudioBox } from "../nodes/AudioBox";
-import { Documents } from "../../documents/Documents"
-import { KeyValueBox } from "./KeyValueBox"
-import { WebBox } from "../nodes/WebBox";
-import { PDFBox } from "../nodes/PDFBox";
import "./DocumentView.scss";
import React = require("react");
-import { TextField } from "../../../fields/TextField";
-import { DocumentManager } from "../../util/DocumentManager";
+import { DocumentContentsView } from "./DocumentContentsView";
+import { Utils } from "../../../Utils";
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
@@ -87,7 +79,6 @@ export function FakeJsxArgs(keys: string[], fields: string[] = []): JsxArgs {
@observer
export class DocumentView extends React.Component<DocumentViewProps> {
private _mainCont = React.createRef<HTMLDivElement>();
- private _documentBindings: any = null;
private _downX: number = 0;
private _downY: number = 0;
private _reactionDisposer: Opt<IReactionDisposer>;
@@ -182,7 +173,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
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) {
+ if (!this.topMost || e.buttons == 2 || e.altKey) {
this.startDragging(e.x, e.y);
}
}
@@ -197,6 +188,9 @@ export class DocumentView extends React.Component<DocumentViewProps> {
SelectionManager.SelectDoc(this, e.ctrlKey);
}
}
+ stopPropogation = (e: React.SyntheticEvent) => {
+ e.stopPropagation();
+ }
deleteClicked = (): void => {
if (this.props.RemoveDocument) {
@@ -246,11 +240,23 @@ export class DocumentView extends React.Component<DocumentViewProps> {
destDoc.GetOrCreateAsync(KeyStore.LinkedFromDocs, ListField, field => { (field as ListField<Document>).Data.push(linkDoc) });
linkDoc.Set(KeyStore.LinkedFromDocs, sourceDoc);
-
-
e.stopPropagation();
}
+ onDrop = (e: React.DragEvent) => {
+ if (e.isDefaultPrevented()) {
+ return;
+ }
+ let text = e.dataTransfer.getData("text/plain");
+ if (text && text.startsWith("<div")) {
+ let oldLayout = this.props.Document.GetText(KeyStore.Layout, "");
+ let layout = text.replace("{layout}", oldLayout);
+ this.props.Document.SetText(KeyStore.Layout, layout);
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ }
+
@action
onContextMenu = (e: React.MouseEvent): void => {
e.stopPropagation();
@@ -265,6 +271,12 @@ export class DocumentView extends React.Component<DocumentViewProps> {
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 ID",
+ event: () => {
+ Utils.CopyText(this.props.Document.Id);
+ }
+ });
//ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) })
ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
if (!this.topMost) {
@@ -277,15 +289,6 @@ export class DocumentView extends React.Component<DocumentViewProps> {
SelectionManager.SelectDoc(this, e.ctrlKey);
}
- get mainContent() {
- return <JsxParser
- components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, WebBox, KeyValueBox, VideoBox, AudioBox, PDFBox }}
- bindings={this._documentBindings}
- jsx={this.layout}
- showWarnings={true}
- onError={(test: any) => { console.log(test) }}
- />
- }
isSelected = () => {
return SelectionManager.IsSelected(this);
@@ -295,40 +298,52 @@ export class DocumentView extends React.Component<DocumentViewProps> {
SelectionManager.SelectDoc(this, ctrlPressed)
}
- render() {
- if (!this.props.Document) return <div></div>
- let lkeys = this.props.Document.GetT(KeyStore.LayoutKeys, ListField);
- if (!lkeys || lkeys === "<Waiting>") {
- return <p>Error loading layout keys</p>;
- }
- this._documentBindings = {
+ @computed
+ get getProps() {
+ let bindings: any = {
...this.props,
isSelected: this.isSelected,
select: this.select,
- focus: this.props.focus
+ layout: this.layout
};
for (const key of this.layoutKeys) {
- this._documentBindings[key.Name + "Key"] = key; // this maps string values of the form <keyname>Key to an actual key Kestore.keyname e.g, "DataKey" => KeyStore.Data
+ bindings[key.Name + "Key"] = key; // this maps string values of the form <keyname>Key to an actual key Kestore.keyname e.g, "DataKey" => KeyStore.Data
}
for (const key of this.layoutFields) {
let field = this.props.Document.Get(key);
- this._documentBindings[key.Name] = field && field != FieldWaiting ? field.GetValue() : field;
+ bindings[key.Name] = field && field != FieldWaiting ? field.GetValue() : field;
+ }
+ bindings.bindings = bindings;
+
+ return bindings
+ }
+
+ render() {
+ if (!this.props.Document) {
+ return (null);
+ }
+ let lkeys = this.props.Document.GetT(KeyStore.LayoutKeys, ListField);
+ if (!lkeys || lkeys === "<Waiting>") {
+ return <p>Error loading layout keys</p>;
}
- this._documentBindings.bindings = this._documentBindings;
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} >
- {this.mainContent}
+ onPointerDown={this.onPointerDown}
+ onPointerUp={this.stopPropogation} >
+ <DocumentContentsView {...this.getProps} />
</div>
)
}
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 521b0324e..d7026ed67 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -14,6 +14,9 @@ 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");
@@ -60,6 +63,7 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
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 }),
@@ -113,7 +117,7 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
this.props.doc.SetData(this.props.fieldKey, e.target.value, RichTextField);
}
onPointerDown = (e: React.PointerEvent): void => {
- if (e.buttons === 1 && this.props.isSelected()) {
+ if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
e.stopPropagation();
}
}
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 2cb460eea..487038841 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -9,8 +9,8 @@
}
.imageBox-cont img {
- object-fit: contain;
- height: 100%;
+ height: 100%;
+ width:100%;
}
.imageBox-button {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 30910fb1f..cad8904d0 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,5 +1,5 @@
-import { action, observable } from 'mobx';
+import { action, observable, trace } from 'mobx';
import { observer } from "mobx-react";
import Lightbox from 'react-image-lightbox';
import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
@@ -70,7 +70,7 @@ export class ImageBox extends React.Component<FieldViewProps> {
}
lightbox = (path: string) => {
- const images = [path, "http://www.cs.brown.edu/~bcz/face.gif"];
+ const images = [path];
if (this._isOpen && this.props.isSelected()) {
return (<Lightbox
mainSrc={images[this._photoIndex]}
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index ac8c949a9..283c1f732 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -2,17 +2,62 @@
import { observer } from "mobx-react";
import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
import { Document } from '../../../fields/Document';
-import { FieldWaiting } from '../../../fields/Field';
+import { FieldWaiting, Field } from '../../../fields/Field';
import { KeyStore } from '../../../fields/KeyStore';
import { FieldView, FieldViewProps } from './FieldView';
import "./KeyValueBox.scss";
import { KeyValuePair } from "./KeyValuePair";
import React = require("react")
+import { CompileScript, ToField } from "../../util/Scripting";
+import { Key } from '../../../fields/Key';
+import { observable, action } from "mobx";
@observer
export class KeyValueBox extends React.Component<FieldViewProps> {
public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(KeyValueBox, fieldStr) }
+ @observable private _keyInput: string = "";
+ @observable private _valueInput: string = "";
+
+
+ constructor(props: FieldViewProps) {
+ super(props);
+ }
+
+
+
+ shouldComponentUpdate() {
+ return false;
+ }
+
+ @action
+ onEnterKey = (e: React.KeyboardEvent): void => {
+ if (e.key == 'Enter') {
+ if (this._keyInput && this._valueInput) {
+ let doc = this.props.doc.GetT(KeyStore.Data, Document);
+ if (!doc || doc == FieldWaiting) {
+ return
+ }
+ let realDoc = doc;
+
+ let script = CompileScript(this._valueInput, undefined, true);
+ if (!script.compiled) {
+ return;
+ }
+ let field = script();
+ if (field instanceof Field) {
+ realDoc.Set(new Key(this._keyInput), field);
+ } else {
+ let dataField = ToField(field);
+ if (dataField) {
+ realDoc.Set(new Key(this._keyInput), dataField);
+ }
+ }
+ this._keyInput = ""
+ this._valueInput = ""
+ }
+ }
+ }
onPointerDown = (e: React.PointerEvent): void => {
if (e.buttons === 1 && this.props.isSelected()) {
@@ -33,7 +78,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
let ids: { [key: string]: string } = {};
let protos = doc.GetAllPrototypes();
for (const proto of protos) {
- proto._proxies.forEach((val, key) => {
+ proto._proxies.forEach((val: any, key: string) => {
if (!(key in ids)) {
ids[key] = key;
}
@@ -48,9 +93,26 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
return rows;
}
+ @action
+ keyChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this._keyInput = e.currentTarget.value;
+ }
+
+ @action
+ valueChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this._valueInput = e.currentTarget.value;
+ }
- render() {
+ newKeyValue = () => {
+ return (
+ <tr>
+ <td><input type="text" value={this._keyInput} placeholder="Key" onChange={this.keyChanged} /></td>
+ <td><input type="text" value={this._valueInput} placeholder="Value" onChange={this.valueChanged} onKeyPress={this.onEnterKey} /></td>
+ </tr>
+ )
+ }
+ render() {
return (<div className="keyValueBox-cont" onWheel={this.onPointerWheel}>
<table className="keyValueBox-table">
<tbody>
@@ -59,6 +121,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
<th>Fields</th>
</tr>
{this.createTable()}
+ {this.newKeyValue()}
</tbody>
</table>
</div>)
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index a97e98313..111f85a05 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -8,6 +8,8 @@ import { observable, action } from 'mobx';
import { Document } from '../../../fields/Document';
import { Key } from '../../../fields/Key';
import { Server } from "../../Server"
+import { EditableView } from "../EditableView";
+import { CompileScript, ToField } from "../../util/Scripting";
// Represents one row in a key value plane
@@ -48,10 +50,37 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
bindings: {},
selectOnLoad: false,
}
+ let contents = (
+ <FieldView {...props} />
+ );
return (
<tr className={this.props.rowStyle}>
<td>{this.key.Name}</td>
- <td><FieldView {...props} /></td>
+ <td><EditableView contents={contents} height={36} GetValue={() => {
+ let field = props.doc.Get(props.fieldKey);
+ if (field && field instanceof Field) {
+ return field.ToScriptString();
+ }
+ return field || "";
+ }}
+ SetValue={(value: string) => {
+ let script = CompileScript(value, undefined, true);
+ if (!script.compiled) {
+ return false;
+ }
+ 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 false;
+ }}></EditableView></td>
</tr>
)
}
diff --git a/src/client/views/nodes/LinkBox.scss b/src/client/views/nodes/LinkBox.scss
index 00e5ebb3d..5d5f782d2 100644
--- a/src/client/views/nodes/LinkBox.scss
+++ b/src/client/views/nodes/LinkBox.scss
@@ -1,13 +1,14 @@
+@import "../global_variables";
.link-container {
width: 100%;
- height: 30px;
+ height: 35px;
display: flex;
flex-direction: row;
border-top: 0.5px solid #bababa;
}
.info-container {
- width: 60%;
+ width: 55%;
padding-top: 5px;
padding-left: 5px;
display: flex;
@@ -23,17 +24,42 @@
}
.button-container {
- width: 40%;
+ width: 45%;
display: flex;
flex-direction: row;
}
.button {
- height: 15px;
- width: 15px;
- margin: 8px 5px;
+ height: 20px;
+ width: 20px;
+ margin: 8px 4px;
border-radius: 50%;
- opacity: 0.6;
+ opacity: 0.9;
pointer-events: auto;
- background-color: #2B6091;
+ background-color: $dark-color;
+ color: $light-color;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ font-size: 60%;
+ transition: transform 0.2s;
+}
+
+.button:hover {
+ background: $main-accent;
+ cursor: pointer;
+}
+
+.fa-icon-view {
+ margin-left: 3px;
+ margin-top: 5px;
+}
+
+.fa-icon-edit {
+ margin-left: 5px;
+ margin-top: 5px;
+}
+
+.fa-icon-delete {
+ margin-left: 6px;
+ margin-top: 5px;
} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 69df676ff..430c1b694 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -11,6 +11,16 @@ import { ListField } from "../../../fields/ListField";
import { DocumentManager } from "../../util/DocumentManager";
import { LinkEditor } from "./LinkEditor";
import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faEye } from '@fortawesome/free-solid-svg-icons';
+import { faEdit } from '@fortawesome/free-solid-svg-icons';
+import { faTimes } from '@fortawesome/free-solid-svg-icons';
+
+
+library.add(faEye);
+library.add(faEdit);
+library.add(faTimes);
interface Props {
linkDoc: Document;
@@ -79,9 +89,12 @@ export class LinkBox extends React.Component<Props> {
</div>
<div className="button-container">
- <div className="button" onPointerDown={this.onViewButtonPressed}></div>
- <div className="button" onPointerDown={this.onEditButtonPressed}></div>
- <div className="button" onPointerDown={this.onDeleteButtonPressed}></div>
+ <div title="Follow Link" className="button" onPointerDown={this.onViewButtonPressed}>
+ <FontAwesomeIcon className="fa-icon-view" icon="eye" size="sm" /></div>
+ <div title="Edit Link" className="button" onPointerDown={this.onEditButtonPressed}>
+ <FontAwesomeIcon className="fa-icon-edit" icon="edit" size="sm" /></div>
+ <div title="Delete Link" className="button" onPointerDown={this.onDeleteButtonPressed}>
+ <FontAwesomeIcon className="fa-icon-delete" icon="times" size="sm" /></div>
</div>
</div>
)
diff --git a/src/client/views/nodes/LinkEditor.scss b/src/client/views/nodes/LinkEditor.scss
index cb191dc8c..fb0c69cff 100644
--- a/src/client/views/nodes/LinkEditor.scss
+++ b/src/client/views/nodes/LinkEditor.scss
@@ -1,3 +1,4 @@
+@import "../global_variables";
.edit-container {
width: 100%;
height: auto;
@@ -9,21 +10,34 @@
margin-bottom: 10px;
padding: 5px;
font-size: 12px;
+ border: 1px solid #bababa;
}
.description-input {
- font-size: 12px;
+ font-size: 11px;
padding: 5px;
margin-bottom: 10px;
+ border: 1px solid #bababa;
}
.save-button {
width: 50px;
height: 20px;
- background-color: #2B6091;
+ pointer-events: auto;
+ background-color: $dark-color;
+ color: $light-color;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ padding: 2px;
+ font-size: 10px;
margin: 0 auto;
- color: white;
+ transition: transform 0.2s;
text-align: center;
line-height: 20px;
- font-size: 12px;
+}
+
+.save-button:hover {
+ background: $main-accent;
+ transform: scale(1.05);
+ cursor: pointer;
} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkMenu.scss b/src/client/views/nodes/LinkMenu.scss
index a120ab2a7..dedcce6ef 100644
--- a/src/client/views/nodes/LinkMenu.scss
+++ b/src/client/views/nodes/LinkMenu.scss
@@ -10,6 +10,7 @@
padding: 5px;
margin-bottom: 10px;
font-size: 12px;
+ border: 1px solid #bababa;
}
#linkMenu-list {
diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx
index 5c6b06d00..5eeb40772 100644
--- a/src/client/views/nodes/LinkMenu.tsx
+++ b/src/client/views/nodes/LinkMenu.tsx
@@ -39,8 +39,8 @@ export class LinkMenu extends React.Component<Props> {
<div id="linkMenu-container">
<input id="linkMenu-searchBar" type="text" placeholder="Search..."></input>
<div id="linkMenu-list">
- {this.renderLinkItems(linkTo, KeyStore.LinkedToDocs, "Source: ")}
- {this.renderLinkItems(linkFrom, KeyStore.LinkedFromDocs, "Destination: ")}
+ {this.renderLinkItems(linkTo, KeyStore.LinkedToDocs, "Destination: ")}
+ {this.renderLinkItems(linkFrom, KeyStore.LinkedFromDocs, "Source: ")}
</div>
</div>
)
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index 9f92410d4..ad947afd5 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -11,5 +11,5 @@
}
.pdfBox-contentContainer {
position: absolute;
- transform-origin: "left top";
+ transform-origin: left top;
} \ No newline at end of file
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 70a70c7c8..b0b5a63a4 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 } from 'mobx';
+import { action, computed, observable, reaction, IReactionDisposer, trace } from 'mobx';
import { observer } from "mobx-react";
import 'react-image-lightbox/style.css';
import Measure from "react-measure";
@@ -86,7 +86,7 @@ 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, 0); }
+ @computed private get curPage() { return this.props.doc.GetNumber(KeyStore.CurPage, -1); }
componentDidMount() {
this._reactionDisposer = reaction(
@@ -423,7 +423,9 @@ export class PDFBox extends React.Component<FieldViewProps> {
// so this design is flawed.
var nativeWidth = this.props.doc.GetNumber(KeyStore.NativeWidth, 0);
if (!this.props.doc.GetNumber(KeyStore.NativeHeight, 0)) {
- this.props.doc.SetNumber(KeyStore.NativeHeight, nativeWidth * r.entry.height / r.entry.width);
+ var nativeHeight = nativeWidth * r.entry.height / r.entry.width;
+ this.props.doc.SetNumber(KeyStore.Height, nativeHeight / nativeWidth * this.props.doc.GetNumber(KeyStore.Width, 0));
+ this.props.doc.SetNumber(KeyStore.NativeHeight, nativeHeight);
}
if (!this.props.doc.GetT(KeyStore.Thumbnail, ImageField)) {
this.saveThumbnail();
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index 7306450d9..76bbeb37c 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -1,4 +1,4 @@
.videobox-cont{
width: 100%;
- height: 100%;
+ height: Auto;
} \ No newline at end of file
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 22ff5c5ad..8c1ee669f 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,12 +1,13 @@
import React = require("react")
-import { FieldViewProps, FieldView } from './FieldView';
+import { observer } from "mobx-react";
import { FieldWaiting } from '../../../fields/Field';
-import { observer } from "mobx-react"
-import { VideoField } from '../../../fields/VideoField';
-import "./VideoBox.scss"
-import { ContextMenu } from "../../views/ContextMenu";
-import { observable, action } from 'mobx';
-import { KeyStore } from '../../../fields/KeyStore';
+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 { KeyStore } from "../../../fields/KeyStore";
+import { number } from "prop-types";
@observer
export class VideoBox extends React.Component<FieldViewProps> {
@@ -17,27 +18,44 @@ export class VideoBox extends React.Component<FieldViewProps> {
super(props);
}
-
- componentDidMount() {
- }
+ _loaded: boolean = false;
- componentWillUnmount() {
+ @action
+ setScaling = (r: any) => {
+ if (this._loaded) {
+ // bcz: the nativeHeight should really be set when the document is imported.
+ // also, the native dimensions could be different for different pages of the PDF
+ // so this design is flawed.
+ var nativeWidth = this.props.doc.GetNumber(KeyStore.NativeWidth, 0);
+ var nativeHeight = this.props.doc.GetNumber(KeyStore.NativeHeight, 0);
+ var newNativeHeight = nativeWidth * r.entry.height / r.entry.width;
+ if (!nativeHeight && newNativeHeight != nativeHeight && !isNaN(newNativeHeight)) {
+ this.props.doc.SetNumber(KeyStore.Height, newNativeHeight / nativeWidth * this.props.doc.GetNumber(KeyStore.Width, 0));
+ this.props.doc.SetNumber(KeyStore.NativeHeight, newNativeHeight);
+ }
+ } else {
+ this._loaded = true;
+ }
}
-
+
+
render() {
let field = this.props.doc.Get(this.props.fieldKey)
- let path = field == FieldWaiting ? "http://techslides.com/demos/sample-videos/small.mp4":
+ let path = field == FieldWaiting ? "http://techslides.com/demos/sample-videos/small.mp4" :
field instanceof VideoField ? field.Data.href : "http://techslides.com/demos/sample-videos/small.mp4";
-
+
+ //setTimeout(action(() => this._loaded = true), 500);
return (
- <div>
- <video width = {200} height = {200} controls className = "videobox-cont">
- <source src = {path} type = "video/mp4"/>
- Not supported.
- </video>
- </div>
+ <Measure onResize={this.setScaling}>
+ {({ measureRef }) =>
+ <video className="videobox-cont" ref={measureRef}>
+ <source src={path} type="video/mp4" />
+ Not supported.
+ </video>
+ }
+ </Measure>
)
}
} \ No newline at end of file