diff options
-rw-r--r-- | src/client/views/Main.tsx | 24 | ||||
-rw-r--r-- | src/client/views/collections/CollectionFreeFormView.tsx | 38 | ||||
-rw-r--r-- | src/client/views/collections/CollectionViewBase.tsx | 44 | ||||
-rw-r--r-- | src/fields/TupleField.ts | 2 | ||||
-rw-r--r-- | src/server/ServerUtil.ts | 2 | ||||
-rw-r--r-- | src/server/authentication/models/user_utils.ts | 22 | ||||
-rw-r--r-- | src/server/index.ts | 179 |
7 files changed, 179 insertions, 132 deletions
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 268f70de1..fd756972b 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -39,6 +39,8 @@ 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'; +import { ServerUtils } from '../../server/ServerUtil'; +import { UserUtils } from '../../server/authentication/models/user_utils'; @observer export class Main extends React.Component { @@ -61,6 +63,8 @@ export class Main extends React.Component { this.mainDocId = pathname[pathname.length - 1]; } + UserUtils.loadCurrentUserId(); + library.add(faFont); library.add(faImage); library.add(faFilePdf); @@ -77,14 +81,6 @@ 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 = () => { @@ -101,7 +97,7 @@ 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.prepend(RouteStore.getActiveWorkspace), (error, response, body) => { + request.get(ServerUtils.prepend(RouteStore.getActiveWorkspace), (error, response, body) => { if (this.mainDocId || body) { Server.GetField(this.mainDocId || body, field => { if (field instanceof Document) { @@ -122,7 +118,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.prepend(RouteStore.addWorkspace), { + request.post(ServerUtils.prepend(RouteStore.addWorkspace), { body: { target: newId }, json: true }, () => { if (init) this.populateWorkspaces(); }); @@ -141,7 +137,7 @@ export class Main extends React.Component { @action populateWorkspaces = () => { // retrieve all workspace documents from the server - request.get(this.prepend(RouteStore.getAllWorkspaces), (error, res, body) => { + request.get(ServerUtils.prepend(RouteStore.getAllWorkspaces), (error, res, body) => { let ids = JSON.parse(body) as string[]; Server.GetFields(ids, action((fields: { [id: string]: Field }) => this.userWorkspaces = ids.map(id => fields[id] as Document))); }); @@ -149,7 +145,7 @@ export class Main extends React.Component { @action openWorkspace = (doc: Document): void => { - request.post(this.prepend(RouteStore.setActiveWorkspace), { + request.post(ServerUtils.prepend(RouteStore.setActiveWorkspace), { body: { target: doc.Id }, json: true }); @@ -163,8 +159,6 @@ export class Main extends React.Component { } } - prepend = (extension: string) => window.location.origin + extension; - render() { let imgRef = React.createRef<HTMLDivElement>(); let pdfRef = React.createRef<HTMLDivElement>(); @@ -233,7 +227,7 @@ export class Main extends React.Component { <div className="main-buttonDiv" style={{ top: '34px', left: '2px', position: 'absolute' }} ref={workspacesRef}> <button onClick={this.toggleWorkspaces}>Workspaces</button></div> <div className="main-buttonDiv" style={{ top: '34px', right: '1px', position: 'absolute' }} ref={logoutRef}> - <button onClick={() => request.get(this.prepend(RouteStore.logout), () => { })}>Log Out</button></div> + <button onClick={() => request.get(ServerUtils.prepend(RouteStore.logout), () => { })}>Log Out</button></div> <WorkspacesMenu active={this.mainContainer} open={this.openWorkspace} new={this.createNewWorkspace} allWorkspaces={this.userWorkspaces} /> {/* for the expandable add nodes menu. Not included with the above because once it expands it expands the whole div with it, making canvas interactions limited. */} diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx index a3a596c37..89edd1397 100644 --- a/src/client/views/collections/CollectionFreeFormView.tsx +++ b/src/client/views/collections/CollectionFreeFormView.tsx @@ -322,7 +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))} + onPointerMove={(e) => super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY))} onWheel={this.onPointerWheel} onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} @@ -330,20 +330,6 @@ 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}> @@ -351,11 +337,33 @@ export class CollectionFreeFormView extends CollectionViewBase { <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} /> <PreviewCursor container={this} addLiveTextDocument={this.addLiveTextBox} getTransform={this.getTransform} /> {this.views} + {super.getCursors().map(entry => { + if (entry.Data.length > 0) { + let point = entry.Data[1] + return ( + <div + key={entry.Data[0]} + style={{ + position: 'absolute', + transform: `translate(${point[0] - 10}px, ${point[1] - 10}px)`, + zIndex: 10000, + transformOrigin: 'center center', + width: "20px", + height: "20px", + borderRadius: "50%", + background: "pink", + border: "2px solid black" + }} + /> + ); + } + })} </div> <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.props.addDocument} removeDocument={this.props.removeDocument} getMarqueeTransform={this.getMarqueeTransform} getTransform={this.getTransform} /> {this.overlayView} + </div> ); } diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx index 81d7f4077..02ee49a38 100644 --- a/src/client/views/collections/CollectionViewBase.tsx +++ b/src/client/views/collections/CollectionViewBase.tsx @@ -13,8 +13,8 @@ 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"; +import { UserUtils } from "../../../server/authentication/models/user_utils"; export interface CollectionViewProps { fieldKey: Key; @@ -34,10 +34,9 @@ export interface SubCollectionViewProps extends CollectionViewProps { addDocument: (doc: Document) => void; removeDocument: (doc: Document) => boolean; CollectionView: CollectionView; - currentUser?: DashUserModel; } -export type CursorEntry = TupleField<DashUserModel, [number, number]>; +export type CursorEntry = TupleField<string, [number, number]>; export class CollectionViewBase extends React.Component<SubCollectionViewProps> { private dropDisposer?: DragManager.DragDropDisposer; @@ -50,31 +49,34 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps> } } + @action 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); - // } + let ind; + let doc = this.props.Document; + let id = UserUtils.currentUserId; + if (id) { + doc.GetOrCreateAsync<ListField<CursorEntry>>(KeyStore.Cursors, ListField, field => { + let cursors = field.Data; + if (cursors.length > 0 && (ind = cursors.findIndex(entry => entry.Data[0] === id)) > -1) { + cursors[ind].Data[1] = position; + } else { + let entry = new TupleField<string, [number, number]>([id, position]); + 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 []; + let doc = this.props.Document; + let id = UserUtils.currentUserId; + let cursors = doc.GetList<CursorEntry>(KeyStore.Cursors, []); + let notMe = cursors.filter(entry => entry.Data[0] !== id); + return id ? notMe : []; } - @undoBatch @action protected drop(e: Event, de: DragManager.DropEvent) { diff --git a/src/fields/TupleField.ts b/src/fields/TupleField.ts index 778bd5d2f..e2162c751 100644 --- a/src/fields/TupleField.ts +++ b/src/fields/TupleField.ts @@ -51,7 +51,7 @@ export class TupleField<T, U> extends BasicField<[T, U]> { ToJson(): { type: Types, data: [T, U], _id: string } { return { - type: Types.List, + type: Types.Tuple, data: this.Data, _id: this.Id } diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts index dce4bada5..f10f82deb 100644 --- a/src/server/ServerUtil.ts +++ b/src/server/ServerUtil.ts @@ -22,6 +22,8 @@ import { TupleField } from '../fields/TupleField'; export class ServerUtils { + public static prepend(extension: string): string { return window.location.origin + extension; } + public static FromJson(json: any): Field { let obj = json let data: any = obj.data diff --git a/src/server/authentication/models/user_utils.ts b/src/server/authentication/models/user_utils.ts new file mode 100644 index 000000000..1497a4ba4 --- /dev/null +++ b/src/server/authentication/models/user_utils.ts @@ -0,0 +1,22 @@ +import { DashUserModel } from "./user_model"; +import * as request from 'request' +import { RouteStore } from "../../RouteStore"; +import { ServerUtils } from "../../ServerUtil"; + +export class UserUtils { + private static current: string; + + public static get currentUserId() { + return UserUtils.current; + } + + public static loadCurrentUserId() { + request.get(ServerUtils.prepend(RouteStore.getCurrUser), (error, response, body) => { + if (body) { + UserUtils.current = JSON.parse(body) as string; + } else { + throw new Error("There should be a user! Why does Dash think there isn't one?") + } + }); + } +}
\ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index 17aba99ee..0512ebf72 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -6,19 +6,14 @@ 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 { MessageStore, Transferable } from "./Message"; import { Client } from './Client'; import { Socket } from 'socket.io'; import { Utils } from '../Utils'; import { ObservableMap } from 'mobx'; import { FieldId, Field } from '../fields/Field'; import { Database } from './database'; -import { ServerUtils } from './ServerUtil'; -import { ObjectID } from 'mongodb'; -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, postReset, getForgot, postForgot, getReset } from './authentication/controllers/user_controller'; const config = require('../../webpack.config'); const compiler = webpack(config); @@ -29,17 +24,14 @@ import expressFlash = require('express-flash'); import flash = require('connect-flash'); import * as bodyParser from 'body-parser'; import * as session from 'express-session'; -// import cookieSession = require('cookie-session'); import * as cookieParser from 'cookie-parser'; import c = require("crypto"); const MongoStore = require('connect-mongo')(session); const mongoose = require('mongoose'); -import { performance } from 'perf_hooks' -import User, { DashUserModel } from './authentication/models/user_model'; +import { DashUserModel } from './authentication/models/user_model'; import * as fs from 'fs'; import * as request from 'request' import { RouteStore } from './RouteStore'; -import * as MobileDetect from 'mobile-detect'; const download = (url: string, dest: fs.PathLike) => { request.get(url).pipe(fs.createWriteStream(dest)); @@ -56,7 +48,7 @@ mongoose.connection.on('connected', function () { app.use(cookieParser()); app.use(session({ - secret: `our secret`, + secret: "64d6866242d3b5a5503c675b32c9605e4e90478e9b77bcf2bc", resave: true, cookie: { maxAge: 7 * 24 * 60 * 60 }, saveUninitialized: true, @@ -91,30 +83,30 @@ enum Method { * 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, 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 + * @param subscribers the forward slash prepended path names (reference and add to RouteStore.ts) that will all invoke the given @param handler */ function addSecureRoute(method: Method, - route: string, 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, res, req); - }); - break; - case Method.POST: - app.post(route, (req, res) => { - const dashUser: DashUserModel = req.user; - if (!dashUser) return onRejection(res); - handler(dashUser, res, req); - }); - break; + onRejection: (res: express.Response) => any = (res) => res.redirect(RouteStore.logout), + ...subscribers: string[] +) { + let abstracted = (req: express.Request, res: express.Response) => { + const dashUser: DashUserModel = req.user; + if (!dashUser) return onRejection(res); + handler(dashUser, res, req); } + subscribers.forEach(route => { + switch (method) { + case Method.GET: + app.get(route, abstracted); + break; + case Method.POST: + app.post(route, abstracted); + break; + } + }); } // STATIC FILE SERVING @@ -128,60 +120,87 @@ app.use(RouteStore.images, express.static(__dirname + RouteStore.public)) // anyone attempting to navigate to localhost at this port will // first have to login -addSecureRoute(Method.GET, RouteStore.root, (user, res) => { - res.redirect(RouteStore.home); -}); - -addSecureRoute(Method.GET, RouteStore.home, (user, res) => { - res.sendFile(path.join(__dirname, '../../deploy/index.html')); -}); - -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, res) => { - res.send(JSON.stringify(user.allWorkspaceIds)); -}); - -addSecureRoute(Method.GET, RouteStore.getCurrUser, (user, res) => { - res.send(JSON.stringify(user)); -}); +addSecureRoute( + Method.GET, + (user, res) => res.redirect(RouteStore.home), + undefined, + RouteStore.root +); + +addSecureRoute( + Method.GET, + (user, res) => res.sendFile(path.join(__dirname, '../../deploy/index.html')), + undefined, + RouteStore.home, + RouteStore.openDocumentWithId +); + +addSecureRoute( + Method.GET, + (user, res) => res.send(user.activeWorkspaceId || ""), + undefined, + RouteStore.getActiveWorkspace, +); + +addSecureRoute( + Method.GET, + (user, res) => res.send(JSON.stringify(user.allWorkspaceIds)), + undefined, + RouteStore.getAllWorkspaces +); + +addSecureRoute( + Method.GET, + (user, res) => res.send(JSON.stringify(user.id)), + undefined, + RouteStore.getCurrUser +); // 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, res, req) => { - user.update({ $push: { allWorkspaceIds: req.body.target } }, (err, raw) => { - res.sendStatus(err ? 500 : 200); - }); -}); - -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); - }); -}); +addSecureRoute( + Method.POST, + (user, res, req) => { + user.update({ $set: { activeWorkspaceId: req.body.target } }, (err, raw) => { + res.sendStatus(err ? 500 : 200); + }); + }, + undefined, + RouteStore.setActiveWorkspace +); + +addSecureRoute( + Method.POST, + (user, res, req) => { + user.update({ $push: { allWorkspaceIds: req.body.target } }, (err, raw) => { + res.sendStatus(err ? 500 : 200); + }); + }, + undefined, + RouteStore.addWorkspace +); + +addSecureRoute( + Method.POST, + (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); + }); + }, + undefined, + RouteStore.upload +); // AUTHENTICATION |