aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/.DS_Storebin6148 -> 6148 bytes
-rw-r--r--src/client/documents/Documents.ts38
-rw-r--r--src/client/util/DocumentManager.ts51
-rw-r--r--src/client/util/DragManager.ts26
-rw-r--r--src/client/views/.DS_Storebin0 -> 6148 bytes
-rw-r--r--src/client/views/ContextMenu.scss7
-rw-r--r--src/client/views/ContextMenu.tsx10
-rw-r--r--src/client/views/DocumentDecorations.scss21
-rw-r--r--src/client/views/DocumentDecorations.tsx75
-rw-r--r--src/client/views/InkingControl.tsx2
-rw-r--r--src/client/views/Main.tsx39
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx27
-rw-r--r--src/client/views/collections/CollectionFreeFormView.scss7
-rw-r--r--src/client/views/collections/CollectionFreeFormView.tsx134
-rw-r--r--src/client/views/collections/CollectionPDFView.tsx2
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx4
-rw-r--r--src/client/views/collections/CollectionView.tsx10
-rw-r--r--src/client/views/collections/CollectionViewBase.tsx73
-rw-r--r--src/client/views/nodes/AudioBox.scss4
-rw-r--r--src/client/views/nodes/AudioBox.tsx44
-rw-r--r--src/client/views/nodes/DocumentView.tsx122
-rw-r--r--src/client/views/nodes/FieldView.tsx11
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx8
-rw-r--r--src/client/views/nodes/LinkBox.scss39
-rw-r--r--src/client/views/nodes/LinkBox.tsx89
-rw-r--r--src/client/views/nodes/LinkEditor.scss29
-rw-r--r--src/client/views/nodes/LinkEditor.tsx58
-rw-r--r--src/client/views/nodes/LinkMenu.scss20
-rw-r--r--src/client/views/nodes/LinkMenu.tsx54
-rw-r--r--src/client/views/nodes/PDFBox.tsx7
-rw-r--r--src/client/views/nodes/VideoBox.scss4
-rw-r--r--src/client/views/nodes/VideoBox.tsx43
-rw-r--r--src/client/views/nodes/WebBox.scss1
-rw-r--r--src/fields/AudioField.ts31
-rw-r--r--src/fields/Document.ts8
-rw-r--r--src/fields/KeyStore.ts5
-rw-r--r--src/fields/VideoField.ts30
-rw-r--r--src/server/Message.ts2
-rw-r--r--src/server/RouteStore.ts28
-rw-r--r--src/server/ServerUtil.ts35
-rw-r--r--src/server/authentication/config/passport.ts3
-rw-r--r--src/server/authentication/controllers/user_controller.ts47
-rw-r--r--src/server/authentication/models/user_model.ts2
-rw-r--r--src/server/database.ts4
-rw-r--r--src/server/index.ts137
45 files changed, 1167 insertions, 224 deletions
diff --git a/src/.DS_Store b/src/.DS_Store
index 620e4ebce..f20f36d63 100644
--- a/src/.DS_Store
+++ b/src/.DS_Store
Binary files differ
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index bf4acc857..4cab53030 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -14,6 +14,11 @@ import { HtmlField } from "../../fields/HtmlField";
import { Key } from "../../fields/Key"
import { Field } from "../../fields/Field";
import { KeyValueBox } from "../views/nodes/KeyValueBox"
+import { KVPField } from "../../fields/KVPField";
+import { VideoField } from "../../fields/VideoField"
+import { VideoBox } from "../views/nodes/VideoBox";
+import { AudioField } from "../../fields/AudioField";
+import { AudioBox } from "../views/nodes/AudioBox";
import { PDFField } from "../../fields/PDFField";
import { PDFBox } from "../views/nodes/PDFBox";
import { CollectionPDFView } from "../views/collections/CollectionPDFView";
@@ -29,6 +34,7 @@ export interface DocumentOptions {
title?: string;
panx?: number;
pany?: number;
+ page?: number;
scale?: number;
layout?: string;
layoutKeys?: Key[];
@@ -41,6 +47,8 @@ export namespace Documents {
let webProto: Document;
let collProto: Document;
let kvpProto: Document;
+ let videoProto: Document;
+ let audioProto: Document;
let pdfProto: Document;
const textProtoId = "textProto";
const pdfProtoId = "pdfProto";
@@ -48,6 +56,8 @@ export namespace Documents {
const webProtoId = "webProto";
const collProtoId = "collectionProto";
const kvpProtoId = "kvpProto";
+ const videoProtoId = "videoProto"
+ const audioProtoId = "audioProto";
export function initProtos(callback: () => void) {
Server.GetFields([collProtoId, textProtoId, imageProtoId], (fields) => {
@@ -69,6 +79,7 @@ export namespace Documents {
if (options.title !== undefined) { doc.SetText(KeyStore.Title, options.title); }
if (options.panx !== undefined) { doc.SetNumber(KeyStore.PanX, options.panx); }
if (options.pany !== undefined) { doc.SetNumber(KeyStore.PanY, options.pany); }
+ if (options.page !== undefined) { doc.SetNumber(KeyStore.Page, options.page); }
if (options.scale !== undefined) { doc.SetNumber(KeyStore.Scale, options.scale); }
if (options.viewType !== undefined) { doc.SetNumber(KeyStore.ViewType, options.viewType); }
if (options.layout !== undefined) { doc.SetText(KeyStore.Layout, options.layout); }
@@ -124,15 +135,32 @@ export namespace Documents {
kvpProto = setupPrototypeOptions(kvpProtoId, "KVP_PROTO", KeyValueBox.LayoutString(),
{ x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] })
}
+ function GetVideoPrototype(): Document {
+ return videoProto ? videoProto :
+ videoProto = setupPrototypeOptions(videoProtoId, "VIDEO_PROTO", VideoBox.LayoutString(),
+ { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] })
+ }
+ function GetAudioPrototype(): Document {
+ return audioProto ? audioProto :
+ audioProto = setupPrototypeOptions(audioProtoId, "AUDIO_PROTO", AudioBox.LayoutString(),
+ { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] })
+ }
+
export function ImageDocument(url: string, options: DocumentOptions = {}) {
let doc = SetInstanceOptions(GetImagePrototype(), { ...options, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] },
new URL(url), ImageField);
- doc.SetText(KeyStore.Caption, "my caption...");
- doc.SetText(KeyStore.BackgroundLayout, EmbeddedCaption());
- doc.SetText(KeyStore.OverlayLayout, FixedCaption());
+ // doc.SetText(KeyStore.Caption, "my caption...");
+ // doc.SetText(KeyStore.BackgroundLayout, EmbeddedCaption());
+ // doc.SetText(KeyStore.OverlayLayout, FixedCaption());
return doc;
}
+ export function VideoDocument(url: string, options: DocumentOptions = {}) {
+ return SetInstanceOptions(GetVideoPrototype(), options, new URL(url), VideoField);
+ }
+ export function AudioDocument(url: string, options: DocumentOptions = {}) {
+ return SetInstanceOptions(GetAudioPrototype(), options, new URL(url), AudioField);
+ }
export function TextDocument(options: DocumentOptions = {}) {
return SetInstanceOptions(GetTextPrototype(), options, undefined, undefined);
}
@@ -170,10 +198,10 @@ export namespace Documents {
+ FormattedTextBox.LayoutString("CaptionKey") +
`</div>
</div>` };
- function FixedCaption() {
+ function FixedCaption(fieldName: string = "Caption") {
return `<div style="position:absolute; height:30px; bottom:0; width:100%">
<div style="position:absolute; width:100%; height:100%; text-align:center;bottom:0;">`
- + FormattedTextBox.LayoutString("CaptionKey") +
+ + FormattedTextBox.LayoutString(fieldName + "Key") +
`</div>
</div>` };
} \ No newline at end of file
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
new file mode 100644
index 000000000..5b99b4ef8
--- /dev/null
+++ b/src/client/util/DocumentManager.ts
@@ -0,0 +1,51 @@
+import React = require('react')
+import { observer } from 'mobx-react';
+import { observable, action } from 'mobx';
+import { Document } from "../../fields/Document"
+import { DocumentView } from '../views/nodes/DocumentView';
+
+
+export class DocumentManager {
+
+ //global holds all of the nodes (regardless of which collection they're in)
+ @observable
+ public DocumentViews: DocumentView[] = [];
+
+ // singleton instance
+ private static _instance: DocumentManager;
+
+ // create one and only one instance of NodeManager
+ public static get Instance(): DocumentManager {
+ return this._instance || (this._instance = new this());
+ }
+
+ //private constructor so no other class can create a nodemanager
+ private constructor() {
+ // this.DocumentViews = new Array<DocumentView>();
+ }
+
+ public getDocumentView(toFind: Document): DocumentView | null {
+
+ let toReturn: DocumentView | null;
+ toReturn = null;
+
+ //gets document view that is in a freeform canvas collection
+ DocumentManager.Instance.DocumentViews.map(view => {
+ let doc = view.props.Document;
+ // if (view.props.ContainingCollectionView instanceof CollectionFreeFormView) {
+ // if (Object.is(doc, toFind)) {
+ // toReturn = view;
+ // return;
+ // }
+ // }
+
+ if (Object.is(doc, toFind)) {
+ toReturn = view;
+ return;
+ }
+
+ })
+
+ return (toReturn);
+ }
+} \ No newline at end of file
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 513a6ac9e..9bd7d5c24 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -97,9 +97,9 @@ export namespace DragManager {
}
export function StartDrag(ele: HTMLElement, dragData: { [id: string]: any }, options?: DragOptions) {
- DocumentDecorations.Instance.Hidden = true;
if (!dragDiv) {
dragDiv = document.createElement("div");
+ dragDiv.className = "dragManager-dragDiv"
DragManager.Root().appendChild(dragDiv);
}
const w = ele.offsetWidth, h = ele.offsetHeight;
@@ -124,17 +124,21 @@ export namespace DragManager {
// So we replace the pdf's canvas with the image thumbnail
const docView: DocumentView = dragData["documentView"];
const doc: Document = docView ? docView.props.Document : dragData["document"];
- var pdfBox = dragElement.getElementsByClassName("pdfBox-cont")[0] as HTMLElement;
- let thumbnail = doc.GetT(KeyStore.Thumbnail, ImageField);
- if (pdfBox && pdfBox.childElementCount && thumbnail) {
- let img = new Image();
- img!.src = thumbnail.toString();
- img!.style.position = "absolute";
- img!.style.width = `${rect.width / scaleX}px`;
- img!.style.height = `${rect.height / scaleY}px`;
- pdfBox.replaceChild(img!, pdfBox.children[0])
+
+ if (doc) {
+ var pdfBox = dragElement.getElementsByClassName("pdfBox-cont")[0] as HTMLElement;
+ let thumbnail = doc.GetT(KeyStore.Thumbnail, ImageField);
+ if (pdfBox && pdfBox.childElementCount && thumbnail) {
+ let img = new Image();
+ img!.src = thumbnail.toString();
+ img!.style.position = "absolute";
+ img!.style.width = `${rect.width / scaleX}px`;
+ img!.style.height = `${rect.height / scaleY}px`;
+ pdfBox.replaceChild(img!, pdfBox.children[0])
+ }
}
+
dragDiv.appendChild(dragElement);
let hideSource = false;
@@ -154,7 +158,7 @@ export namespace DragManager {
e.preventDefault();
x += e.movementX;
y += e.movementY;
- if (e.shiftKey) {
+ if (e.shiftKey && (e.button == 2 || e.altKey)) {
abortDrag();
CollectionDockingView.Instance.StartOtherDrag(doc, { pageX: e.pageX, pageY: e.pageY, preventDefault: () => { }, button: 0 });
}
diff --git a/src/client/views/.DS_Store b/src/client/views/.DS_Store
new file mode 100644
index 000000000..6bd614c8b
--- /dev/null
+++ b/src/client/views/.DS_Store
Binary files differ
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index ea40c8e99..41994ef79 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -26,7 +26,8 @@
border-bottom-style: solid;
padding: 10px;
white-space: nowrap;
- font-size: 1.5vw;
+ // font-size: 1.5vw;
+ font-size: 12px;
}
.contextMenu-item:hover {
@@ -35,7 +36,7 @@
}
.contextMenu-description {
- font-size: 1.5vw;
+ // font-size: 1.5vw;
text-align: left;
- width: 8vw;
+ // width: 8vw;
} \ No newline at end of file
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index fcb934860..12352c667 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -44,8 +44,13 @@ export class ContextMenu extends React.Component {
@action
displayMenu(x: number, y: number) {
- this._pageX = x
- this._pageY = 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);
+
+ this._pageX = x > maxX ? maxX : x;
+ this._pageY = y > maxY ? maxY : y;
this._searchString = "";
@@ -64,6 +69,7 @@ export class ContextMenu extends React.Component {
}
render() {
+
return (
<div className="contextMenu-cont" style={{ left: this._pageX, top: this._pageY, display: this._display }} 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 e8b93a18b..fb9091dfc 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -2,7 +2,7 @@
position: absolute;
display: grid;
z-index: 1000;
- grid-template-rows: 20px 1fr 20px;
+ grid-template-rows: 20px 1fr 20px 0px;
grid-template-columns: 20px 1fr 20px;
pointer-events: none;
#documentDecorations-centerCont {
@@ -29,4 +29,23 @@
#documentDecorations-rightResizer {
cursor: ew-resize;
}
+
+}
+.linkButton-empty {
+ height: 20px;
+ width: 20px;
+ margin-top: 10px;
+ border-radius: 50%;
+ opacity: 0.6;
+ pointer-events: auto;
+ background-color: #2B6091;
+}
+.linkButton-nonempty {
+ height: 20px;
+ width: 20px;
+ margin-top: 10px;
+ border-radius: 50%;
+ opacity: 0.6;
+ pointer-events: auto;
+ background-color: rgb(35, 165, 42);
} \ No newline at end of file
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 9fd73a33b..dc62f97cf 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,16 +1,24 @@
-import { observable, computed } from "mobx";
+import { observable, computed, action } from "mobx";
import React = require("react");
import { SelectionManager } from "../util/SelectionManager";
import { observer } from "mobx-react";
import './DocumentDecorations.scss'
import { KeyStore } from '../../fields/KeyStore'
import { NumberField } from "../../fields/NumberField";
+import { props } from "bluebird";
+import { DragManager } from "../util/DragManager";
+import { LinkMenu } from "./nodes/LinkMenu";
+import { ListField } from "../../fields/ListField";
+const higflyout = require("@hig/flyout");
+const { anchorPoints } = higflyout;
+const Flyout = higflyout.default;
@observer
export class DocumentDecorations extends React.Component {
static Instance: DocumentDecorations
private _resizer = ""
private _isPointerDown = false;
+ private _linkButton = React.createRef<HTMLDivElement>();
@observable private _hidden = false;
constructor(props: Readonly<{}>) {
@@ -52,6 +60,46 @@ export class DocumentDecorations extends React.Component {
}
}
+ onLinkButtonDown = (e: React.PointerEvent): void => {
+ // if ()
+ // let linkMenu = new LinkMenu(SelectionManager.SelectedDocuments()[0]);
+ // linkMenu.Hidden = false;
+ console.log("down");
+
+ e.stopPropagation();
+ document.removeEventListener("pointermove", this.onLinkButtonMoved)
+ document.addEventListener("pointermove", this.onLinkButtonMoved);
+ document.removeEventListener("pointerup", this.onLinkButtonUp)
+ document.addEventListener("pointerup", this.onLinkButtonUp);
+
+ }
+
+ onLinkButtonUp = (e: PointerEvent): void => {
+ console.log("up");
+ document.removeEventListener("pointermove", this.onLinkButtonMoved)
+ document.removeEventListener("pointerup", this.onLinkButtonUp)
+ e.stopPropagation();
+ }
+
+
+ onLinkButtonMoved = (e: PointerEvent): void => {
+ console.log("moved");
+ let dragData: { [id: string]: any } = {};
+ dragData["linkSourceDoc"] = SelectionManager.SelectedDocuments()[0];
+ if (this._linkButton.current != null) {
+ DragManager.StartDrag(this._linkButton.current, dragData, {
+ handlers: {
+ dragComplete: action(() => { }),
+ },
+ hideSource: false
+ })
+ }
+ document.removeEventListener("pointermove", this.onLinkButtonMoved)
+ document.removeEventListener("pointerup", this.onLinkButtonUp)
+ e.stopPropagation();
+ }
+
+
onPointerMove = (e: PointerEvent): void => {
e.stopPropagation();
e.preventDefault();
@@ -138,6 +186,13 @@ export class DocumentDecorations extends React.Component {
}
}
+ changeFlyoutContent = (): void => {
+
+ }
+ // buttonOnPointerUp = (e: React.PointerEvent): void => {
+ // e.stopPropagation();
+ // }
+
render() {
var bounds = this.Bounds;
if (this.Hidden) {
@@ -147,6 +202,19 @@ export class DocumentDecorations extends React.Component {
console.log("DocumentDecorations: Bounds Error")
return (null);
}
+
+ let linkButton = null;
+ if (SelectionManager.SelectedDocuments().length > 0) {
+ let selFirst = SelectionManager.SelectedDocuments()[0];
+ 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} />
+ </Flyout>);
+ }
return (
<div id="documentDecorations-container" style={{
width: (bounds.r - bounds.x + 40) + "px",
@@ -163,7 +231,10 @@ export class DocumentDecorations extends React.Component {
<div id="documentDecorations-bottomLeftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
<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>
+
+ {linkButton}
+
+ </div >
)
}
} \ No newline at end of file
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 929fb42a1..955831fb6 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -1,5 +1,5 @@
import { observable, action, computed } from "mobx";
-import { CirclePicker, ColorResult } from 'react-color'
+import { CirclePicker, ColorResult } from 'react-color';
import React = require("react");
import "./InkingCanvas.scss"
import { InkTool } from "../../fields/InkField";
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 44fdf60ce..4a0f2b021 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -22,6 +22,8 @@ import "./Main.scss";
import { observer } from 'mobx-react';
import { Field, Opt } from '../../fields/Field';
import { InkingControl } from './InkingControl';
+import { RouteStore } from '../../server/RouteStore';
+import { Database } from '../../server/database';
@observer
export class Main extends React.Component {
@@ -29,6 +31,7 @@ export class Main extends React.Component {
@observable private mainContainer?: Document;
@observable private mainfreeform?: Document;
@observable private userWorkspaces: Document[] = [];
+ @observable private activeUsers: Document[] = [];
constructor(props: Readonly<{}>) {
super(props);
@@ -54,11 +57,11 @@ export class Main extends React.Component {
initAuthenticationRouters = () => {
// Load the user's active workspace, or create a new one if initial session after signup
- request.get(this.contextualize("getActiveWorkspaceId"), (error, response, body) => {
+ request.get(this.prepend(RouteStore.getActiveWorkspace), (error, response, body) => {
if (body) {
Server.GetField(body, field => {
if (field instanceof Document) {
- this.openDocument(field);
+ this.openWorkspace(field);
this.populateWorkspaces();
} else {
this.createNewWorkspace(true);
@@ -74,7 +77,7 @@ export class Main extends React.Component {
createNewWorkspace = (init: boolean): void => {
let mainDoc = Documents.DockDocument(JSON.stringify({ content: [{ type: 'row', content: [] }] }), { title: `Main Container ${this.userWorkspaces.length + 1}` });
let newId = mainDoc.Id;
- request.post(this.contextualize("addWorkspaceId"), {
+ request.post(this.prepend(RouteStore.addWorkspace), {
body: { target: newId },
json: true
}, () => { if (init) this.populateWorkspaces(); });
@@ -85,23 +88,24 @@ export class Main extends React.Component {
var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(freeformDoc)] }] };
mainDoc.SetText(KeyStore.Data, JSON.stringify(dockingLayout));
mainDoc.Set(KeyStore.ActiveFrame, freeformDoc);
- this.openDocument(mainDoc);
+ this.openWorkspace(mainDoc);
}, 0);
this.userWorkspaces.push(mainDoc);
+ mainDoc.GetList<Document>(KeyStore.ActiveUsers, []);
}
@action
populateWorkspaces = () => {
// retrieve all workspace documents from the server
- request.get(this.contextualize("getAllWorkspaceIds"), (error, res, body) => {
+ request.get(this.prepend(RouteStore.getAllWorkspaces), (error, res, body) => {
let ids = JSON.parse(body) as string[];
Server.GetFields(ids, action((fields: { [id: string]: Field }) => this.userWorkspaces = ids.map(id => fields[id] as Document)));
});
}
@action
- openDocument = (doc: Document): void => {
- request.post(this.contextualize("setActiveWorkspaceId"), {
+ openWorkspace = (doc: Document): void => {
+ request.post(this.prepend(RouteStore.setActiveWorkspace), {
body: { target: doc.Id },
json: true
});
@@ -115,27 +119,34 @@ export class Main extends React.Component {
}
}
- contextualize = (extension: string) => window.location.origin + "/" + extension;
+ prepend = (extension: string) => window.location.origin + extension;
render() {
let imgRef = React.createRef<HTMLDivElement>();
+ let pdfRef = React.createRef<HTMLDivElement>();
let webRef = React.createRef<HTMLDivElement>();
let textRef = React.createRef<HTMLDivElement>();
let schemaRef = React.createRef<HTMLDivElement>();
+ let videoRef = React.createRef<HTMLDivElement>();
+ let audioRef = React.createRef<HTMLDivElement>();
let colRef = React.createRef<HTMLDivElement>();
let workspacesRef = React.createRef<HTMLDivElement>();
- let pdfRef = React.createRef<HTMLDivElement>();
let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg";
let pdfurl = "http://www.adobe.com/support/products/enterprise/knowledgecenter/media/c4611_sample_explain.pdf"
let weburl = "https://cs.brown.edu/courses/cs166/";
+ 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: 200, height: 200, title: "a text note" }))
- let addColNode = action(() => Documents.FreeformDocument([], { width: 200, height: 200, title: "a feeform collection" }));
- let addPDFNode = action(() => Documents.PdfDocument(pdfurl, { width: 200, height: 200, title: "a schema collection" }));
+ 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, height: 200, title: "video node" }));
+ let addPDFNode = action(() => Documents.PdfDocument(pdfurl, { width: 200, height: 200, title: "a schema collection" }));
let addImageNode = action(() => Documents.ImageDocument(imgurl, { width: 200, height: 200, title: "an image of a cat" }));
let addWebNode = action(() => Documents.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" }));
+ let addAudioNode = action(() => Documents.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" }))
let addClick = (creator: () => Document) => action(() => this.mainfreeform!.GetList<Document>(KeyStore.Data, []).push(creator()));
@@ -155,7 +166,7 @@ export class Main extends React.Component {
ContainingCollectionView={undefined} />
<DocumentDecorations />
<ContextMenu />
- <WorkspacesMenu active={this.mainContainer} open={this.openDocument} new={this.createNewWorkspace} allWorkspaces={this.userWorkspaces} />
+ <WorkspacesMenu active={this.mainContainer} open={this.openWorkspace} new={this.createNewWorkspace} allWorkspaces={this.userWorkspaces} />
<div className="main-buttonDiv" style={{ bottom: '0px' }} ref={imgRef} >
<button onPointerDown={setupDrag(imgRef, addImageNode)} onClick={addClick(addImageNode)}>Add Image</button></div>
<div className="main-buttonDiv" style={{ bottom: '25px' }} ref={webRef} >
@@ -170,6 +181,10 @@ export class Main extends React.Component {
<button onClick={clearDatabase}>Clear Database</button></div>
<div className="main-buttonDiv" style={{ top: '25px' }} ref={workspacesRef}>
<button onClick={this.toggleWorkspaces}>View Workspaces</button></div>
+ <div className="main-buttonDiv" style={{ bottom: '175px' }} ref={videoRef}>
+ <button onPointerDown={setupDrag(videoRef, addVideoNode)} onClick={addClick(addVideoNode)}>Add Video</button></div>
+ <div className="main-buttonDiv" style={{ bottom: '200px' }} ref={audioRef}>
+ <button onPointerDown={setupDrag(audioRef, addAudioNode)} onClick={addClick(addAudioNode)}>Add Audio</button></div>
<div className="main-buttonDiv" style={{ bottom: '150px' }} ref={pdfRef}>
<button onPointerDown={setupDrag(pdfRef, addPDFNode)} onClick={addClick(addPDFNode)}>Add PDF</button></div>
<button className="main-undoButtons" style={{ bottom: '25px' }} onClick={() => UndoManager.Undo()}>Undo</button>
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index ceb9d0a55..b5cafe681 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -44,7 +44,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
(window as any).ReactDOM = ReactDOM;
}
public StartOtherDrag(dragDoc: Document, e: any) {
- this.AddRightSplit(dragDoc, true).contentItems[0].tab._dragListener.onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: () => { }, button: e.button })
+ this.AddRightSplit(dragDoc, true).contentItems[0].tab._dragListener.onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: () => { }, button: 0 })
}
@action
@@ -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);
@@ -174,17 +178,12 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
}
@action
onPointerDown = (e: React.PointerEvent): void => {
- if (e.button === 2 && this.props.active()) {
+ var className = (e.target as any).className;
+ if (className == "lm_drag_handle" || className == "lm_close" || className == "lm_maximise" || className == "lm_minimise" || className == "lm_close_tab") {
+ this._flush = true;
+ }
+ if (this.props.active()) {
e.stopPropagation();
- e.preventDefault();
- } else {
- var className = (e.target as any).className;
- if (className == "lm_drag_handle" || className == "lm_close" || className == "lm_maximise" || className == "lm_minimise" || className == "lm_close_tab") {
- this._flush = true;
- }
- if (e.buttons === 1 && this.props.active()) {
- e.stopPropagation();
- }
}
}
diff --git a/src/client/views/collections/CollectionFreeFormView.scss b/src/client/views/collections/CollectionFreeFormView.scss
index b7b5faf6d..28242c939 100644
--- a/src/client/views/collections/CollectionFreeFormView.scss
+++ b/src/client/views/collections/CollectionFreeFormView.scss
@@ -21,6 +21,13 @@
height: 100%;
}
}
+.collectionfreeformview-marquee{
+ border-style: dashed;
+ box-sizing: border-box;
+ position: absolute;
+ border-width: 1px;
+ border-color: black;
+}
.collectionfreeformview-overlay {
.collectionfreeformview > .jsx-parser{
diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx
index 0d5e15767..0c74d1852 100644
--- a/src/client/views/collections/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/CollectionFreeFormView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable, reaction, trace } from "mobx";
+import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
import { Document } from "../../../fields/Document";
import { FieldWaiting } from "../../../fields/Field";
@@ -10,9 +10,9 @@ 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 { CollectionView } from "../collections/CollectionView";
-import { CollectionPDFView } from "../collections/CollectionPDFView";
import { InkingCanvas } from "../InkingCanvas";
import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
import { DocumentView } from "../nodes/DocumentView";
@@ -25,13 +25,15 @@ import "./CollectionFreeFormView.scss";
import { COLLECTION_BORDER_WIDTH } from "./CollectionView";
import { CollectionViewBase } from "./CollectionViewBase";
import React = require("react");
-import { render } from "pug";
+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;
private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type)
@@ -60,18 +62,23 @@ export class CollectionFreeFormView extends CollectionViewBase {
super.drop(e, de);
const docView: DocumentView = de.data["documentView"];
let doc: Document = docView ? docView.props.Document : de.data["document"];
- let screenX = de.x - (de.data["xOffset"] as number || 0);
- let screenY = de.y - (de.data["yOffset"] as number || 0);
- const [x, y] = this.getTransform().transformPoint(screenX, screenY);
- doc.SetNumber(KeyStore.X, x);
- doc.SetNumber(KeyStore.Y, y);
- this.bringToFront(doc);
+ if (doc) {
+ let screenX = de.x - (de.data["xOffset"] as number || 0);
+ let screenY = de.y - (de.data["yOffset"] as number || 0);
+ const [x, y] = this.getTransform().transformPoint(screenX, screenY);
+ doc.SetNumber(KeyStore.X, x);
+ doc.SetNumber(KeyStore.Y, y);
+ this.bringToFront(doc);
+ }
}
+ @observable
+ _marquee = false;
+
@action
onPointerDown = (e: React.PointerEvent): void => {
- if (((e.button === 2 && this.props.active()) ||
- !e.defaultPrevented) && (!this.isAnnotationOverlay || this.zoomScaling != 1 || e.button == 0)) {
+ if (((e.button === 2 && this.props.active()) || !e.defaultPrevented) &&
+ (!this.isAnnotationOverlay || this.zoomScaling != 1 || e.button == 0)) {
document.removeEventListener("pointermove", this.onPointerMove);
document.addEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
@@ -85,10 +92,19 @@ export class CollectionFreeFormView extends CollectionViewBase {
@action
onPointerUp = (e: PointerEvent): void => {
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
+ if (this._marquee) {
+ document.removeEventListener("keydown", this.marqueeCommand);
+ }
e.stopPropagation();
- if (Math.abs(this._downX - e.clientX) < 3 && Math.abs(this._downY - e.clientY) < 3) {
+
+ if (this._marquee) {
+ if (!e.shiftKey) {
+ SelectionManager.DeselectAll();
+ }
+ var selectedDocs = this.marqueeSelect();
+ selectedDocs.map(s => this.props.CollectionView.SelectedDocs.push(s.Id));
+ }
+ else if (!this._marquee && Math.abs(this._downX - e.clientX) < 3 && Math.abs(this._downY - e.clientY) < 3) {
//show preview text cursor on tap
this._previewCursorVisible = true;
//select is not already selected
@@ -96,7 +112,48 @@ export class CollectionFreeFormView extends CollectionViewBase {
this.props.select(false);
}
}
+ this.cleanupInteractions();
+ }
+
+ @action
+ cleanupInteractions = () => {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ this._marquee = 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._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);
+ 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;
}
@action
@@ -104,17 +161,51 @@ export class CollectionFreeFormView extends CollectionViewBase {
if (!e.cancelBubble && this.props.active()) {
e.stopPropagation();
e.preventDefault();
- 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);
+ let wasMarquee = this._marquee;
+ this._marquee = e.buttons != 2 && !e.altKey && !e.metaKey;
+ if (this._marquee && !wasMarquee) {
+ this._previewCursorVisible = false;
+ document.addEventListener("keydown", this.marqueeCommand);
+ }
+
+ if (!this._marquee) {
+ 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;
}
@action
+ marqueeCommand = (e: KeyboardEvent) => {
+ if (e.key == "Backspace") {
+ this.marqueeSelect().map(d => this.props.removeDocument(d));
+ this.cleanupInteractions();
+ }
+ if (e.key == "c") {
+ 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);
+
+ let selected = this.marqueeSelect().map(m => m);
+ this.marqueeSelect().map(d => this.props.removeDocument(d));
+ //setTimeout(() => {
+ this.props.CollectionView.addDocument(Documents.FreeformDocument(selected.map(d => {
+ d.SetNumber(KeyStore.X, d.GetNumber(KeyStore.X, 0) - p[0] - v[0] / 2);
+ d.SetNumber(KeyStore.Y, d.GetNumber(KeyStore.Y, 0) - p[1] - v[1] / 2);
+ d.SetNumber(KeyStore.Page, this.props.Document.GetNumber(KeyStore.Page, 0));
+ d.SetText(KeyStore.Title, "" + d.GetNumber(KeyStore.Width, 0) + " " + d.GetNumber(KeyStore.Height, 0));
+ return d;
+ }), { x: p[0], y: p[1], panx: 0, pany: 0, width: v[0], height: v[1], title: "a nested collection" }));
+ // }, 100);
+ this.cleanupInteractions();
+ }
+ }
+
+ @action
onPointerWheel = (e: React.WheelEvent): void => {
e.stopPropagation();
e.preventDefault();
@@ -291,6 +382,10 @@ export class CollectionFreeFormView extends CollectionViewBase {
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);
@@ -317,6 +412,7 @@ export class CollectionFreeFormView extends CollectionViewBase {
<InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} />
{cursor}
{this.views}
+ {marquee}
</div>
{this.overlayView}
</div>
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index 7fd9f0f11..f22c07060 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -6,6 +6,7 @@ import { ContextMenu } from "../ContextMenu";
import { CollectionView, CollectionViewType } from "./CollectionView";
import { CollectionViewProps } from "./CollectionViewBase";
import React = require("react");
+import { FieldId } from "../../../fields/Field";
@observer
@@ -17,6 +18,7 @@ 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;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 2868e1322..17eeaa5a2 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -205,6 +205,8 @@ export class CollectionSchemaView extends CollectionViewBase {
)
let previewHandle = !this.props.active() ? (null) : (
<div className="collectionSchemaView-previewHandle" onPointerDown={this.onExpanderDown} />);
+ let dividerDragger = this._splitPercentage == 100 ? (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` }} >
<div className="collectionSchemaView-dropTarget" onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}>
@@ -234,7 +236,7 @@ export class CollectionSchemaView extends CollectionViewBase {
</div>
}
</Measure>
- <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} style={{ width: `${this.DIVIDER_WIDTH}px` }} />
+ {dividerDragger}
<div className="collectionSchemaView-previewRegion" style={{ width: `calc(${100 - this._splitPercentage}% - ${this.DIVIDER_WIDTH}px)` }}>
{content}
</div>
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 49df04163..17a0fbd23 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,4 +1,4 @@
-import { action, computed } from "mobx";
+import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
import { Document } from "../../../fields/Document";
import { ListField } from "../../../fields/ListField";
@@ -12,7 +12,7 @@ import { CollectionDockingView } from "./CollectionDockingView";
import { CollectionSchemaView } from "./CollectionSchemaView";
import { CollectionViewProps } from "./CollectionViewBase";
import { CollectionTreeView } from "./CollectionTreeView";
-import { Field } from "../../../fields/Field";
+import { Field, FieldId } from "../../../fields/Field";
export enum CollectionViewType {
Invalid,
@@ -27,6 +27,9 @@ export const COLLECTION_BORDER_WIDTH = 2;
@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}
@@ -96,7 +99,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) })
}
}
@@ -116,4 +118,4 @@ export class CollectionView extends React.Component<CollectionViewProps> {
{this.subView}
</div>)
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx
index 0a3b965f2..ddde05011 100644
--- a/src/client/views/collections/CollectionViewBase.tsx
+++ b/src/client/views/collections/CollectionViewBase.tsx
@@ -1,4 +1,4 @@
-import { action } from "mobx";
+import { action, runInAction } from "mobx";
import { Document } from "../../../fields/Document";
import { ListField } from "../../../fields/ListField";
import React = require("react");
@@ -11,6 +11,7 @@ import { Documents, DocumentOptions } from "../../documents/Documents";
import { Key } from "../../../fields/Key";
import { Transform } from "../../util/Transform";
import { CollectionView } from "./CollectionView";
+import { RouteStore } from "../../../server/RouteStore";
export interface CollectionViewProps {
fieldKey: Key;
@@ -47,7 +48,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 && (!docView.props.ContainingCollectionView || docView.props.ContainingCollectionView !== this.props.CollectionView)) {
if (docView.props.RemoveDocument) {
docView.props.RemoveDocument(docView.props.Document);
}
@@ -68,13 +69,17 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
let html = e.dataTransfer.getData("text/html");
let text = e.dataTransfer.getData("text/plain");
if (html && html.indexOf("<img") != 0) {
+ console.log("not good");
let htmlDoc = Documents.HtmlDocument(html, { ...options, width: 300, height: 300 });
htmlDoc.SetText(KeyStore.DocumentText, text);
this.props.addDocument(htmlDoc);
return;
}
+ console.log(e.dataTransfer.items.length);
+
for (let i = 0; i < e.dataTransfer.items.length; i++) {
+ const upload = window.location.origin + RouteStore.upload;
let item = e.dataTransfer.items[i];
if (item.kind === "string" && item.type.indexOf("uri") != -1) {
e.dataTransfer.items[i].getAsString(function (s) {
@@ -93,28 +98,58 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
})
}
- if (item.kind == "file" && item.type.indexOf("image")) {
+ let type = item.type
+ console.log(type)
+ if (item.kind == "file") {
let fReader = new FileReader()
let file = item.getAsFile();
-
- fReader.addEventListener("load", action("drop", () => {
- if (fReader.result) {
- let url = "" + fReader.result;
- let doc = Documents.ImageDocument(url, options)
- 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(doc);
- }
- }
- }), false)
+ let formData = new FormData()
if (file) {
- fReader.readAsDataURL(file)
+ formData.append('file', file)
}
+
+ fetch(upload, {
+ method: 'POST',
+ body: formData
+ })
+ .then((res: Response) => {
+ return res.json()
+ }).then(json => {
+
+ json.map((file: any) => {
+ let path = window.location.origin + file
+ runInAction(() => {
+ var doc: any;
+
+ if (type.indexOf("image") !== -1) {
+ doc = Documents.ImageDocument(path, { ...options, nativeWidth: 300, width: 300, })
+ }
+ if (type.indexOf("video") !== -1) {
+ doc = Documents.VideoDocument(path, { ...options, nativeWidth: 300, width: 300, })
+ }
+ 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) {
+ docs = new ListField<Document>();
+ that.props.Document.Set(KeyStore.Data, docs)
+ }
+ if (doc) {
+ docs.Data.push(doc);
+ }
+
+ }
+ })
+ })
+ })
+
+
}
}
}
diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss
new file mode 100644
index 000000000..704cdc31c
--- /dev/null
+++ b/src/client/views/nodes/AudioBox.scss
@@ -0,0 +1,4 @@
+.audiobox-cont{
+ height: 100%;
+ width: 100%;
+} \ No newline at end of file
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
new file mode 100644
index 000000000..f7d89843d
--- /dev/null
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -0,0 +1,44 @@
+import React = require("react")
+import { FieldViewProps, FieldView } from './FieldView';
+import { FieldWaiting } from '../../../fields/Field';
+import { observer } from "mobx-react"
+import { ContextMenu } from "../../views/ContextMenu";
+import { observable, action } from 'mobx';
+import { KeyStore } from '../../../fields/KeyStore';
+import { AudioField } from "../../../fields/AudioField";
+import "./AudioBox.scss"
+import { NumberField } from "../../../fields/NumberField";
+
+@observer
+export class AudioBox extends React.Component<FieldViewProps> {
+
+ public static LayoutString() { return FieldView.LayoutString(AudioBox) }
+
+ constructor(props: FieldViewProps) {
+ super(props);
+ }
+
+
+
+ componentDidMount() {
+ }
+
+ componentWillUnmount() {
+ }
+
+
+ render() {
+ let field = this.props.doc.Get(this.props.fieldKey)
+ let path = field == FieldWaiting ? "http://techslides.com/demos/samples/sample.mp3":
+ field instanceof AudioField ? field.Data.href : "http://techslides.com/demos/samples/sample.mp3";
+
+ return (
+ <div>
+ <audio controls className = "audiobox-cont">
+ <source src = {path} type="audio/mpeg"/>
+ Not supported.
+ </audio>
+ </div>
+ )
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index bfc45cf3a..da40f0dc1 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,4 +1,4 @@
-import { action, computed } from "mobx";
+import { action, computed, IReactionDisposer, runInAction, reaction } from "mobx";
import { observer } from "mobx-react";
import { Document } from "../../../fields/Document";
import { Field, FieldWaiting, Opt } from "../../../fields/Field";
@@ -16,12 +16,16 @@ 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";
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
@@ -86,6 +90,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
private _documentBindings: any = null;
private _downX: number = 0;
private _downY: number = 0;
+ private _reactionDisposer: Opt<IReactionDisposer>;
@computed get active(): boolean { return SelectionManager.IsSelected(this) || !this.props.ContainingCollectionView || this.props.ContainingCollectionView.active(); }
@computed get topMost(): boolean { return !this.props.ContainingCollectionView || this.props.ContainingCollectionView.collectionViewType == CollectionViewType.Docking; }
@computed get layout(): string { return this.props.Document.GetText(KeyStore.Layout, "<p>Error loading layout data</p>"); }
@@ -95,15 +100,15 @@ export class DocumentView extends React.Component<DocumentViewProps> {
onPointerDown = (e: React.PointerEvent): void => {
this._downX = e.clientX;
this._downY = e.clientY;
- if (e.shiftKey && e.buttons === 1) {
- CollectionDockingView.Instance.StartOtherDrag(this.props.Document, e);
+ if (e.shiftKey && e.buttons === 2) {
+ if (this.props.isTopMost) {
+ this.startDragging(e.pageX, e.pageY);
+ }
+ else CollectionDockingView.Instance.StartOtherDrag(this.props.Document, e);
e.stopPropagation();
} else {
if (this.active && !e.isDefaultPrevented()) {
e.stopPropagation();
- if (e.buttons === 2) {
- e.preventDefault();
- }
document.removeEventListener("pointermove", this.onPointerMove)
document.addEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp)
@@ -111,25 +116,74 @@ export class DocumentView extends React.Component<DocumentViewProps> {
}
}
}
+
+ private dropDisposer?: DragManager.DragDropDisposer;
+ protected createDropTarget = (ele: HTMLDivElement) => {
+
+ }
+
+ componentDidMount() {
+ if (this._mainCont.current) {
+ this.dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } });
+ }
+ runInAction(() => {
+ DocumentManager.Instance.DocumentViews.push(this);
+ })
+ this._reactionDisposer = reaction(
+ () => this.props.ContainingCollectionView && this.props.ContainingCollectionView.SelectedDocs.slice(),
+ () => {
+ if (this.props.ContainingCollectionView && this.props.ContainingCollectionView.SelectedDocs.indexOf(this.props.Document.Id) != -1)
+ SelectionManager.SelectDoc(this, true);
+ });
+ }
+
+ componentDidUpdate() {
+ if (this.dropDisposer) {
+ this.dropDisposer();
+ }
+ if (this._mainCont.current) {
+ this.dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } });
+ }
+ }
+
+ componentWillUnmount() {
+ if (this.dropDisposer) {
+ this.dropDisposer();
+ }
+ runInAction(() => {
+ DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1);
+
+ })
+ if (this._reactionDisposer) {
+ this._reactionDisposer();
+ }
+ }
+
+ startDragging(x: number, y: number) {
+ if (this._mainCont.current) {
+ const [left, top] = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+ let dragData: { [id: string]: any } = {};
+ dragData["documentView"] = this;
+ dragData["xOffset"] = x - left;
+ dragData["yOffset"] = y - top;
+ DragManager.StartDrag(this._mainCont.current, dragData, {
+ handlers: {
+ dragComplete: action(() => { }),
+ },
+ hideSource: true
+ })
+ }
+ }
+
onPointerMove = (e: PointerEvent): void => {
if (e.cancelBubble) {
return;
}
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._mainCont.current != null && !this.topMost) {
- const [left, top] = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
- let dragData: { [id: string]: any } = {};
- dragData["documentView"] = this;
- dragData["xOffset"] = e.x - left;
- dragData["yOffset"] = e.y - top;
- DragManager.StartDrag(this._mainCont.current, dragData, {
- handlers: {
- dragComplete: action(() => { }),
- },
- hideSource: true
- })
+ document.removeEventListener("pointerup", this.onPointerUp);
+ if (!this.topMost || e.buttons == 2 || e.altKey) {
+ this.startDragging(e.x, e.y);
}
}
e.stopPropagation();
@@ -170,6 +224,34 @@ export class DocumentView extends React.Component<DocumentViewProps> {
}
@action
+ drop = (e: Event, de: DragManager.DropEvent) => {
+ console.log("drop");
+ const sourceDocView: DocumentView = de.data["linkSourceDoc"];
+ if (!sourceDocView) {
+ return;
+ }
+ let sourceDoc: Document = sourceDocView.props.Document;
+ let destDoc: Document = this.props.Document;
+ if (this.props.isTopMost) {
+ return;
+ }
+ let linkDoc: Document = new Document();
+
+ linkDoc.Set(KeyStore.Title, new TextField("New Link"));
+ linkDoc.Set(KeyStore.LinkDescription, new TextField(""));
+ linkDoc.Set(KeyStore.LinkTags, new TextField("Default"));
+
+ sourceDoc.GetOrCreateAsync(KeyStore.LinkedToDocs, ListField, field => { (field as ListField<Document>).Data.push(linkDoc) });
+ linkDoc.Set(KeyStore.LinkedToDocs, destDoc);
+ destDoc.GetOrCreateAsync(KeyStore.LinkedFromDocs, ListField, field => { (field as ListField<Document>).Data.push(linkDoc) });
+ linkDoc.Set(KeyStore.LinkedFromDocs, sourceDoc);
+
+
+
+ e.stopPropagation();
+ }
+
+ @action
onContextMenu = (e: React.MouseEvent): void => {
e.stopPropagation();
let moved = Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3;
@@ -197,7 +279,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
get mainContent() {
return <JsxParser
- components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, WebBox, KeyValueBox, PDFBox }}
+ components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, WebBox, KeyValueBox, VideoBox, AudioBox, PDFBox }}
bindings={this._documentBindings}
jsx={this.layout}
showWarnings={true}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 9e63006d1..49f4cefce 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -8,10 +8,15 @@ import { NumberField } from "../../../fields/NumberField";
import { RichTextField } from "../../../fields/RichTextField";
import { ImageField } from "../../../fields/ImageField";
import { WebField } from "../../../fields/WebField";
+import { VideoField } from "../../../fields/VideoField"
import { Key } from "../../../fields/Key";
import { FormattedTextBox } from "./FormattedTextBox";
import { ImageBox } from "./ImageBox";
import { WebBox } from "./WebBox";
+import { VideoBox } from "./VideoBox";
+import { AudioBox } from "./AudioBox";
+import { AudioField } from "../../../fields/AudioField";
+
//
// these properties get assigned through the render() method of the DocumentView when it creates this node.
@@ -55,6 +60,12 @@ export class FieldView extends React.Component<FieldViewProps> {
}
else if (field instanceof WebField) {
return <WebBox {...this.props} />
+ }
+ else if (field instanceof VideoField){
+ return <VideoBox {...this.props}/>
+ }
+ else if (field instanceof AudioField){
+ return <AudioBox {...this.props}/>
}
// bcz: this belongs here, but it doesn't render well so taking it out for now
// else if (field instanceof HtmlField) {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index a6cee9957..5c7aaf9fe 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -69,7 +69,7 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
};
let field = this.props.doc.GetT(this.props.fieldKey, RichTextField);
- if (field && field != FieldWaiting) {
+ if (field && field != FieldWaiting && field.Data) {
state = EditorState.fromJSON(config, JSON.parse(field.Data));
} else {
state = EditorState.create(config);
@@ -113,7 +113,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();
}
}
@@ -151,8 +151,12 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
})
}
+ onKeyPress(e: React.KeyboardEvent) {
+ e.stopPropagation();
+ }
render() {
return (<div className="formattedTextBox-cont"
+ onKeyPress={this.onKeyPress}
onPointerDown={this.onPointerDown}
onContextMenu={this.specificContextMenu}
onWheel={this.onPointerWheel}
diff --git a/src/client/views/nodes/LinkBox.scss b/src/client/views/nodes/LinkBox.scss
new file mode 100644
index 000000000..00e5ebb3d
--- /dev/null
+++ b/src/client/views/nodes/LinkBox.scss
@@ -0,0 +1,39 @@
+.link-container {
+ width: 100%;
+ height: 30px;
+ display: flex;
+ flex-direction: row;
+ border-top: 0.5px solid #bababa;
+}
+
+.info-container {
+ width: 60%;
+ padding-top: 5px;
+ padding-left: 5px;
+ display: flex;
+ flex-direction: column
+}
+
+.link-name {
+ font-size: 11px;
+}
+
+.doc-name {
+ font-size: 8px;
+}
+
+.button-container {
+ width: 40%;
+ display: flex;
+ flex-direction: row;
+}
+
+.button {
+ height: 15px;
+ width: 15px;
+ margin: 8px 5px;
+ border-radius: 50%;
+ opacity: 0.6;
+ pointer-events: auto;
+ background-color: #2B6091;
+} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
new file mode 100644
index 000000000..69df676ff
--- /dev/null
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -0,0 +1,89 @@
+import { observable, computed, action } from "mobx";
+import React = require("react");
+import { SelectionManager } from "../../util/SelectionManager";
+import { observer } from "mobx-react";
+import './LinkBox.scss'
+import { KeyStore } from '../../../fields/KeyStore'
+import { props } from "bluebird";
+import { DocumentView } from "./DocumentView";
+import { Document } from "../../../fields/Document";
+import { ListField } from "../../../fields/ListField";
+import { DocumentManager } from "../../util/DocumentManager";
+import { LinkEditor } from "./LinkEditor";
+import { CollectionDockingView } from "../collections/CollectionDockingView";
+
+interface Props {
+ linkDoc: Document;
+ linkName: String;
+ pairedDoc: Document;
+ type: String;
+ showEditor: () => void
+}
+
+@observer
+export class LinkBox extends React.Component<Props> {
+
+ onViewButtonPressed = (e: React.PointerEvent): void => {
+ console.log("view down");
+ e.stopPropagation();
+ let docView = DocumentManager.Instance.getDocumentView(this.props.pairedDoc);
+ if (docView) {
+ docView.props.focus(this.props.pairedDoc);
+ } else {
+ CollectionDockingView.Instance.AddRightSplit(this.props.pairedDoc)
+ }
+ }
+
+ onEditButtonPressed = (e: React.PointerEvent): void => {
+ console.log("edit down");
+ e.stopPropagation();
+
+ this.props.showEditor();
+ }
+
+ onDeleteButtonPressed = (e: React.PointerEvent): void => {
+ console.log("delete down");
+ e.stopPropagation();
+ this.props.linkDoc.GetTAsync(KeyStore.LinkedFromDocs, Document, field => {
+ if (field) {
+ field.GetTAsync<ListField<Document>>(KeyStore.LinkedToDocs, ListField, field => {
+ if (field) {
+ field.Data.splice(field.Data.indexOf(this.props.linkDoc));
+ }
+ })
+ }
+ });
+ this.props.linkDoc.GetTAsync(KeyStore.LinkedToDocs, Document, field => {
+ if (field) {
+ field.GetTAsync<ListField<Document>>(KeyStore.LinkedFromDocs, ListField, field => {
+ if (field) {
+ field.Data.splice(field.Data.indexOf(this.props.linkDoc));
+ }
+ })
+ }
+ });
+ }
+
+ render() {
+
+ return (
+ //<LinkEditor linkBox={this} linkDoc={this.props.linkDoc} />
+ <div className="link-container">
+ <div className="info-container" onPointerDown={this.onViewButtonPressed}>
+ <div className="link-name">
+ <p>{this.props.linkName}</p>
+ </div>
+ <div className="doc-name">
+ <p>{this.props.type}{this.props.pairedDoc.Title}</p>
+ </div>
+ </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>
+ </div>
+ )
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkEditor.scss b/src/client/views/nodes/LinkEditor.scss
new file mode 100644
index 000000000..cb191dc8c
--- /dev/null
+++ b/src/client/views/nodes/LinkEditor.scss
@@ -0,0 +1,29 @@
+.edit-container {
+ width: 100%;
+ height: auto;
+ display: flex;
+ flex-direction: column;
+}
+
+.name-input {
+ margin-bottom: 10px;
+ padding: 5px;
+ font-size: 12px;
+}
+
+.description-input {
+ font-size: 12px;
+ padding: 5px;
+ margin-bottom: 10px;
+}
+
+.save-button {
+ width: 50px;
+ height: 20px;
+ background-color: #2B6091;
+ margin: 0 auto;
+ color: white;
+ text-align: center;
+ line-height: 20px;
+ font-size: 12px;
+} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkEditor.tsx b/src/client/views/nodes/LinkEditor.tsx
new file mode 100644
index 000000000..3f7b4bf2d
--- /dev/null
+++ b/src/client/views/nodes/LinkEditor.tsx
@@ -0,0 +1,58 @@
+import { observable, computed, action } from "mobx";
+import React = require("react");
+import { SelectionManager } from "../../util/SelectionManager";
+import { observer } from "mobx-react";
+import './LinkEditor.scss'
+import { KeyStore } from '../../../fields/KeyStore'
+import { props } from "bluebird";
+import { DocumentView } from "./DocumentView";
+import { Document } from "../../../fields/Document";
+import { TextField } from "../../../fields/TextField";
+import { link } from "fs";
+
+interface Props {
+ linkDoc: Document;
+ showLinks: () => void;
+}
+
+@observer
+export class LinkEditor extends React.Component<Props> {
+
+ @observable private _nameInput: string = this.props.linkDoc.GetText(KeyStore.Title, "");
+ @observable private _descriptionInput: string = this.props.linkDoc.GetText(KeyStore.LinkDescription, "");
+
+
+ onSaveButtonPressed = (e: React.PointerEvent): void => {
+ console.log("view down");
+ e.stopPropagation();
+
+ this.props.linkDoc.SetData(KeyStore.Title, this._nameInput, TextField);
+ this.props.linkDoc.SetData(KeyStore.LinkDescription, this._descriptionInput, TextField);
+
+ this.props.showLinks();
+ }
+
+
+
+ render() {
+
+ return (
+ <div className="edit-container">
+ <input onChange={this.onNameChanged} className="name-input" type="text" value={this._nameInput} placeholder="Name . . ."></input>
+ <textarea onChange={this.onDescriptionChanged} className="description-input" value={this._descriptionInput} placeholder="Description . . ."></textarea>
+ <div className="save-button" onPointerDown={this.onSaveButtonPressed}>SAVE</div>
+ </div>
+
+ )
+ }
+
+ @action
+ onNameChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this._nameInput = e.target.value;
+ }
+
+ @action
+ onDescriptionChanged = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
+ this._descriptionInput = e.target.value;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkMenu.scss b/src/client/views/nodes/LinkMenu.scss
new file mode 100644
index 000000000..a120ab2a7
--- /dev/null
+++ b/src/client/views/nodes/LinkMenu.scss
@@ -0,0 +1,20 @@
+#linkMenu-container {
+ width: 100%;
+ height: auto;
+ display: flex;
+ flex-direction: column;
+}
+
+#linkMenu-searchBar {
+ width: 100%;
+ padding: 5px;
+ margin-bottom: 10px;
+ font-size: 12px;
+}
+
+#linkMenu-list {
+ margin-top: 5px;
+ width: 100%;
+ height: 100px;
+ overflow-y: scroll;
+} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx
new file mode 100644
index 000000000..5c6b06d00
--- /dev/null
+++ b/src/client/views/nodes/LinkMenu.tsx
@@ -0,0 +1,54 @@
+import { action, observable } from "mobx";
+import { observer } from "mobx-react";
+import { Document } from "../../../fields/Document";
+import { FieldWaiting } from "../../../fields/Field";
+import { Key } from "../../../fields/Key";
+import { KeyStore } from '../../../fields/KeyStore';
+import { ListField } from "../../../fields/ListField";
+import { DocumentView } from "./DocumentView";
+import { LinkBox } from "./LinkBox";
+import { LinkEditor } from "./LinkEditor";
+import './LinkMenu.scss';
+import React = require("react");
+
+interface Props {
+ docView: DocumentView;
+ changeFlyout: () => void
+}
+
+@observer
+export class LinkMenu extends React.Component<Props> {
+
+ @observable private _editingLink?: Document;
+
+ renderLinkItems(links: Document[], key: Key, type: string) {
+ return links.map(link => {
+ let doc = link.GetT(key, Document);
+ if (doc && doc != FieldWaiting) {
+ return <LinkBox key={doc.Id} linkDoc={link} linkName={link.Title} pairedDoc={doc} showEditor={action(() => this._editingLink = link)} type={type} />
+ }
+ })
+ }
+
+ render() {
+ //get list of links from document
+ let linkFrom: Document[] = this.props.docView.props.Document.GetData(KeyStore.LinkedFromDocs, ListField, []);
+ let linkTo: Document[] = this.props.docView.props.Document.GetData(KeyStore.LinkedToDocs, ListField, []);
+ if (this._editingLink === undefined) {
+ return (
+ <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: ")}
+ </div>
+ </div>
+ )
+ } else {
+ return (
+ <LinkEditor linkDoc={this._editingLink} showLinks={action(() => this._editingLink = undefined)}></LinkEditor>
+ )
+ }
+
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 70a70c7c8..1f873f8c5 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -17,6 +17,7 @@ import "./ImageBox.scss";
import "./PDFBox.scss";
import { Sticky } from './Sticky'; //you should look at sticky and annotation, because they are used here
import React = require("react")
+import { RouteStore } from "../../../server/RouteStore";
/** ALSO LOOK AT: Annotation.tsx, Sticky.tsx
* This method renders PDF and puts all kinds of functionalities such as annotation, highlighting,
@@ -423,7 +424,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();
@@ -439,7 +442,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
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}`}>
+ <Document file={window.origin + RouteStore.corsProxy + `${pdfUrl}`}>
<Measure onResize={this.setScaling}>
{({ measureRef }) =>
<div className="pdfBox-page" ref={measureRef}>
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
new file mode 100644
index 000000000..7306450d9
--- /dev/null
+++ b/src/client/views/nodes/VideoBox.scss
@@ -0,0 +1,4 @@
+.videobox-cont{
+ width: 100%;
+ height: 100%;
+} \ No newline at end of file
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
new file mode 100644
index 000000000..22ff5c5ad
--- /dev/null
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -0,0 +1,43 @@
+import React = require("react")
+import { FieldViewProps, FieldView } from './FieldView';
+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';
+
+@observer
+export class VideoBox extends React.Component<FieldViewProps> {
+
+ public static LayoutString() { return FieldView.LayoutString(VideoBox) }
+
+ constructor(props: FieldViewProps) {
+ super(props);
+ }
+
+
+
+ componentDidMount() {
+ }
+
+ componentWillUnmount() {
+ }
+
+
+ render() {
+ let field = this.props.doc.Get(this.props.fieldKey)
+ 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";
+
+ return (
+ <div>
+ <video width = {200} height = {200} controls className = "videobox-cont">
+ <source src = {path} type = "video/mp4"/>
+ Not supported.
+ </video>
+ </div>
+ )
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index e72b3c4da..a535b2638 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -4,6 +4,7 @@
position: absolute;
width: 100%;
height: 100%;
+ overflow: scroll;
}
.webBox-button {
diff --git a/src/fields/AudioField.ts b/src/fields/AudioField.ts
new file mode 100644
index 000000000..aefcc15c1
--- /dev/null
+++ b/src/fields/AudioField.ts
@@ -0,0 +1,31 @@
+import { BasicField } from "./BasicField";
+import { Field, FieldId } from "./Field";
+import { Types } from "../server/Message";
+
+export class AudioField extends BasicField<URL> {
+ constructor(data: URL | undefined = undefined, id?: FieldId, save: boolean = true) {
+ super(data == undefined ? new URL("http://techslides.com/demos/samples/sample.mp3") : data, save, id);
+ }
+
+ toString(): string {
+ return this.Data.href;
+ }
+
+
+ ToScriptString(): string {
+ return `new AudioField("${this.Data}")`;
+ }
+
+ Copy(): Field {
+ return new AudioField(this.Data);
+ }
+
+ ToJson(): { type: Types, data: URL, _id: string } {
+ return {
+ type: Types.Audio,
+ data: this.Data,
+ _id: this.Id
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/fields/Document.ts b/src/fields/Document.ts
index 2e873439c..25e239417 100644
--- a/src/fields/Document.ts
+++ b/src/fields/Document.ts
@@ -1,6 +1,6 @@
import { Key } from "./Key"
import { KeyStore } from "./KeyStore";
-import { Field, Cast, FieldWaiting, FieldValue, FieldId } from "./Field"
+import { Field, Cast, FieldWaiting, FieldValue, FieldId, Opt } from "./Field"
import { NumberField } from "./NumberField";
import { ObservableMap, computed, action } from "mobx";
import { TextField } from "./TextField";
@@ -128,6 +128,12 @@ export class Document extends Field {
return false;
}
+ GetTAsync<T extends Field>(key: Key, ctor: { new(): T }, callback: (field: Opt<T>) => void): boolean {
+ return this.GetAsync(key, (field) => {
+ callback(Cast(field, ctor));
+ })
+ }
+
/**
* Same as {@link Document#GetAsync}, except a field of the given type
* will be created if there is no field associated with the given key,
diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts
index f67257093..71b189e19 100644
--- a/src/fields/KeyStore.ts
+++ b/src/fields/KeyStore.ts
@@ -27,8 +27,13 @@ export namespace KeyStore {
export const Caption = new Key("Caption");
export const ActiveFrame = new Key("ActiveFrame");
export const DocumentText = new Key("DocumentText");
+ export const LinkedToDocs = new Key("LinkedToDocs");
+ export const LinkedFromDocs = new Key("LinkedFromDocs");
+ export const LinkDescription = new Key("LinkDescription");
+ export const LinkTags = new Key("LinkTag");
export const Thumbnail = new Key("Thumbnail");
export const CurPage = new Key("CurPage");
export const NumPages = new Key("NumPages");
export const Ink = new Key("Ink");
+ export const ActiveUsers = new Key("Active Users");
}
diff --git a/src/fields/VideoField.ts b/src/fields/VideoField.ts
new file mode 100644
index 000000000..5f4ae19bf
--- /dev/null
+++ b/src/fields/VideoField.ts
@@ -0,0 +1,30 @@
+import { BasicField } from "./BasicField";
+import { Field, FieldId } from "./Field";
+import { Types } from "../server/Message";
+
+export class VideoField extends BasicField<URL> {
+ constructor(data: URL | undefined = undefined, id?: FieldId, save: boolean = true) {
+ super(data == undefined ? new URL("http://techslides.com/demos/sample-videos/small.mp4") : data, save, id);
+ }
+
+ toString(): string {
+ return this.Data.href;
+ }
+
+ ToScriptString(): string {
+ return `new VideoField("${this.Data}")`;
+ }
+
+ Copy(): Field {
+ return new VideoField(this.Data);
+ }
+
+ ToJson(): { type: Types, data: URL, _id: string } {
+ return {
+ type: Types.Video,
+ data: this.Data,
+ _id: this.Id
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/server/Message.ts b/src/server/Message.ts
index 5e97a5edf..8a00f6b59 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -45,7 +45,7 @@ export class GetFieldArgs {
}
export enum Types {
- Number, List, Key, Image, Web, Document, Text, RichText, DocumentReference, Html, Ink, PDF
+ Number, List, Key, Image, Web, Document, Text, RichText, DocumentReference, Html, Video, Audio, Ink, PDF
}
export class DocumentTransfer implements Transferable {
diff --git a/src/server/RouteStore.ts b/src/server/RouteStore.ts
new file mode 100644
index 000000000..ace2152d7
--- /dev/null
+++ b/src/server/RouteStore.ts
@@ -0,0 +1,28 @@
+// PREPEND ALL ROUTES WITH FORWARD SLASHES!
+
+export enum RouteStore {
+ // GENERAL
+ root = "/root",
+ home = "/home",
+ corsProxy = "/corsProxy",
+ delete = "/delete",
+
+ // UPLOAD AND STATIC FILE SERVING
+ public = "/public",
+ upload = "/upload",
+ images = "/images",
+
+ // USER AND WORKSPACES
+ addWorkspace = "/addWorkspaceId",
+ getAllWorkspaces = "/getAllWorkspaceIds",
+ getActiveWorkspace = "/getActiveWorkspaceId",
+ setActiveWorkspace = "/setActiveWorkspaceId",
+
+ // AUTHENTICATION
+ signup = "/signup",
+ login = "/login",
+ logout = "/logout",
+ forgot = "/forgotpassword",
+ reset = "/reset/:token",
+
+} \ No newline at end of file
diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts
index 3b9d14891..5331c9e30 100644
--- a/src/server/ServerUtil.ts
+++ b/src/server/ServerUtil.ts
@@ -1,17 +1,24 @@
-import {HtmlField} from '../fields/HtmlField';
+
+import { Field } from './../fields/Field';
+import { TextField } from './../fields/TextField';
+import { NumberField } from './../fields/NumberField';
+import { RichTextField } from './../fields/RichTextField';
+import { Key } from './../fields/Key';
+import { ImageField } from './../fields/ImageField';
+import { ListField } from './../fields/ListField';
+import { Document } from './../fields/Document';
+import { Server } from './../client/Server';
+import { Types } from './Message';
+import { Utils } from '../Utils';
+import { HtmlField } from '../fields/HtmlField';
+import { WebField } from '../fields/WebField';
+import { AudioField } from '../fields/AudioField';
+import { VideoField } from '../fields/VideoField';
import {InkField} from '../fields/InkField';
import {PDFField} from '../fields/PDFField';
-import {WebField} from '../fields/WebField';
-import {Utils} from '../Utils';
-import {Document} from './../fields/Document';
-import {Field} from './../fields/Field';
-import {ImageField} from './../fields/ImageField';
-import {Key} from './../fields/Key';
-import {ListField} from './../fields/ListField';
-import {NumberField} from './../fields/NumberField';
-import {RichTextField} from './../fields/RichTextField';
-import {TextField} from './../fields/TextField';
-import {Types} from './Message';
+
+
+
export class ServerUtils {
public static FromJson(json: any): Field {
@@ -44,6 +51,10 @@ export class ServerUtils {
return new PDFField(new URL(data), id, false)
case Types.List:
return ListField.FromJson(id, data)
+ case Types.Audio:
+ return new AudioField(new URL(data), id, false)
+ case Types.Video:
+ return new VideoField(new URL(data), id, false)
case Types.Ink:
return InkField.FromJson(id, data);
case Types.Document:
diff --git a/src/server/authentication/config/passport.ts b/src/server/authentication/config/passport.ts
index d90bedb18..b6fe15655 100644
--- a/src/server/authentication/config/passport.ts
+++ b/src/server/authentication/config/passport.ts
@@ -4,6 +4,7 @@ import * as mongodb from 'mongodb';
import * as _ from "lodash";
import { default as User } from '../models/user_model';
import { Request, Response, NextFunction } from "express";
+import { RouteStore } from '../../RouteStore';
const LocalStrategy = passportLocal.Strategy;
@@ -35,7 +36,7 @@ export let isAuthenticated = (req: Request, res: Response, next: NextFunction) =
if (req.isAuthenticated()) {
return next();
}
- return res.redirect("/login");
+ return res.redirect(RouteStore.login);
}
export let isAuthorized = (req: Request, res: Response, next: NextFunction) => {
diff --git a/src/server/authentication/controllers/user_controller.ts b/src/server/authentication/controllers/user_controller.ts
index 7b89b5152..2cef958e8 100644
--- a/src/server/authentication/controllers/user_controller.ts
+++ b/src/server/authentication/controllers/user_controller.ts
@@ -10,18 +10,7 @@ import * as pug from 'pug';
import * as async from 'async';
import * as nodemailer from 'nodemailer';
import c = require("crypto");
-
-
-/**
- * GET /
- * Whenever a user navigates to the root of Dash
- * (doesn't specify a sub-route), redirect to login.
- * If the user is already signed in, it will effectively
- * automatically redirect them to /home instead
- */
-export let getEntry = (req: Request, res: Response) => {
- res.redirect("/login");
-}
+import { RouteStore } from "../../RouteStore";
/**
* GET /signup
@@ -31,7 +20,7 @@ export let getEntry = (req: Request, res: Response) => {
export let getSignup = (req: Request, res: Response) => {
if (req.user) {
let user = req.user;
- return res.redirect("/home");
+ return res.redirect(RouteStore.home);
}
res.render("signup.pug", {
title: "Sign Up",
@@ -56,7 +45,7 @@ export let postSignup = (req: Request, res: Response, next: NextFunction) => {
title: "Sign Up",
user: req.user,
});
- return res.redirect("/signup");
+ return res.redirect(RouteStore.signup);
}
const email = req.body.email;
@@ -71,7 +60,7 @@ export let postSignup = (req: Request, res: Response, next: NextFunction) => {
User.findOne({ email }, (err, existingUser) => {
if (err) { return next(err); }
if (existingUser) {
- return res.redirect("/login");
+ return res.redirect(RouteStore.login);
}
user.save((err) => {
if (err) { return next(err); }
@@ -79,7 +68,7 @@ export let postSignup = (req: Request, res: Response, next: NextFunction) => {
if (err) {
return next(err);
}
- res.redirect("/home");
+ res.redirect(RouteStore.home);
});
});
});
@@ -93,7 +82,7 @@ export let postSignup = (req: Request, res: Response, next: NextFunction) => {
*/
export let getLogin = (req: Request, res: Response) => {
if (req.user) {
- return res.redirect("/home");
+ return res.redirect(RouteStore.home);
}
res.render("login.pug", {
title: "Log In",
@@ -104,7 +93,7 @@ export let getLogin = (req: Request, res: Response) => {
/**
* POST /login
* Sign in using email and password.
- * On failure, redirect to login page
+ * On failure, redirect to signup page
*/
export let postLogin = (req: Request, res: Response, next: NextFunction) => {
req.assert("email", "Email is not valid").isEmail();
@@ -115,17 +104,17 @@ export let postLogin = (req: Request, res: Response, next: NextFunction) => {
if (errors) {
req.flash("errors", "Unable to login at this time. Please try again.");
- return res.redirect("/signup");
+ return res.redirect(RouteStore.signup);
}
passport.authenticate("local", (err: Error, user: DashUserModel, info: IVerifyOptions) => {
if (err) { return next(err); }
if (!user) {
- return res.redirect("/signup");
+ return res.redirect(RouteStore.signup);
}
req.logIn(user, (err) => {
if (err) { return next(err); }
- res.redirect("/home");
+ res.redirect(RouteStore.home);
});
})(req, res, next);
};
@@ -136,16 +125,12 @@ export let postLogin = (req: Request, res: Response, next: NextFunction) => {
* and destroys the user's current session.
*/
export let getLogout = (req: Request, res: Response) => {
- const dashUser: DashUserModel | undefined = req.user;
- if (dashUser) {
- dashUser.update({ $set: { didSelectSessionWorkspace: false } }, () => { })
- }
req.logout();
const sess = req.session;
if (sess) {
sess.destroy((err) => { if (err) { console.log(err); } });
}
- res.redirect('/login');
+ res.redirect(RouteStore.login);
}
export let getForgot = function (req: Request, res: Response) {
@@ -172,7 +157,7 @@ export let postForgot = function (req: Request, res: Response, next: NextFunctio
User.findOne({ email }, function (err, user: DashUserModel) {
if (!user) {
// NO ACCOUNT WITH SUBMITTED EMAIL
- return res.redirect('/forgot');
+ return res.redirect(RouteStore.forgot);
}
user.passwordResetToken = token;
user.passwordResetExpires = new Date(Date.now() + 3600000); // 1 HOUR
@@ -205,14 +190,14 @@ export let postForgot = function (req: Request, res: Response, next: NextFunctio
}
], function (err) {
if (err) return next(err);
- res.redirect('/forgot');
+ res.redirect(RouteStore.forgot);
})
}
export let getReset = function (req: Request, res: Response) {
User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } }, function (err, user: DashUserModel) {
if (!user || err) {
- return res.redirect('/forgot');
+ return res.redirect(RouteStore.forgot);
}
res.render("reset.pug", {
title: "Reset Password",
@@ -242,7 +227,7 @@ export let postReset = function (req: Request, res: Response) {
user.save(function (err) {
if (err) {
- return res.redirect("/login");
+ return res.redirect(RouteStore.login);
}
req.logIn(user, function (err) {
if (err) {
@@ -273,6 +258,6 @@ export let postReset = function (req: Request, res: Response) {
});
}
], function (err) {
- res.redirect('/login');
+ res.redirect(RouteStore.login);
});
} \ No newline at end of file
diff --git a/src/server/authentication/models/user_model.ts b/src/server/authentication/models/user_model.ts
index 29076ba19..3d4ed6896 100644
--- a/src/server/authentication/models/user_model.ts
+++ b/src/server/authentication/models/user_model.ts
@@ -23,6 +23,7 @@ export type DashUserModel = mongoose.Document & {
allWorkspaceIds: Array<String>,
activeWorkspaceId: String,
+ activeUsersId: String,
profile: {
name: string,
@@ -53,6 +54,7 @@ const userSchema = new mongoose.Schema({
default: []
},
activeWorkspaceId: String,
+ activeUsersId: String,
facebook: String,
twitter: String,
diff --git a/src/server/database.ts b/src/server/database.ts
index 1553dd94e..f414266e2 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -16,12 +16,12 @@ export class Database {
})
}
- public update(id: string, value: any) {
+ public update(id: string, value: any, callback: () => void) {
if (this.db) {
let collection = this.db.collection('documents');
collection.update({ _id: id }, { $set: value }, {
upsert: true
- });
+ }, callback);
}
}
diff --git a/src/server/index.ts b/src/server/index.ts
index fad30f3ad..6f6f620d8 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -3,6 +3,8 @@ const app = express()
import * as webpack from 'webpack'
import * as wdm from 'webpack-dev-middleware';
import * as whm from 'webpack-hot-middleware';
+import * as path from 'path'
+import * as formidable from 'formidable'
import * as passport from 'passport';
import { MessageStore, Message, SetFieldArgs, GetFieldArgs, Transferable } from "./Message";
import { Client } from './Client';
@@ -17,7 +19,7 @@ import * as bcrypt from "bcrypt-nodejs";
import { Document } from '../fields/Document';
import * as io from 'socket.io'
import * as passportConfig from './authentication/config/passport';
-import { getLogin, postLogin, getSignup, postSignup, getLogout, getEntry, postReset, getForgot, postForgot, getReset } from './authentication/controllers/user_controller';
+import { getLogin, postLogin, getSignup, postSignup, getLogout, postReset, getForgot, postForgot, getReset } from './authentication/controllers/user_controller';
const config = require('../../webpack.config');
const compiler = webpack(config);
const port = 1050; // default port to listen
@@ -33,10 +35,10 @@ import c = require("crypto");
const MongoStore = require('connect-mongo')(session);
const mongoose = require('mongoose');
import { performance } from 'perf_hooks'
-import * as path from 'path'
import User, { DashUserModel } from './authentication/models/user_model';
import * as fs from 'fs';
import * as request from 'request'
+import { RouteStore } from './RouteStore';
const download = (url: string, dest: fs.PathLike) => {
request.get(url).pipe(fs.createWriteStream(dest));
@@ -61,11 +63,7 @@ app.use(session({
url: 'mongodb://localhost:27017/Dash'
})
}));
-// app.use(cookieSession({
-// name: 'authentication',
-// keys: [`${c.randomBytes(8)}`, `${c.randomBytes(8)}`, `${c.randomBytes(8)}`],
-// maxAge: 7 * 24 * 60 * 60 * 1000
-// }));
+
app.use(flash());
app.use(expressFlash());
app.use(bodyParser.json());
@@ -78,97 +76,123 @@ app.use((req, res, next) => {
next();
});
-// AUTHENTICATION ROUTING
-
enum Method {
- Get,
- Post
+ GET,
+ POST
}
+/**
+ * Please invoke this function when adding a new route to Dash's server.
+ * It ensures that any requests leading to or containing user-sensitive information
+ * does not execute unless Passport authentication detects a user logged in.
+ * @param method whether or not the request is a GET or a POST
+ * @param route the forward slash prepended path name (reference and add to RouteStore.ts)
+ * @param handler the action to invoke, recieving a DashUserModel and the expected request and response
+ * @param onRejection an optional callback invoked on return if no user is found to be logged in
+ */
function addSecureRoute(method: Method,
route: string,
handler: (user: DashUserModel, req: express.Request, res: express.Response) => void,
- nope: (res: express.Response) => any) {
- route = "/" + route;
+ onRejection: (res: express.Response) => any = (res) => res.redirect(RouteStore.logout)) {
switch (method) {
- case Method.Get:
+ case Method.GET:
app.get(route, (req, res) => {
const dashUser: DashUserModel = req.user;
- if (!dashUser) return nope(res);
+ if (!dashUser) return onRejection(res);
handler(dashUser, req, res);
});
break;
- case Method.Post:
+ case Method.POST:
app.post(route, (req, res) => {
const dashUser: DashUserModel = req.user;
- if (!dashUser) return nope(res);
+ if (!dashUser) return onRejection(res);
handler(dashUser, req, res);
});
break;
}
}
-// ***
-// Look for the definitions of these get and post
-// functions in the exports of user.ts
+// STATIC FILE SERVING
-addSecureRoute(Method.Get, "home", (user, req, res) => {
+let FieldStore: ObservableMap<FieldId, Field> = new ObservableMap();
+
+app.use(express.static(__dirname + RouteStore.public));
+app.use(RouteStore.images, express.static(__dirname + RouteStore.public))
+
+addSecureRoute(Method.POST, RouteStore.upload, (user, req, res) => {
+ let form = new formidable.IncomingForm()
+ form.uploadDir = __dirname + "/public/files/"
+ form.keepExtensions = true
+ // let path = req.body.path;
+ console.log("upload")
+ form.parse(req, (err, fields, files) => {
+ console.log("parsing")
+ let names: any[] = [];
+ for (const name in files) {
+ let file = files[name];
+ names.push(`/files/` + path.basename(file.path));
+ }
+ res.send(names);
+ });
+});
+
+// anyone attempting to navigate to localhost at this port will
+// first have to login
+addSecureRoute(Method.GET, RouteStore.root, (user, req, res) => {
+
+}, res => {
+ res.send()
+});
+
+// YAY! SHOW THEM THEIR WORKSPACES NOW
+addSecureRoute(Method.GET, RouteStore.home, (user, req, res) => {
res.sendFile(path.join(__dirname, '../../deploy/index.html'));
-}, res => res.redirect("/login"))
+});
-addSecureRoute(Method.Get, "getActiveWorkspaceId", (user, req, res) => {
+addSecureRoute(Method.GET, RouteStore.getActiveWorkspace, (user, req, res) => {
res.send(user.activeWorkspaceId || "");
-}, () => { });
+});
-addSecureRoute(Method.Get, "getAllWorkspaceIds", (user, req, res) => {
+addSecureRoute(Method.GET, RouteStore.getAllWorkspaces, (user, req, res) => {
res.send(JSON.stringify(user.allWorkspaceIds as Array<String>));
-}, () => { });
+});
-addSecureRoute(Method.Post, "setActiveWorkspaceId", (user, req) => {
+addSecureRoute(Method.POST, RouteStore.setActiveWorkspace, (user, req) => {
user.update({ $set: { activeWorkspaceId: req.body.target } }, () => { });
-}, () => { });
+});
-addSecureRoute(Method.Post, "addWorkspaceId", (user, req) => {
+addSecureRoute(Method.POST, RouteStore.addWorkspace, (user, req) => {
user.update({ $push: { allWorkspaceIds: req.body.target } }, () => { });
-}, () => { });
+});
-// anyone attempting to navigate to localhost at this port will
-// first have to login
-app.get("/", getEntry);
+// AUTHENTICATION
// Sign Up
-app.get("/signup", getSignup);
-app.post("/signup", postSignup);
+app.get(RouteStore.signup, getSignup);
+app.post(RouteStore.signup, postSignup);
// Log In
-app.get("/login", getLogin);
-app.post("/login", postLogin);
+app.get(RouteStore.login, getLogin);
+app.post(RouteStore.login, postLogin);
// Log Out
-app.get('/logout', getLogout);
-
-// ***
+app.get(RouteStore.logout, getLogout);
// FORGOT PASSWORD EMAIL HANDLING
-app.get('/forgot', getForgot)
-app.post('/forgot', postForgot)
+app.get(RouteStore.forgot, getForgot)
+app.post(RouteStore.forgot, postForgot)
// RESET PASSWORD EMAIL HANDLING
-app.get('/reset/:token', getReset);
-app.post('/reset/:token', postReset);
+app.get(RouteStore.reset, getReset);
+app.post(RouteStore.reset, postReset);
-let FieldStore: ObservableMap<FieldId, Field> = new ObservableMap();
-app.get("/hello", (req, res) => {
- res.send("<p>Hello</p>");
-})
-
-app.use("/corsProxy", (req, res) => {
+app.use(RouteStore.corsProxy, (req, res) => {
req.pipe(request(req.url.substring(1))).pipe(res);
});
-app.get("/delete", (req, res) => {
+app.get(RouteStore.delete, (req, res) => {
deleteAll();
- res.redirect("/");
+ res.redirect(RouteStore.home);
});
app.use(wdm(compiler, {
@@ -208,10 +232,6 @@ function barReceived(guid: String) {
clients[guid.toString()] = new Client(guid.toString());
}
-function addDocument(document: Document) {
-
-}
-
function getField([id, callback]: [string, (result: any) => void]) {
Database.Instance.getDocument(id, (result: any) => {
if (result) {
@@ -228,8 +248,9 @@ function getFields([ids, callback]: [string[], (result: any) => void]) {
}
function setField(socket: Socket, newValue: Transferable) {
- Database.Instance.update(newValue._id, newValue)
- socket.broadcast.emit(MessageStore.SetField.Message, newValue)
+ Database.Instance.update(newValue._id, newValue, () => {
+ socket.broadcast.emit(MessageStore.SetField.Message, newValue);
+ })
}
server.listen(serverPort);