aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/views/Main.tsx12
-rw-r--r--src/client/views/collections/CollectionFreeFormView.tsx15
-rw-r--r--src/client/views/collections/CollectionViewBase.tsx34
-rw-r--r--src/fields/KeyStore.ts2
-rw-r--r--src/fields/TupleField.ts59
-rw-r--r--src/server/Message.ts2
-rw-r--r--src/server/RouteStore.ts3
-rw-r--r--src/server/ServerUtil.ts7
-rw-r--r--src/server/index.ts80
9 files changed, 169 insertions, 45 deletions
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index e76a5e04b..268f70de1 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -38,6 +38,7 @@ import { faPenNib } from '@fortawesome/free-solid-svg-icons';
import { faFilm } from '@fortawesome/free-solid-svg-icons';
import { faMusic } from '@fortawesome/free-solid-svg-icons';
import Measure from 'react-measure';
+import { DashUserModel } from '../../server/authentication/models/user_model';
@observer
export class Main extends React.Component {
@@ -49,12 +50,13 @@ export class Main extends React.Component {
@observable public pheight: number = 0;
private mainDocId: string | undefined;
+ private currentUser?: DashUserModel;
constructor(props: Readonly<{}>) {
super(props);
// causes errors to be generated when modifying an observable outside of an action
configure({ enforceActions: "observed" });
- if (window.location.pathname !== "/home") {
+ if (window.location.pathname !== RouteStore.home) {
let pathname = window.location.pathname.split("/");
this.mainDocId = pathname[pathname.length - 1];
}
@@ -75,6 +77,14 @@ export class Main extends React.Component {
Documents.initProtos(() => {
this.initAuthenticationRouters();
});
+
+ request.get(this.prepend(RouteStore.getCurrUser), (error, response, body) => {
+ if (body) {
+ this.currentUser = body as DashUserModel;
+ } else {
+ throw new Error("There should be a user! Why does Dash think there isn't one?")
+ }
+ })
}
initEventListeners = () => {
diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx
index bb28dd20a..a3a596c37 100644
--- a/src/client/views/collections/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/CollectionFreeFormView.tsx
@@ -322,6 +322,7 @@ export class CollectionFreeFormView extends CollectionViewBase {
return (
<div className={`collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`}
onPointerDown={this.onPointerDown}
+ onPointerMove={(e) => super.setCursorPosition(this.props.ScreenToLocalTransform().transformPoint(e.screenX, e.screenY))}
onWheel={this.onPointerWheel}
onDrop={this.onDrop.bind(this)}
onDragOver={this.onDragOver}
@@ -329,6 +330,20 @@ export class CollectionFreeFormView extends CollectionViewBase {
style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px`, }}
tabIndex={0}
ref={this.createDropTarget}>
+ {super.getCursors().map(entry => {
+ let point = entry.Data[1]
+ return (
+ <div
+ style={{
+ position: 'absolute',
+ left: point[0],
+ top: point[1],
+ borderRadius: "50%",
+ background: "pink"
+ }}
+ />
+ );
+ })}
<div className="collectionfreeformview"
style={{ transformOrigin: "left top", transform: `translate(${dx}px, ${dy}px) scale(${this.zoomScaling}, ${this.zoomScaling}) translate(${panx}px, ${pany}px)` }}
ref={this._canvasRef}>
diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx
index 4a2761139..81d7f4077 100644
--- a/src/client/views/collections/CollectionViewBase.tsx
+++ b/src/client/views/collections/CollectionViewBase.tsx
@@ -1,4 +1,4 @@
-import { action, runInAction } from "mobx";
+import { action, runInAction, observable, computed } from "mobx";
import { Document } from "../../../fields/Document";
import { ListField } from "../../../fields/ListField";
import React = require("react");
@@ -12,6 +12,9 @@ import { Key } from "../../../fields/Key";
import { Transform } from "../../util/Transform";
import { CollectionView } from "./CollectionView";
import { RouteStore } from "../../../server/RouteStore";
+import { TupleField } from "../../../fields/TupleField";
+import { Server } from "mongodb";
+import { DashUserModel } from "../../../server/authentication/models/user_model";
export interface CollectionViewProps {
fieldKey: Key;
@@ -25,13 +28,17 @@ export interface CollectionViewProps {
panelHeight: () => number;
focus: (doc: Document) => void;
}
+
export interface SubCollectionViewProps extends CollectionViewProps {
active: () => boolean;
addDocument: (doc: Document) => void;
removeDocument: (doc: Document) => boolean;
CollectionView: CollectionView;
+ currentUser?: DashUserModel;
}
+export type CursorEntry = TupleField<DashUserModel, [number, number]>;
+
export class CollectionViewBase extends React.Component<SubCollectionViewProps> {
private dropDisposer?: DragManager.DragDropDisposer;
protected createDropTarget = (ele: HTMLDivElement) => {
@@ -43,6 +50,31 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
}
}
+ protected setCursorPosition(position: [number, number]) {
+ let user = this.props.currentUser;
+ if (user && user.id) {
+ let ind;
+ let doc = this.props.Document;
+ let cursors = doc.GetOrCreate<ListField<CursorEntry>>(KeyStore.Cursors, ListField, false).Data;
+ let entry = new TupleField<DashUserModel, [number, number]>([user.id, position]);
+ // if ((ind = cursors.findIndex(entry => entry.Data[0] === user.id)) > -1) {
+ // cursors[ind] = entry;
+ // } else {
+ // cursors.push(entry);
+ // }
+ }
+ }
+
+ protected getCursors(): CursorEntry[] {
+ let user = this.props.currentUser;
+ if (user && user.id) {
+ let doc = this.props.Document;
+ // return doc.GetList<CursorEntry>(KeyStore.Cursors, []).filter(entry => entry.Data[0] !== user.id);
+ }
+ return [];
+ }
+
+
@undoBatch
@action
protected drop(e: Event, de: DragManager.DropEvent) {
diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts
index 08a1c00b9..c6e58ee35 100644
--- a/src/fields/KeyStore.ts
+++ b/src/fields/KeyStore.ts
@@ -37,5 +37,5 @@ export namespace KeyStore {
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");
+ export const Cursors = new Key("Cursors");
}
diff --git a/src/fields/TupleField.ts b/src/fields/TupleField.ts
new file mode 100644
index 000000000..778bd5d2f
--- /dev/null
+++ b/src/fields/TupleField.ts
@@ -0,0 +1,59 @@
+import { action, IArrayChange, IArraySplice, IObservableArray, observe, observable, Lambda } from "mobx";
+import { Server } from "../client/Server";
+import { UndoManager } from "../client/util/UndoManager";
+import { Types } from "../server/Message";
+import { BasicField } from "./BasicField";
+import { Field, FieldId } from "./Field";
+
+export class TupleField<T, U> extends BasicField<[T, U]> {
+ constructor(data: [T, U], id?: FieldId, save: boolean = true) {
+ super(data, save, id);
+ if (save) {
+ Server.UpdateField(this);
+ }
+ this.observeTuple();
+ }
+
+ private observeDisposer: Lambda | undefined;
+ private observeTuple(): void {
+ this.observeDisposer = observe(this.Data as (T | U)[] as IObservableArray<T | U>, (change: IArrayChange<T | U> | IArraySplice<T | U>) => {
+ if (change.type === "update") {
+ UndoManager.AddEvent({
+ undo: () => this.Data[change.index] = change.oldValue,
+ redo: () => this.Data[change.index] = change.newValue
+ })
+ Server.UpdateField(this);
+ } else {
+ throw new Error("Why are you messing with the length of a tuple, huh?");
+ }
+ });
+ }
+
+ protected setData(value: [T, U]) {
+ if (this.observeDisposer) {
+ this.observeDisposer()
+ }
+ this.data = observable(value) as (T | U)[] as [T, U];
+ this.observeTuple();
+ }
+
+ UpdateFromServer(values: [T, U]) {
+ this.setData(values);
+ }
+
+ ToScriptString(): string {
+ return `new TupleField([${this.Data[0], this.Data[1]}])`;
+ }
+
+ Copy(): Field {
+ return new TupleField<T, U>(this.Data);
+ }
+
+ ToJson(): { type: Types, data: [T, U], _id: string } {
+ return {
+ type: Types.List,
+ 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 8a00f6b59..a2d1ab829 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, Video, Audio, Ink, PDF
+ Number, List, Key, Image, Web, Document, Text, RichText, DocumentReference, Html, Video, Audio, Ink, PDF, Tuple
}
export class DocumentTransfer implements Transferable {
diff --git a/src/server/RouteStore.ts b/src/server/RouteStore.ts
index f12aed85e..683fd6d7e 100644
--- a/src/server/RouteStore.ts
+++ b/src/server/RouteStore.ts
@@ -13,12 +13,15 @@ export enum RouteStore {
images = "/images",
// USER AND WORKSPACES
+ getCurrUser = "/getCurrentUser",
addWorkspace = "/addWorkspaceId",
getAllWorkspaces = "/getAllWorkspaceIds",
getActiveWorkspace = "/getActiveWorkspaceId",
setActiveWorkspace = "/setActiveWorkspaceId",
updateCursor = "/updateCursor",
+ openDocumentWithId = "/doc/:docId",
+
// AUTHENTICATION
signup = "/signup",
login = "/login",
diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts
index 5331c9e30..dce4bada5 100644
--- a/src/server/ServerUtil.ts
+++ b/src/server/ServerUtil.ts
@@ -14,8 +14,9 @@ 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 { InkField } from '../fields/InkField';
+import { PDFField } from '../fields/PDFField';
+import { TupleField } from '../fields/TupleField';
@@ -55,6 +56,8 @@ export class ServerUtils {
return new AudioField(new URL(data), id, false)
case Types.Video:
return new VideoField(new URL(data), id, false)
+ case Types.Tuple:
+ return new TupleField(data, id, false);
case Types.Ink:
return InkField.FromJson(id, data);
case Types.Document:
diff --git a/src/server/index.ts b/src/server/index.ts
index d0df95ca3..7baf896b7 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -77,6 +77,10 @@ app.use((req, res, next) => {
next();
});
+app.get("/hello", (req, res) => {
+ res.send("<p>Hello</p>");
+})
+
enum Method {
GET,
POST
@@ -88,26 +92,26 @@ enum Method {
* 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 handler the action to invoke, recieving a DashUserModel and, as expected, the Express.Request and Express.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,
+ handler: (user: DashUserModel, res: express.Response, req: express.Request) => void,
onRejection: (res: express.Response) => any = (res) => res.redirect(RouteStore.logout)) {
switch (method) {
case Method.GET:
app.get(route, (req, res) => {
const dashUser: DashUserModel = req.user;
if (!dashUser) return onRejection(res);
- handler(dashUser, req, res);
+ handler(dashUser, res, req);
});
break;
case Method.POST:
app.post(route, (req, res) => {
const dashUser: DashUserModel = req.user;
if (!dashUser) return onRejection(res);
- handler(dashUser, req, res);
+ handler(dashUser, res, req);
});
break;
}
@@ -120,66 +124,64 @@ 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);
- });
-});
+// GETTERS
// anyone attempting to navigate to localhost at this port will
// first have to login
-addSecureRoute(Method.GET, RouteStore.root, (user, req, res) => {
+addSecureRoute(Method.GET, RouteStore.root, (user, res) => {
res.redirect(RouteStore.home);
});
-// YAY! SHOW THEM THEIR WORKSPACES NOW
-addSecureRoute(Method.GET, RouteStore.home, (user, req, res) => {
+addSecureRoute(Method.GET, RouteStore.home, (user, res) => {
res.sendFile(path.join(__dirname, '../../deploy/index.html'));
});
-addSecureRoute(Method.GET, RouteStore.getActiveWorkspace, (user, req, res) => {
+addSecureRoute(Method.GET, RouteStore.openDocumentWithId, (user, res) => {
+ res.sendFile(path.join(__dirname, '../../deploy/index.html'));
+});
+
+addSecureRoute(Method.GET, RouteStore.getActiveWorkspace, (user, res) => {
res.send(user.activeWorkspaceId || "");
});
-addSecureRoute(Method.GET, RouteStore.getAllWorkspaces, (user, req, res) => {
+addSecureRoute(Method.GET, RouteStore.getAllWorkspaces, (user, res) => {
res.send(JSON.stringify(user.allWorkspaceIds));
});
-addSecureRoute(Method.POST, RouteStore.setActiveWorkspace, (user, req, res) => {
+addSecureRoute(Method.GET, RouteStore.getCurrUser, (user, res) => {
+ res.send(JSON.stringify(user));
+});
+
+// SETTERS
+
+addSecureRoute(Method.POST, RouteStore.setActiveWorkspace, (user, res, req) => {
user.update({ $set: { activeWorkspaceId: req.body.target } }, (err, raw) => {
res.sendStatus(err ? 500 : 200);
});
});
-addSecureRoute(Method.POST, RouteStore.addWorkspace, (user, req, res) => {
+addSecureRoute(Method.POST, RouteStore.addWorkspace, (user, res, req) => {
user.update({ $push: { allWorkspaceIds: req.body.target } }, (err, raw) => {
res.sendStatus(err ? 500 : 200);
});
});
-// define a route handler for the default home page
-// app.get("/", (req, res) => {
-// res.redirect("/doc/mainDoc");
-// // res.sendFile(path.join(__dirname, '../../deploy/index.html'));
-// });
-
-app.get("/doc/:docId", (req, res) => {
- res.sendFile(path.join(__dirname, '../../deploy/index.html'));
-})
-app.get("/hello", (req, res) => {
- res.send("<p>Hello</p>");
-})
+addSecureRoute(Method.POST, RouteStore.upload, (user, res, req) => {
+ 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);
+ });
+});
// AUTHENTICATION