aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes')
-rw-r--r--src/client/views/nodes/Annotation.tsx117
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx34
-rw-r--r--src/client/views/nodes/DocumentView.scss2
-rw-r--r--src/client/views/nodes/DocumentView.tsx111
-rw-r--r--src/client/views/nodes/FieldView.tsx28
-rw-r--r--src/client/views/nodes/FormattedTextBox.scss30
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx67
-rw-r--r--src/client/views/nodes/ImageBox.scss6
-rw-r--r--src/client/views/nodes/ImageBox.tsx35
-rw-r--r--src/client/views/nodes/KeyValueBox.scss31
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx66
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx58
-rw-r--r--src/client/views/nodes/PDFBox.scss15
-rw-r--r--src/client/views/nodes/PDFBox.tsx490
-rw-r--r--src/client/views/nodes/Sticky.tsx83
-rw-r--r--src/client/views/nodes/WebBox.scss14
-rw-r--r--src/client/views/nodes/WebBox.tsx38
-rw-r--r--src/client/views/nodes/WebView.tsx22
18 files changed, 1109 insertions, 138 deletions
diff --git a/src/client/views/nodes/Annotation.tsx b/src/client/views/nodes/Annotation.tsx
new file mode 100644
index 000000000..a2c7be1a8
--- /dev/null
+++ b/src/client/views/nodes/Annotation.tsx
@@ -0,0 +1,117 @@
+import "./ImageBox.scss";
+import React = require("react")
+import { observer } from "mobx-react"
+import { observable, action } from 'mobx';
+import 'react-pdf/dist/Page/AnnotationLayer.css'
+
+interface IProps{
+ Span: HTMLSpanElement;
+ X: number;
+ Y: number;
+ Highlights: any[];
+ Annotations: any[];
+ CurrAnno: any[];
+
+}
+
+/**
+ * Annotation class is used to take notes on a particular highlight. You can also change highlighted span's color
+ * Improvements to be made: Removing the annotation when onRemove is called. (Removing this, not just the highlighted span).
+ * Also need to support multiline highlighting
+ *
+ * Written by: Andrew Kim
+ */
+@observer
+export class Annotation extends React.Component<IProps> {
+
+ /**
+ * changes color of the span (highlighted section)
+ */
+ onColorChange = (e:React.PointerEvent) => {
+ if (e.currentTarget.innerHTML == "r"){
+ this.props.Span.style.backgroundColor = "rgba(255,0,0, 0.3)"
+ } else if (e.currentTarget.innerHTML == "b"){
+ this.props.Span.style.backgroundColor = "rgba(0,255, 255, 0.3)"
+ } else if (e.currentTarget.innerHTML == "y"){
+ this.props.Span.style.backgroundColor = "rgba(255,255,0, 0.3)"
+ } else if (e.currentTarget.innerHTML == "g"){
+ this.props.Span.style.backgroundColor = "rgba(76, 175, 80, 0.3)"
+ }
+
+ }
+
+ /**
+ * removes the highlighted span. Supposed to remove Annotation too, but I don't know how to unmount this
+ */
+ @action
+ onRemove = (e:any) => {
+ let index:number = -1;
+ //finding the highlight in the highlight array
+ this.props.Highlights.forEach((e) => {
+ for (let i = 0; i < e.spans.length; i++){
+ if (e.spans[i] == this.props.Span){
+ index = this.props.Highlights.indexOf(e);
+ this.props.Highlights.splice(index, 1);
+ }
+ }
+ })
+
+ //removing from CurrAnno and Annotation array
+ this.props.Annotations.splice(index, 1);
+ this.props.CurrAnno.pop()
+
+ //removing span from div
+ if(this.props.Span.parentElement){
+ let nodesArray = this.props.Span.parentElement.childNodes;
+ nodesArray.forEach((e) => {
+ if (e == this.props.Span){
+ if (this.props.Span.parentElement){
+ this.props.Highlights.forEach((item) => {
+ if (item == e){
+ item.remove();
+ }
+ })
+ e.remove();
+ }
+ }
+ })
+ }
+
+
+ }
+
+ render() {
+ return (
+ <div
+ style = {{
+ position: "absolute",
+ top: "20px",
+ left: "0px",
+ zIndex: 1,
+ transform: `translate(${this.props.X}px, ${this.props.Y}px)`,
+
+ }}>
+ <div style = {{width:"200px", height:"50px", backgroundColor: "orange"}}>
+ <button
+ style = {{borderRadius: "25px", width:"25%", height:"100%"}}
+ onClick = {this.onRemove}
+ >x</button>
+ <div style = {{width:"75%", height: "100%" , display:"inline-block"}}>
+ <button onPointerDown = {this.onColorChange} style = {{backgroundColor:"red", borderRadius:"50%", color: "transparent"}}>r</button>
+ <button onPointerDown = {this.onColorChange} style = {{backgroundColor:"blue", borderRadius:"50%", color: "transparent"}}>b</button>
+ <button onPointerDown = {this.onColorChange} style = {{backgroundColor:"yellow", borderRadius:"50%", color:"transparent"}}>y</button>
+ <button onPointerDown = {this.onColorChange} style = {{backgroundColor:"green", borderRadius:"50%", color:"transparent"}}>g</button>
+ </div>
+
+ </div>
+ <div style = {{width:"200px", height:"200"}}>
+ <textarea style = {{width: "100%", height: "100%"}}
+ defaultValue = "Enter Text Here..."
+
+ ></textarea>
+ </div>
+ </div>
+
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 17123bf52..50dc5a619 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -27,44 +27,26 @@ export class CollectionFreeFormDocumentView extends React.Component<DocumentView
return `scale(${this.props.ContentScaling()}, ${this.props.ContentScaling()}) translate(${this.props.Document.GetNumber(KeyStore.X, 0)}px, ${this.props.Document.GetNumber(KeyStore.Y, 0)}px)`;
}
- @computed
- get width(): number {
- return this.props.Document.GetNumber(KeyStore.Width, 0);
- }
-
- @computed
- get nativeWidth(): number {
- return this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
- }
+ @computed get zIndex(): number { return this.props.Document.GetNumber(KeyStore.ZIndex, 0); }
+ @computed get width(): number { return this.props.Document.Width(); }
+ @computed get height(): number { return this.props.Document.Height(); }
+ @computed get nativeWidth(): number { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
+ @computed get nativeHeight(): number { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
set width(w: number) {
this.props.Document.SetData(KeyStore.Width, w, NumberField)
- if (this.nativeWidth > 0 && this.nativeHeight > 0) {
+ if (this.nativeWidth && this.nativeHeight) {
this.props.Document.SetNumber(KeyStore.Height, this.nativeHeight / this.nativeWidth * w)
}
}
- @computed
- get height(): number {
- return this.props.Document.GetNumber(KeyStore.Height, 0);
- }
- @computed
- get nativeHeight(): number {
- return this.props.Document.GetNumber(KeyStore.NativeHeight, 0);
- }
-
set height(h: number) {
this.props.Document.SetData(KeyStore.Height, h, NumberField);
- if (this.nativeWidth > 0 && this.nativeHeight > 0) {
+ if (this.nativeWidth && this.nativeHeight) {
this.props.Document.SetNumber(KeyStore.Width, this.nativeWidth / this.nativeHeight * h)
}
}
- @computed
- get zIndex(): number {
- return this.props.Document.GetNumber(KeyStore.ZIndex, 0);
- }
-
set zIndex(h: number) {
this.props.Document.SetData(KeyStore.ZIndex, h, NumberField)
}
@@ -88,7 +70,7 @@ export class CollectionFreeFormDocumentView extends React.Component<DocumentView
render() {
return (
- <div ref={this._mainCont} style={{
+ <div className="collectionFreeFormDocumentView-container" ref={this._mainCont} style={{
transformOrigin: "left top",
transform: this.transform,
width: this.width,
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 8e2ebd690..ab913897b 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -1,7 +1,7 @@
.documentView-node {
position: absolute;
background: #cdcdcd;
- overflow: hidden;
+ //overflow: hidden;
&.minimized {
width: 30px;
height: 30px;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index bc8bbfef1..e109f2a38 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -12,26 +12,32 @@ import { CollectionDockingView } from "../collections/CollectionDockingView";
import { CollectionFreeFormView } from "../collections/CollectionFreeFormView";
import { CollectionSchemaView } from "../collections/CollectionSchemaView";
import { CollectionView, CollectionViewType } from "../collections/CollectionView";
-import { WebView } from "./WebView";
+import { CollectionPDFView } from "../collections/CollectionPDFView";
import { ContextMenu } from "../ContextMenu";
import { FormattedTextBox } from "../nodes/FormattedTextBox";
import { ImageBox } from "../nodes/ImageBox";
+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");
-const JsxParser = require('react-jsx-parser').default;//TODO Why does this need to be imported like this?
+import { props } from "bluebird";
+const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
+
export interface DocumentViewProps {
ContainingCollectionView: Opt<CollectionView>;
-
Document: Document;
AddDocument?: (doc: Document) => void;
RemoveDocument?: (doc: Document) => boolean;
ScreenToLocalTransform: () => Transform;
isTopMost: boolean;
- //tfs: This shouldn't be necessary I don't think
ContentScaling: () => number;
PanelWidth: () => number;
PanelHeight: () => number;
+ focus: (doc: Document) => void;
+ SelectOnLoad: boolean;
}
export interface JsxArgs extends DocumentViewProps {
Keys: { [name: string]: Key }
@@ -77,32 +83,24 @@ 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 _contextMenuCanOpen = false;
private _downX: number = 0;
private _downY: number = 0;
-
@computed get active(): boolean { return SelectionManager.IsSelected(this) || !this.props.ContainingCollectionView || this.props.ContainingCollectionView.active(); }
@computed get topMost(): boolean { return !this.props.ContainingCollectionView || this.props.ContainingCollectionView.collectionViewType == CollectionViewType.Docking; }
@computed get layout(): string { return this.props.Document.GetText(KeyStore.Layout, "<p>Error loading layout data</p>"); }
@computed get layoutKeys(): Key[] { return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array<Key>()); }
@computed get layoutFields(): Key[] { return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array<Key>()); }
-
screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect();
-
onPointerDown = (e: React.PointerEvent): void => {
this._downX = e.clientX;
this._downY = e.clientY;
if (e.shiftKey && e.buttons === 1) {
- console.log("Hello")
let document = e.ctrlKey ? this.props.Document.CreateAlias() : this.props.Document
- CollectionDockingView.Instance.StartOtherDrag(this._mainCont.current!, this.props.Document);
+ CollectionDockingView.Instance.StartOtherDrag(document, e);
e.stopPropagation();
- }
- else {
- this._contextMenuCanOpen = true;
+ } else {
if (this.active && !e.isDefaultPrevented()) {
e.stopPropagation();
if (e.buttons === 2) {
@@ -115,34 +113,32 @@ export class DocumentView extends React.Component<DocumentViewProps> {
}
}
}
-
onPointerMove = (e: PointerEvent): void => {
if (e.cancelBubble) {
- this._contextMenuCanOpen = false;
return;
}
if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
- this._contextMenuCanOpen = false;
+ document.removeEventListener("pointermove", this.onPointerMove)
+ document.removeEventListener("pointerup", this.onPointerUp)
if (this._mainCont.current != null && !this.topMost) {
- this._contextMenuCanOpen = false;
const [left, top] = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
let dragData: { [id: string]: any } = {};
- dragData["document"] = this;
+ dragData["documentView"] = this;
+ dragData["document"] = e.ctrlKey ? this.props.Document.CreateAlias() : this.props.Document
dragData["xOffset"] = e.x - left;
dragData["yOffset"] = e.y - top;
- dragData["alias"] = e.shiftKey
+ dragData["alias"] = e.ctrlKey
DragManager.StartDrag(this._mainCont.current, dragData, {
handlers: {
- dragComplete: action((e: DragManager.DragCompleteEvent) => { }),
+ dragComplete: action(() => { }),
},
- hideSource: !e.shiftKey
+ hideSource: !e.ctrlKey
})
}
}
e.stopPropagation();
e.preventDefault();
}
-
onPointerUp = (e: PointerEvent): void => {
document.removeEventListener("pointermove", this.onPointerMove)
document.removeEventListener("pointerup", this.onPointerUp)
@@ -152,17 +148,24 @@ export class DocumentView extends React.Component<DocumentViewProps> {
}
}
- deleteClicked = (e: React.MouseEvent): void => {
+ deleteClicked = (): void => {
if (this.props.RemoveDocument) {
this.props.RemoveDocument(this.props.Document);
}
}
+
+ fieldsClicked = (e: React.MouseEvent): void => {
+ if (this.props.AddDocument) {
+ this.props.AddDocument(Documents.KVPDocument(this.props.Document));
+ }
+ }
fullScreenClicked = (e: React.MouseEvent): void => {
CollectionDockingView.Instance.OpenFullScreen(this.props.Document);
ContextMenu.Instance.clearItems();
ContextMenu.Instance.addItem({ description: "Close Full Screen", event: this.closeFullScreenClicked });
ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
}
+
closeFullScreenClicked = (e: React.MouseEvent): void => {
CollectionDockingView.Instance.CloseFullScreen();
ContextMenu.Instance.clearItems();
@@ -172,63 +175,67 @@ export class DocumentView extends React.Component<DocumentViewProps> {
@action
onContextMenu = (e: React.MouseEvent): void => {
- e.preventDefault()
e.stopPropagation();
- if (!SelectionManager.IsSelected(this) || !this._contextMenuCanOpen) {
+ let moved = Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3;
+ if (moved || e.isDefaultPrevented()) {
+ e.preventDefault()
return;
}
+ e.preventDefault()
- if (this.topMost) {
- ContextMenu.Instance.clearItems()
- ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked })
- ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
- }
- else {
+ ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked })
+ ContextMenu.Instance.addItem({ description: "Fields", event: this.fieldsClicked })
+ ContextMenu.Instance.addItem({ description: "Center", event: () => this.props.focus(this.props.Document) })
+ ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document) })
+ //ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) })
+ ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
+ if (!this.topMost) {
// DocumentViews should stop propagation of this event
e.stopPropagation();
-
- ContextMenu.Instance.clearItems();
- ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked })
- ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document) })
- ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked })
- 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: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) })
- ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
- SelectionManager.SelectDoc(this, e.ctrlKey);
}
- }
+ ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked })
+ ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
+ SelectionManager.SelectDoc(this, e.ctrlKey);
+ }
@computed get mainContent() {
return <JsxParser
- components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebView }}
+ components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, WebBox, KeyValueBox, PDFBox }}
bindings={this._documentBindings}
jsx={this.layout}
showWarnings={true}
onError={(test: any) => { console.log(test) }}
/>
}
+
+ isSelected = () => {
+ return SelectionManager.IsSelected(this);
+ }
+
+ select = (ctrlPressed: boolean) => {
+ SelectionManager.SelectDoc(this, ctrlPressed)
+ }
+
render() {
- if (!this.props.Document)
- return <div></div>
+ 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 = {
...this.props,
- isSelected: () => SelectionManager.IsSelected(this),
- select: (ctrlPressed: boolean) => SelectionManager.SelectDoc(this, ctrlPressed)
+ isSelected: this.isSelected,
+ select: this.select,
+ focus: this.props.focus
};
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
+ 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
}
for (const key of this.layoutFields) {
let field = this.props.Document.Get(key);
this._documentBindings[key.Name] = field && field != FieldWaiting ? field.GetValue() : field;
}
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);
@@ -238,7 +245,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
width: nativeWidth > 0 ? nativeWidth.toString() + "px" : "100%",
height: nativeHeight > 0 ? nativeHeight.toString() + "px" : "100%",
transformOrigin: "left top",
- transform: `scale(${scaling},${scaling})`
+ transform: `scale(${scaling} , ${scaling})`
}}
onContextMenu={this.onContextMenu}
onPointerDown={this.onPointerDown} >
@@ -246,4 +253,4 @@ export class DocumentView extends React.Component<DocumentViewProps> {
</div>
)
}
-}
+} \ No newline at end of file
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 1a9d325db..9e63006d1 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,17 +1,17 @@
import React = require("react")
import { observer } from "mobx-react";
import { computed } from "mobx";
-import { Field, Opt, FieldWaiting, FieldValue } from "../../../fields/Field";
+import { Field, FieldWaiting, FieldValue } from "../../../fields/Field";
import { Document } from "../../../fields/Document";
import { TextField } from "../../../fields/TextField";
import { NumberField } from "../../../fields/NumberField";
import { RichTextField } from "../../../fields/RichTextField";
import { ImageField } from "../../../fields/ImageField";
+import { WebField } from "../../../fields/WebField";
import { Key } from "../../../fields/Key";
import { FormattedTextBox } from "./FormattedTextBox";
import { ImageBox } from "./ImageBox";
-import { HtmlField } from "../../../fields/HtmlField";
-import { WebView } from "./WebView";
+import { WebBox } from "./WebBox";
//
// these properties get assigned through the render() method of the DocumentView when it creates this node.
@@ -24,12 +24,15 @@ export interface FieldViewProps {
isSelected: () => boolean;
select: () => void;
isTopMost: boolean;
+ selectOnLoad: boolean;
bindings: any;
}
@observer
export class FieldView extends React.Component<FieldViewProps> {
- public static LayoutString(fieldType: { name: string }, fieldStr: string = "DataKey") { return `<${fieldType.name} doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={${fieldStr}} isSelected={isSelected} select={select} isTopMost={isTopMost} />`; }
+ public static LayoutString(fieldType: { name: string }, fieldStr: string = "DataKey") {
+ return `<${fieldType.name} doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={${fieldStr}} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost} />`;
+ }
@computed
get field(): FieldValue<Field> {
@@ -50,13 +53,20 @@ export class FieldView extends React.Component<FieldViewProps> {
else if (field instanceof ImageField) {
return <ImageBox {...this.props} />
}
+ else if (field instanceof WebField) {
+ return <WebBox {...this.props} />
+ }
+ // bcz: this belongs here, but it doesn't render well so taking it out for now
+ // else if (field instanceof HtmlField) {
+ // return <WebBox {...this.props} />
+ // }
else if (field instanceof NumberField) {
return <p>{field.Data}</p>
- } else if (field instanceof HtmlField) {
- return <WebView {...this.props} />
- } else if (field != FieldWaiting) {
- return <p>{field.GetValue}</p>
- } else
+ }
+ else if (field != FieldWaiting) {
+ return <p>{JSON.stringify(field.GetValue())}</p>
+ }
+ else
return <p> {"Waiting for server..."} </p>
}
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index 226456fab..ab5849f09 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -9,6 +9,30 @@
}
.formattedTextBox-cont {
- background: beige;
- padding: 0;
-} \ No newline at end of file
+ background: white;
+ padding: 1;
+ border-width: 1px;
+ border-radius: 2px;
+ border-color:black;
+ box-sizing: border-box;
+ background: white;
+ border-style:solid;
+ overflow-y: scroll;
+ overflow-x: hidden;
+ color: initial;
+ height: 100%;
+}
+
+.menuicon {
+ display: inline-block;
+ border-right: 1px solid rgba(0, 0, 0, 0.2);
+ color: #888;
+ line-height: 1;
+ padding: 0 7px;
+ margin: 1px;
+ cursor: pointer;
+ text-align: center;
+ min-width: 1.4em;
+ }
+ .strong, .heading { font-weight: bold; }
+ .em { font-style: italic; } \ No newline at end of file
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 727645934..3c78e21e0 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -2,14 +2,20 @@ import { action, IReactionDisposer, reaction } from "mobx";
import { baseKeymap } from "prosemirror-commands";
import { history, redo, undo } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
-import { schema } from "prosemirror-schema-basic";
-import { EditorState, Transaction } from "prosemirror-state";
+import { schema } from "../../util/RichTextSchema";
+import { EditorState, Transaction, } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
-import { Opt, FieldWaiting, FieldValue } from "../../../fields/Field";
+import { Opt, FieldWaiting } from "../../../fields/Field";
import "./FormattedTextBox.scss";
import React = require("react")
import { RichTextField } from "../../../fields/RichTextField";
import { FieldViewProps, FieldView } from "./FieldView";
+import { Plugin } from 'prosemirror-state'
+import { Decoration, DecorationSet } from 'prosemirror-view'
+import { TooltipTextMenu } from "../../util/TooltipTextMenu"
+import { ContextMenu } from "../../views/ContextMenu";
+
+
// FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document
@@ -39,7 +45,6 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
super(props);
this._ref = React.createRef();
-
this.onChange = this.onChange.bind(this);
}
@@ -55,18 +60,18 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
componentDidMount() {
let state: EditorState;
- const { doc, fieldKey } = this.props;
const config = {
schema,
plugins: [
history(),
keymap({ "Mod-z": undo, "Mod-y": redo }),
- keymap(baseKeymap)
+ keymap(baseKeymap),
+ this.tooltipMenuPlugin()
]
};
- let field = doc.GetT(fieldKey, RichTextField);
- if (field && field != FieldWaiting) { // bcz: don't think this works
+ let field = this.props.doc.GetT(this.props.fieldKey, RichTextField);
+ if (field && field != FieldWaiting) {
state = EditorState.fromJSON(config, JSON.parse(field.Data));
} else {
state = EditorState.create(config);
@@ -86,6 +91,10 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field)));
}
})
+ if (this.props.selectOnLoad) {
+ this.props.select();
+ this._editorView!.focus();
+ }
}
componentWillUnmount() {
@@ -108,19 +117,49 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
// doc.SetData(fieldKey, e.target.value, RichTextField);
}
onPointerDown = (e: React.PointerEvent): void => {
- let me = this;
if (e.buttons === 1 && this.props.isSelected()) {
e.stopPropagation();
}
}
+
+ //REPLACE THIS WITH CAPABILITIES SPECIFIC TO THIS TYPE OF NODE
+ textCapability = (e: React.MouseEvent): void => {
+ }
+
+ specificContextMenu = (e: React.MouseEvent): void => {
+ ContextMenu.Instance.addItem({ description: "Text Capability", event: this.textCapability });
+ // ContextMenu.Instance.addItem({
+ // description: "Submenu",
+ // items: [
+ // {
+ // description: "item 1", event:
+ // },
+ // {
+ // description: "item 2", event:
+ // }
+ // ]
+ // })
+ // e.stopPropagation()
+
+ }
+
+ onPointerWheel = (e: React.WheelEvent): void => {
+ e.stopPropagation();
+ }
+
+ tooltipMenuPlugin() {
+ return new Plugin({
+ view(_editorView) {
+ return new TooltipTextMenu(_editorView)
+ }
+ })
+ }
+
render() {
return (<div className="formattedTextBox-cont"
- style={{
- color: "initial",
- whiteSpace: "initial",
- height: "auto"
- }}
onPointerDown={this.onPointerDown}
+ onContextMenu={this.specificContextMenu}
+ onWheel={this.onPointerWheel}
ref={this._ref} />)
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 5b15b3329..ea459b911 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -4,12 +4,14 @@
position: relative;
text-align: center;
width: 100%;
+ height: auto;
max-width: 100%;
max-height: 100%
}
+
.imageBox-cont img {
- max-width: 100%;
- max-height: 100%
+ object-fit: contain;
+ height: 100%;
}
.imageBox-button {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index f3c1d3f4e..195c66cc0 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,20 +1,22 @@
+import { action, observable } 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
-import "./ImageBox.scss";
-import React = require("react")
-import { ImageField } from '../../../fields/ImageField';
-import { FieldViewProps, FieldView } from './FieldView';
import { FieldWaiting } from '../../../fields/Field';
-import { observer } from "mobx-react"
-import { observable, action } from 'mobx';
+import { ImageField } from '../../../fields/ImageField';
import { KeyStore } from '../../../fields/KeyStore';
+import { ContextMenu } from "../../views/ContextMenu";
+import { FieldView, FieldViewProps } from './FieldView';
+import "./ImageBox.scss";
+import React = require("react")
@observer
export class ImageBox extends React.Component<FieldViewProps> {
public static LayoutString() { return FieldView.LayoutString(ImageBox) }
private _ref: React.RefObject<HTMLDivElement>;
+ private _imgRef: React.RefObject<HTMLImageElement>;
private _downX: number = 0;
private _downY: number = 0;
private _lastTap: number = 0;
@@ -25,12 +27,20 @@ export class ImageBox extends React.Component<FieldViewProps> {
super(props);
this._ref = React.createRef();
+ this._imgRef = React.createRef();
this.state = {
photoIndex: 0,
isOpen: false,
};
}
+ @action
+ onLoad = (target: any) => {
+ var h = this._imgRef.current!.naturalHeight;
+ var w = this._imgRef.current!.naturalWidth;
+ this.props.doc.SetNumber(KeyStore.NativeHeight, this.props.doc.GetNumber(KeyStore.NativeWidth, 0) * h / w)
+ }
+
componentDidMount() {
}
@@ -79,16 +89,23 @@ export class ImageBox extends React.Component<FieldViewProps> {
}
}
+ //REPLACE THIS WITH CAPABILITIES SPECIFIC TO THIS TYPE OF NODE
+ imageCapability = (e: React.MouseEvent): void => {
+ }
+
+ specificContextMenu = (e: React.MouseEvent): void => {
+ ContextMenu.Instance.addItem({ description: "Image Capability", event: this.imageCapability });
+ }
+
render() {
let field = this.props.doc.Get(this.props.fieldKey);
console.log(field)
let path = field == FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
field instanceof ImageField ? field.Data.href : "http://www.cs.brown.edu/~bcz/face.gif";
let nativeWidth = this.props.doc.GetNumber(KeyStore.NativeWidth, 1);
-
return (
- <div className="imageBox-cont" onPointerDown={this.onPointerDown} ref={this._ref} >
- <img src={path} width={nativeWidth} alt="Image not found" />
+ <div className="imageBox-cont" onPointerDown={this.onPointerDown} ref={this._ref} onContextMenu={this.specificContextMenu}>
+ <img src={path} width={nativeWidth} alt="Image not found" ref={this._imgRef} onLoad={this.onLoad} />
{this.lightbox(path)}
</div>)
}
diff --git a/src/client/views/nodes/KeyValueBox.scss b/src/client/views/nodes/KeyValueBox.scss
new file mode 100644
index 000000000..1295266e5
--- /dev/null
+++ b/src/client/views/nodes/KeyValueBox.scss
@@ -0,0 +1,31 @@
+.keyValueBox-cont {
+ overflow-y:scroll;
+ height: 100%;
+ border: black;
+ border-width: 1px;
+ border-style: solid;
+ box-sizing: border-box;
+ display: inline-block;
+ .imageBox-cont img {
+ max-height:45px;
+ height: auto;
+ }
+}
+.keyValueBox-table {
+ position: relative;
+}
+.keyValueBox-header {
+ background:gray;
+}
+.keyValueBox-evenRow {
+ background: white;
+ .formattedTextBox-cont {
+ background: white;
+ }
+}
+.keyValueBox-oddRow {
+ background: lightGray;
+ .formattedTextBox-cont {
+ background: lightgray;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
new file mode 100644
index 000000000..ac8c949a9
--- /dev/null
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -0,0 +1,66 @@
+
+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 { KeyStore } from '../../../fields/KeyStore';
+import { FieldView, FieldViewProps } from './FieldView';
+import "./KeyValueBox.scss";
+import { KeyValuePair } from "./KeyValuePair";
+import React = require("react")
+
+@observer
+export class KeyValueBox extends React.Component<FieldViewProps> {
+
+ public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(KeyValueBox, fieldStr) }
+
+ onPointerDown = (e: React.PointerEvent): void => {
+ if (e.buttons === 1 && this.props.isSelected()) {
+ e.stopPropagation();
+ }
+ }
+ onPointerWheel = (e: React.WheelEvent): void => {
+ e.stopPropagation();
+ }
+
+ createTable = () => {
+ let doc = this.props.doc.GetT(KeyStore.Data, Document);
+ if (!doc || doc == FieldWaiting) {
+ return <tr><td>Loading...</td></tr>
+ }
+ let realDoc = doc;
+
+ let ids: { [key: string]: string } = {};
+ let protos = doc.GetAllPrototypes();
+ for (const proto of protos) {
+ proto._proxies.forEach((val, key) => {
+ if (!(key in ids)) {
+ ids[key] = key;
+ }
+ })
+ }
+
+ let rows: JSX.Element[] = [];
+ let i = 0;
+ for (let key in ids) {
+ rows.push(<KeyValuePair doc={realDoc} rowStyle={"keyValueBox-" + (i++ % 2 ? "oddRow" : "evenRow")} fieldId={key} key={key} />)
+ }
+ return rows;
+ }
+
+
+ render() {
+
+ return (<div className="keyValueBox-cont" onWheel={this.onPointerWheel}>
+ <table className="keyValueBox-table">
+ <tbody>
+ <tr className="keyValueBox-header">
+ <th>Key</th>
+ <th>Fields</th>
+ </tr>
+ {this.createTable()}
+ </tbody>
+ </table>
+ </div>)
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
new file mode 100644
index 000000000..a97e98313
--- /dev/null
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -0,0 +1,58 @@
+import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
+import "./KeyValueBox.scss";
+import React = require("react")
+import { FieldViewProps, FieldView } from './FieldView';
+import { Opt, Field } from '../../../fields/Field';
+import { observer } from "mobx-react"
+import { observable, action } from 'mobx';
+import { Document } from '../../../fields/Document';
+import { Key } from '../../../fields/Key';
+import { Server } from "../../Server"
+
+// Represents one row in a key value plane
+
+export interface KeyValuePairProps {
+ rowStyle: string;
+ fieldId: string;
+ doc: Document;
+}
+@observer
+export class KeyValuePair extends React.Component<KeyValuePairProps> {
+
+ @observable
+ private key: Opt<Key>
+
+ constructor(props: KeyValuePairProps) {
+ super(props);
+ Server.GetField(this.props.fieldId,
+ action((field: Opt<Field>) => {
+ if (field) {
+ this.key = field as Key;
+ }
+ }));
+
+ }
+
+
+ render() {
+ if (!this.key) {
+ return <tr><td>error</td><td></td></tr>
+
+ }
+ let props: FieldViewProps = {
+ doc: this.props.doc,
+ fieldKey: this.key,
+ isSelected: () => false,
+ select: () => { },
+ isTopMost: false,
+ bindings: {},
+ selectOnLoad: false,
+ }
+ return (
+ <tr className={this.props.rowStyle}>
+ <td>{this.key.Name}</td>
+ <td><FieldView {...props} /></td>
+ </tr>
+ )
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
new file mode 100644
index 000000000..9f92410d4
--- /dev/null
+++ b/src/client/views/nodes/PDFBox.scss
@@ -0,0 +1,15 @@
+.react-pdf__Page {
+ transform-origin: left top;
+ position: absolute;
+}
+.react-pdf__Document {
+ position: absolute;
+}
+.pdfBox-buttonTray {
+ position:absolute;
+ z-index: 25;
+}
+.pdfBox-contentContainer {
+ position: absolute;
+ 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
new file mode 100644
index 000000000..70a70c7c8
--- /dev/null
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -0,0 +1,490 @@
+import * as htmlToImage from "html-to-image";
+import { action, computed, observable, reaction, IReactionDisposer } from 'mobx';
+import { observer } from "mobx-react";
+import 'react-image-lightbox/style.css';
+import Measure from "react-measure";
+//@ts-ignore
+import { Document, Page } from "react-pdf";
+import 'react-pdf/dist/Page/AnnotationLayer.css';
+import { FieldWaiting, Opt } from '../../../fields/Field';
+import { ImageField } from '../../../fields/ImageField';
+import { KeyStore } from '../../../fields/KeyStore';
+import { PDFField } from '../../../fields/PDFField';
+import { Utils } from '../../../Utils';
+import { Annotation } from './Annotation';
+import { FieldView, FieldViewProps } from './FieldView';
+import "./ImageBox.scss";
+import "./PDFBox.scss";
+import { Sticky } from './Sticky'; //you should look at sticky and annotation, because they are used here
+import React = require("react")
+
+/** ALSO LOOK AT: Annotation.tsx, Sticky.tsx
+ * This method renders PDF and puts all kinds of functionalities such as annotation, highlighting,
+ * area selection (I call it stickies), embedded ink node for directly annotating using a pen or
+ * mouse, and pagination.
+ *
+ *
+ * HOW TO USE:
+ * AREA selection:
+ * 1) Click on Area button.
+ * 2) click on any part of the PDF, and drag to get desired sized area shape
+ * 3) You can write on the area (hence the reason why it's called sticky)
+ * 4) to make another area, you need to click on area button AGAIN.
+ *
+ * HIGHLIGHT: (Buggy. No multiline/multidiv text highlighting for now...)
+ * 1) just click and drag on a text
+ * 2) click highlight
+ * 3) for annotation, just pull your cursor over to that text
+ * 4) another method: click on highlight first and then drag on your desired text
+ * 5) To make another highlight, you need to reclick on the button
+ *
+ * Draw:
+ * 1) click draw and select color. then just draw like there's no tomorrow.
+ * 2) once you finish drawing your masterpiece, just reclick on the draw button to end your drawing session.
+ *
+ * Pagination:
+ * 1) click on arrows. You'll notice that stickies will stay in those page. But... highlights won't.
+ * 2) to test this out, make few area/stickies and then click on next page then come back. You'll see that they are all saved.
+ *
+ *
+ * written by: Andrew Kim
+ */
+@observer
+export class PDFBox extends React.Component<FieldViewProps> {
+ public static LayoutString() { return FieldView.LayoutString(PDFBox); }
+
+ private _mainDiv = React.createRef<HTMLDivElement>()
+ private _pdf = React.createRef<HTMLCanvasElement>();
+
+ //very useful for keeping track of X and y position throughout the PDF Canvas
+ private initX: number = 0;
+ private initY: number = 0;
+ private initPage: boolean = false;
+
+ //checks if tool is on
+ private _toolOn: boolean = false; //checks if tool is on
+ private _pdfContext: any = null; //gets pdf context
+ private bool: Boolean = false; //general boolean debounce
+ private currSpan: any;//keeps track of current span (for highlighting)
+
+ private _currTool: any; //keeps track of current tool button reference
+ private _drawToolOn: boolean = false; //boolean that keeps track of the drawing tool
+ private _drawTool = React.createRef<HTMLButtonElement>()//drawing tool button reference
+
+ private _colorTool = React.createRef<HTMLButtonElement>(); //color button reference
+ private _currColor: string = "black"; //current color that user selected (for ink/pen)
+
+ private _highlightTool = React.createRef<HTMLButtonElement>(); //highlighter button reference
+ private _highlightToolOn: boolean = false;
+ private _pdfCanvas: any;
+ private _reactionDisposer: Opt<IReactionDisposer>;
+
+ @observable private _perPageInfo: Object[] = []; //stores pageInfo
+ @observable private _pageInfo: any = { area: [], divs: [], anno: [] }; //divs is array of objects linked to anno
+
+ @observable private _currAnno: any = []
+ @observable private _interactive: boolean = false;
+ @observable private _loaded: boolean = false;
+
+ @computed private get curPage() { return this.props.doc.GetNumber(KeyStore.CurPage, 0); }
+
+ componentDidMount() {
+ this._reactionDisposer = reaction(
+ () => this.curPage,
+ () => {
+ if (this.curPage && this.initPage) {
+ this.saveThumbnail();
+ this._interactive = true;
+ } else {
+ if (this.curPage)
+ this.initPage = true;
+ }
+ },
+ { fireImmediately: true });
+
+ }
+
+ componentWillUnmount() {
+ if (this._reactionDisposer) {
+ this._reactionDisposer();
+ }
+ }
+
+ /**
+ * selection tool used for area highlighting (stickies). Kinda temporary
+ */
+ selectionTool = () => {
+ this._toolOn = true;
+ }
+ /**
+ * when user draws on the canvas. When mouse pointer is down
+ */
+ drawDown = (e: PointerEvent) => {
+ this.initX = e.offsetX;
+ this.initY = e.offsetY;
+ this._pdfContext.beginPath();
+ this._pdfContext.lineTo(this.initX, this.initY);
+ this._pdfContext.strokeStyle = this._currColor;
+ this._pdfCanvas.addEventListener("pointermove", this.drawMove);
+ this._pdfCanvas.addEventListener("pointerup", this.drawUp);
+
+ }
+ //when user drags
+ drawMove = (e: PointerEvent): void => {
+ //x and y mouse movement
+ let x = this.initX += e.movementX,
+ y = this.initY += e.movementY;
+ //connects the point
+ this._pdfContext.lineTo(x, y);
+ this._pdfContext.stroke();
+ }
+
+ drawUp = (e: PointerEvent) => {
+ this._pdfContext.closePath();
+ this._pdfCanvas.removeEventListener("pointermove", this.drawMove);
+ this._pdfCanvas.removeEventListener("pointerdown", this.drawDown);
+ this._pdfCanvas.addEventListener("pointerdown", this.drawDown);
+ }
+
+
+ /**
+ * highlighting helper function
+ */
+ makeEditableAndHighlight = (colour: string) => {
+ var range, sel = window.getSelection();
+ if (sel.rangeCount && sel.getRangeAt) {
+ range = sel.getRangeAt(0);
+ }
+ document.designMode = "on";
+ if (!document.execCommand("HiliteColor", false, colour)) {
+ document.execCommand("HiliteColor", false, colour);
+ }
+
+ if (range) {
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let obj: Object = { parentDivs: [], spans: [] };
+ //@ts-ignore
+ if (range.commonAncestorContainer.className == 'react-pdf__Page__textContent') { //multiline highlighting case
+ obj = this.highlightNodes(range.commonAncestorContainer.childNodes)
+ } else { //single line highlighting case
+ let parentDiv = range.commonAncestorContainer.parentElement
+ if (parentDiv) {
+ if (parentDiv.className == 'react-pdf__Page__textContent') { //when highlight is overwritten
+ obj = this.highlightNodes(parentDiv.childNodes)
+ } else {
+ parentDiv.childNodes.forEach((child) => {
+ if (child.nodeName == 'SPAN') {
+ //@ts-ignore
+ obj.parentDivs.push(parentDiv)
+ //@ts-ignore
+ child.id = "highlighted"
+ //@ts-ignore
+ obj.spans.push(child)
+ child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler
+ }
+ })
+ }
+ }
+ }
+ this._pageInfo.divs.push(obj);
+
+ }
+ document.designMode = "off";
+ }
+
+ highlightNodes = (nodes: NodeListOf<ChildNode>) => {
+ let temp = { parentDivs: [], spans: [] }
+ nodes.forEach((div) => {
+ div.childNodes.forEach((child) => {
+ if (child.nodeName == 'SPAN') {
+ //@ts-ignore
+ temp.parentDivs.push(div)
+ //@ts-ignore
+ child.id = "highlighted"
+ //@ts-ignore
+ temp.spans.push(child)
+ child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler
+ }
+ })
+
+ })
+ return temp;
+ }
+
+ /**
+ * when the cursor enters the highlight, it pops out annotation. ONLY WORKS FOR SINGLE DIV LINES
+ */
+ @action
+ onEnter = (e: any) => {
+ let span: HTMLSpanElement = e.toElement;
+ let index: any;
+ this._pageInfo.divs.forEach((obj: any) => {
+ obj.spans.forEach((element: any) => {
+ if (element == span) {
+ if (!index) {
+ index = this._pageInfo.divs.indexOf(obj);
+ }
+ }
+ })
+ })
+
+ if (this._pageInfo.anno.length >= index + 1) {
+ if (this._currAnno.length == 0) {
+ this._currAnno.push(this._pageInfo.anno[index]);
+ }
+ } else {
+ if (this._currAnno.length == 0) { //if there are no current annotation
+ let div = span.offsetParent;
+ //@ts-ignore
+ let divX = div.style.left
+ //@ts-ignore
+ let divY = div.style.top
+ //slicing "px" from the end
+ divX = divX.slice(0, divX.length - 2); //gets X of the DIV element (parent of Span)
+ divY = divY.slice(0, divY.length - 2); //gets Y of the DIV element (parent of Span)
+ let annotation = <Annotation key={Utils.GenerateGuid()} Span={span} X={divX} Y={divY - 300} Highlights={this._pageInfo.divs} Annotations={this._pageInfo.anno} CurrAnno={this._currAnno} />
+ this._pageInfo.anno.push(annotation);
+ this._currAnno.push(annotation);
+ }
+ }
+
+ }
+
+ /**
+ * highlight function for highlighting actual text. This works fine.
+ */
+ highlight = (color: string) => {
+ if (window.getSelection()) {
+ try {
+ if (!document.execCommand("hiliteColor", false, color)) {
+ this.makeEditableAndHighlight(color);
+ }
+ } catch (ex) {
+ this.makeEditableAndHighlight(color)
+ }
+ }
+ }
+
+ /**
+ * controls the area highlighting (stickies) Kinda temporary
+ */
+ onPointerDown = (e: React.PointerEvent) => {
+ if (this._toolOn) {
+ let mouse = e.nativeEvent;
+ this.initX = mouse.offsetX;
+ this.initY = mouse.offsetY;
+
+ }
+ }
+
+ /**
+ * controls area highlighting and partially highlighting. Kinda temporary
+ */
+ @action
+ onPointerUp = (e: React.PointerEvent) => {
+ if (this._highlightToolOn) {
+ this.highlight("rgba(76, 175, 80, 0.3)"); //highlights to this default color.
+ this._highlightToolOn = false;
+ }
+ if (this._toolOn) {
+ let mouse = e.nativeEvent;
+ let finalX = mouse.offsetX;
+ let finalY = mouse.offsetY;
+ let width = Math.abs(finalX - this.initX); //width
+ let height = Math.abs(finalY - this.initY); //height
+
+ //these two if statements are bidirectional dragging. You can drag from any point to another point and generate sticky
+ if (finalX < this.initX) {
+ this.initX = finalX;
+ }
+ if (finalY < this.initY) {
+ this.initY = finalY;
+ }
+
+ if (this._mainDiv.current) {
+ let sticky = <Sticky key={Utils.GenerateGuid()} Height={height} Width={width} X={this.initX} Y={this.initY} />
+ this._pageInfo.area.push(sticky);
+ }
+ this._toolOn = false;
+ }
+ this._interactive = true;
+ }
+
+ /**
+ * starts drawing the line when user presses down.
+ */
+ onDraw = () => {
+ if (this._currTool != null) {
+ this._currTool.style.backgroundColor = "grey";
+ }
+
+ if (this._drawTool.current) {
+ this._currTool = this._drawTool.current;
+ if (this._drawToolOn) {
+ this._drawToolOn = false;
+ this._pdfCanvas.removeEventListener("pointerdown", this.drawDown);
+ this._pdfCanvas.removeEventListener("pointerup", this.drawUp);
+ this._pdfCanvas.removeEventListener("pointermove", this.drawMove);
+ this._drawTool.current.style.backgroundColor = "grey";
+ } else {
+ this._drawToolOn = true;
+ this._pdfCanvas.addEventListener("pointerdown", this.drawDown);
+ this._drawTool.current.style.backgroundColor = "cyan";
+ }
+ }
+ }
+
+
+ /**
+ * for changing color (for ink/pen)
+ */
+ onColorChange = (e: React.PointerEvent) => {
+ if (e.currentTarget.innerHTML == "Red") {
+ this._currColor = "red";
+ } else if (e.currentTarget.innerHTML == "Blue") {
+ this._currColor = "blue";
+ } else if (e.currentTarget.innerHTML == "Green") {
+ this._currColor = "green";
+ } else if (e.currentTarget.innerHTML == "Black") {
+ this._currColor = "black";
+ }
+
+ }
+
+
+ /**
+ * For highlighting (text drag highlighting)
+ */
+ onHighlight = () => {
+ this._drawToolOn = false;
+ if (this._currTool != null) {
+ this._currTool.style.backgroundColor = "grey";
+ }
+ if (this._highlightTool.current) {
+ this._currTool = this._drawTool.current;
+ if (this._highlightToolOn) {
+ this._highlightToolOn = false;
+ this._highlightTool.current.style.backgroundColor = "grey";
+ } else {
+ this._highlightToolOn = true;
+ this._highlightTool.current.style.backgroundColor = "orange";
+ }
+ }
+ }
+
+
+ @action
+ saveThumbnail = () => {
+ setTimeout(() => {
+ var me = this;
+ htmlToImage.toPng(this._mainDiv.current!,
+ { width: me.props.doc.GetNumber(KeyStore.NativeWidth, 0), height: me.props.doc.GetNumber(KeyStore.NativeHeight, 0), quality: 0.5 })
+ .then(function (dataUrl: string) {
+ me.props.doc.SetData(KeyStore.Thumbnail, new URL(dataUrl), ImageField);
+ })
+ .catch(function (error: any) {
+ console.error('oops, something went wrong!', error);
+ });
+ }, 1000);
+ }
+
+ @action
+ onLoaded = (page: any) => {
+ if (this._mainDiv.current) {
+ this._mainDiv.current.childNodes.forEach((element) => {
+ if (element.nodeName == "DIV") {
+ element.childNodes[0].childNodes.forEach((e) => {
+
+ if (e instanceof HTMLCanvasElement) {
+ this._pdfCanvas = e;
+ this._pdfContext = e.getContext("2d")
+
+ }
+
+ })
+ }
+ })
+ }
+
+ // bcz: the number of pages should really be set when the document is imported.
+ this.props.doc.SetNumber(KeyStore.NumPages, page._transport.numPages);
+ if (this._perPageInfo.length == 0) { //Makes sure it only runs once
+ this._perPageInfo = [...Array(page._transport.numPages)]
+ }
+ this._loaded = true;
+ }
+
+ @action
+ setScaling = (r: any) => {
+ // 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);
+ if (!this.props.doc.GetNumber(KeyStore.NativeHeight, 0)) {
+ this.props.doc.SetNumber(KeyStore.NativeHeight, nativeWidth * r.entry.height / r.entry.width);
+ }
+ if (!this.props.doc.GetT(KeyStore.Thumbnail, ImageField)) {
+ this.saveThumbnail();
+ }
+ }
+
+ @computed
+ get pdfContent() {
+ let page = this.curPage;
+ if (page == 0)
+ page = 1;
+ const renderHeight = 2400;
+ let pdfUrl = this.props.doc.GetT(this.props.fieldKey, PDFField);
+ let xf = this.props.doc.GetNumber(KeyStore.NativeHeight, 0) / renderHeight;
+ return <div className="pdfBox-contentContainer" key="container" style={{ transform: `scale(${xf}, ${xf})` }}>
+ <Document file={window.origin + "/corsProxy/" + `${pdfUrl}`}>
+ <Measure onResize={this.setScaling}>
+ {({ measureRef }) =>
+ <div className="pdfBox-page" ref={measureRef}>
+ <Page height={renderHeight} pageNumber={page} onLoadSuccess={this.onLoaded} />
+ </div>
+ }
+ </Measure>
+ </Document>
+ </div >;
+ }
+
+ @computed
+ get pdfRenderer() {
+ let proxy = this._loaded ? (null) : this.imageProxyRenderer;
+ let pdfUrl = this.props.doc.GetT(this.props.fieldKey, PDFField);
+ if ((!this._interactive && proxy) || !pdfUrl || pdfUrl == FieldWaiting) {
+ return proxy;
+ }
+ return [
+ this._pageInfo.area.filter(() => this._pageInfo.area).map((element: any) => element),
+ this._currAnno.map((element: any) => element),
+ <div key="pdfBox-contentShell">
+ {this.pdfContent}
+ {proxy}
+ </div>
+ ];
+ }
+
+ @computed
+ get imageProxyRenderer() {
+ let field = this.props.doc.Get(KeyStore.Thumbnail);
+ if (field) {
+ let path = field == FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
+ field instanceof ImageField ? field.Data.href : "http://cs.brown.edu/people/bcz/prairie.jpg";
+ return <img src={path} width="100%" />;
+ }
+ return (null);
+ }
+
+ render() {
+ return (
+ <div className="pdfBox-cont" ref={this._mainDiv} onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} >
+ {this.pdfRenderer}
+ </div >
+ );
+ }
+
+} \ No newline at end of file
diff --git a/src/client/views/nodes/Sticky.tsx b/src/client/views/nodes/Sticky.tsx
new file mode 100644
index 000000000..d57dd5c0b
--- /dev/null
+++ b/src/client/views/nodes/Sticky.tsx
@@ -0,0 +1,83 @@
+import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
+import React = require("react")
+import { observer } from "mobx-react"
+import 'react-pdf/dist/Page/AnnotationLayer.css'
+
+interface IProps {
+ Height: number;
+ Width: number;
+ X: number;
+ Y: number;
+}
+
+/**
+ * Sticky, also known as area highlighting, is used to highlight large selection of the PDF file.
+ * Improvements that could be made: maybe store line array and store that somewhere for future rerendering.
+ *
+ * Written By: Andrew Kim
+ */
+@observer
+export class Sticky extends React.Component<IProps> {
+
+ private initX: number = 0;
+ private initY: number = 0;
+
+ private _ref = React.createRef<HTMLCanvasElement>();
+ private ctx: any; //context that keeps track of sticky canvas
+
+ /**
+ * drawing. Registers the first point that user clicks when mouse button is pressed down on canvas
+ */
+ drawDown = (e: React.PointerEvent) => {
+ if (this._ref.current) {
+ this.ctx = this._ref.current.getContext("2d");
+ let mouse = e.nativeEvent;
+ this.initX = mouse.offsetX;
+ this.initY = mouse.offsetY;
+ this.ctx.beginPath();
+ this.ctx.lineTo(this.initX, this.initY);
+ this.ctx.strokeStyle = "black";
+ document.addEventListener("pointermove", this.drawMove);
+ document.addEventListener("pointerup", this.drawUp);
+ }
+ }
+
+ //when user drags
+ drawMove = (e: PointerEvent): void => {
+ //x and y mouse movement
+ let x = this.initX += e.movementX,
+ y = this.initY += e.movementY;
+ //connects the point
+ this.ctx.lineTo(x, y);
+ this.ctx.stroke();
+
+ }
+
+ /**
+ * when user lifts the mouse, the drawing ends
+ */
+ drawUp = (e: PointerEvent) => {
+ this.ctx.closePath();
+ console.log(this.ctx);
+ document.removeEventListener("pointermove", this.drawMove);
+ }
+
+ render() {
+ return (
+ <div onPointerDown={this.drawDown}>
+ <canvas ref={this._ref} height={this.props.Height} width={this.props.Width}
+ style={{
+ position: "absolute",
+ top: "20px",
+ left: "0px",
+ zIndex: 1,
+ background: "yellow",
+ transform: `translate(${this.props.X}px, ${this.props.Y}px)`,
+ opacity: 0.4
+ }}
+ />
+
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
new file mode 100644
index 000000000..e72b3c4da
--- /dev/null
+++ b/src/client/views/nodes/WebBox.scss
@@ -0,0 +1,14 @@
+
+.webBox-cont {
+ padding: 0vw;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
+
+.webBox-button {
+ padding : 0vw;
+ border: none;
+ width : 100%;
+ height: 100%;
+} \ No newline at end of file
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
new file mode 100644
index 000000000..2ca8d49ce
--- /dev/null
+++ b/src/client/views/nodes/WebBox.tsx
@@ -0,0 +1,38 @@
+import "./WebBox.scss";
+import React = require("react")
+import { WebField } from '../../../fields/WebField';
+import { FieldViewProps, FieldView } from './FieldView';
+import { FieldWaiting } from '../../../fields/Field';
+import { observer } from "mobx-react"
+import { computed } from 'mobx';
+import { KeyStore } from '../../../fields/KeyStore';
+
+@observer
+export class WebBox extends React.Component<FieldViewProps> {
+
+ public static LayoutString() { return FieldView.LayoutString(WebBox); }
+
+ constructor(props: FieldViewProps) {
+ super(props);
+ }
+
+ @computed get html(): string { return this.props.doc.GetHtml(KeyStore.Data, ""); }
+
+ render() {
+ let field = this.props.doc.Get(this.props.fieldKey);
+ let path = field == FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
+ field instanceof WebField ? field.Data.href : "https://crossorigin.me/" + "https://cs.brown.edu";
+
+ let content = this.html ?
+ <span dangerouslySetInnerHTML={{ __html: this.html }}></span> :
+ <div style={{ width: "100%", height: "100%", position: "absolute" }}>
+ <iframe src={path} style={{ position: "absolute", width: "100%", height: "100%" }}></iframe>
+ {this.props.isSelected() ? (null) : <div style={{ width: "100%", height: "100%", position: "absolute" }} />}
+ </div>;
+
+ return (
+ <div className="webBox-cont" >
+ {content}
+ </div>)
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/WebView.tsx b/src/client/views/nodes/WebView.tsx
deleted file mode 100644
index 717aa8bf5..000000000
--- a/src/client/views/nodes/WebView.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { FieldViewProps, FieldView } from "./FieldView";
-import { computed } from "mobx";
-import { observer } from "mobx-react";
-import { KeyStore } from "../../../fields/KeyStore";
-import React = require('react')
-import { TextField } from "../../../fields/TextField";
-import { HtmlField } from "../../../fields/HtmlField";
-import { RichTextField } from "../../../fields/RichTextField";
-
-@observer
-export class WebView extends React.Component<FieldViewProps> {
- public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(WebView, fieldStr) }
-
- @computed
- get html(): string {
- return this.props.doc.GetData(KeyStore.Data, HtmlField, "" as string);
- }
-
- render() {
- return <span dangerouslySetInnerHTML={{ __html: this.html }}></span>
- }
-} \ No newline at end of file