From 0e46a734bc7159282cb7dfc78447ab3e1c00a4df Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 24 Feb 2019 19:17:14 -0500 Subject: updated profile --- src/server/authentication/models/User.ts | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/server/authentication/models') diff --git a/src/server/authentication/models/User.ts b/src/server/authentication/models/User.ts index 9752c4260..ed2952e48 100644 --- a/src/server/authentication/models/User.ts +++ b/src/server/authentication/models/User.ts @@ -47,6 +47,8 @@ const userSchema = new mongoose.Schema({ passwordResetToken: String, passwordResetExpires: Date, + workspaces: Array, + facebook: String, twitter: String, google: String, -- cgit v1.2.3-70-g09d2 From a160304a54ee0219f48faee8a7402503f2160902 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 24 Feb 2019 23:45:43 -0500 Subject: lost password email implementation beginning --- package-lock.json | 36 ++++++++++++++++++++++--- package.json | 4 +++ src/server/authentication/controllers/user.ts | 8 +++++- src/server/authentication/models/User.ts | 4 +-- src/server/index.ts | 38 +++++++++++++++++++++++++++ views/layout.pug | 4 +-- views/signup.pug | 1 - 7 files changed, 83 insertions(+), 12 deletions(-) (limited to 'src/server/authentication/models') diff --git a/package-lock.json b/package-lock.json index 94e478e1b..357728555 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,11 @@ "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.0.tgz", "integrity": "sha512-7WcbyctkE8GTzogDb0ulRAEw7v8oIS54ft9mQTU7PfM0hp5e+8kpa+HeQ7IQrFbKtJXBKcZ4bh+Em9dTw5L6AQ==" }, + "@types/async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/async/-/async-2.4.1.tgz", + "integrity": "sha512-C09BK/wXzbW+/JK9zckhe+FeSbg7NmvVjUWwApnw7ksRpUq3ecGLiq2Aw1LlY4Z/VmtdhSaIs7jO5/MWRYMcOA==" + }, "@types/babel-types": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.5.tgz", @@ -207,6 +212,14 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.24.tgz", "integrity": "sha512-GWWbvt+z9G5otRBW8rssOFgRY87J9N/qbhqfjMZ+gUuL6zoL+Hm6gP/8qQBG4jjimqdaNLCehcVapZ/Fs2WjCQ==" }, + "@types/nodemailer": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-4.6.6.tgz", + "integrity": "sha512-N2czhXs7fbQhvoquEGzmHAWttnxLfrM3+cWMRFX4hTQq4GE3VyaSE3MOOse4VoNgvtti/H5ow/Hq9KXu/UMWqA==", + "requires": { + "@types/node": "*" + } + }, "@types/orderedmap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/orderedmap/-/orderedmap-1.0.0.tgz", @@ -950,10 +963,12 @@ "integrity": "sha1-ECyenpAF0+fjgpvwxPok7oYu6bk=" }, "async": { - "version": "1.5.2", - "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "requires": { + "lodash": "^4.17.11" + } }, "async-each": { "version": "1.0.1", @@ -5792,6 +5807,11 @@ "true-case-path": "^1.0.2" } }, + "nodemailer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-5.1.1.tgz", + "integrity": "sha512-hKGCoeNdFL2W7S76J/Oucbw0/qRlfG815tENdhzcqTpSjKgAN91mFOqU2lQUflRRxFM7iZvCyaFcAR9noc/CqQ==" + }, "nodemon": { "version": "1.18.10", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.18.10.tgz", @@ -9807,6 +9827,14 @@ "async": "^1.5.2", "debug": "^2.2.0", "mkdirp": "0.5.x" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + } } }, "posix-character-classes": { diff --git a/package.json b/package.json index fbf20230d..4371df90d 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ }, "dependencies": { "@fortawesome/fontawesome-svg-core": "^1.2.14", + "@types/async": "^2.4.1", "@types/bcrypt-nodejs": "0.0.30", "@types/bluebird": "^3.5.25", "@types/body-parser": "^1.17.0", @@ -48,6 +49,7 @@ "@types/mongodb": "^3.1.19", "@types/mongoose": "^5.3.16", "@types/node": "^10.12.24", + "@types/nodemailer": "^4.6.6", "@types/passport": "^1.0.0", "@types/passport-local": "^1.0.33", "@types/prosemirror-commands": "^1.0.1", @@ -65,6 +67,7 @@ "@types/typescript": "^2.0.0", "@types/uuid": "^3.4.4", "@types/webpack": "^4.4.24", + "async": "^2.6.2", "bcrypt-nodejs": "0.0.3", "bluebird": "^3.5.3", "body-parser": "^1.18.3", @@ -87,6 +90,7 @@ "mongodb": "^3.1.13", "mongoose": "^5.4.12", "node-sass": "^4.11.0", + "nodemailer": "^5.1.1", "nodemon": "^1.18.10", "normalize.css": "^8.0.1", "npm": "^6.7.0", diff --git a/src/server/authentication/controllers/user.ts b/src/server/authentication/controllers/user.ts index 93986adf8..a496959d1 100644 --- a/src/server/authentication/controllers/user.ts +++ b/src/server/authentication/controllers/user.ts @@ -12,6 +12,8 @@ import * as pug from 'pug'; * 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"); @@ -29,6 +31,7 @@ export let getSignup = (req: Request, res: Response) => { } res.render("signup.pug", { title: "Sign Up", + user: req.user, errors: req.flash("Unable to facilitate sign up. Please try again.") }); }; @@ -61,7 +64,9 @@ export let postSignup = (req: Request, res: Response, next: NextFunction) => { const user = new User({ email, password, + userDoc: "document here" }); + User.findOne({ email }, (err, existingUser) => { if (err) { return next(err); } if (existingUser) { @@ -94,7 +99,8 @@ export let getLogin = (req: Request, res: Response) => { return res.redirect("/home"); } res.render("login.pug", { - title: "Log In" + title: "Log In", + user: req.user }); }; diff --git a/src/server/authentication/models/User.ts b/src/server/authentication/models/User.ts index ed2952e48..9e6c525c3 100644 --- a/src/server/authentication/models/User.ts +++ b/src/server/authentication/models/User.ts @@ -1,6 +1,5 @@ //@ts-ignore import * as bcrypt from "bcrypt-nodejs"; -import * as crypto from "crypto"; //@ts-ignore import * as mongoose from "mongoose"; var url = 'mongodb://localhost:27017/Dash' @@ -46,8 +45,7 @@ const userSchema = new mongoose.Schema({ password: String, passwordResetToken: String, passwordResetExpires: Date, - - workspaces: Array, + userDoc: String, facebook: String, twitter: String, diff --git a/src/server/index.ts b/src/server/index.ts index 7189b32a0..039d7f56a 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -13,6 +13,7 @@ import { FIELD_ID, 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'; @@ -27,12 +28,15 @@ import flash = require('express-flash'); import * as bodyParser from 'body-parser'; import * as session from 'express-session'; import * as cookieParser from 'cookie-parser'; +import * as nodemailer from 'nodemailer'; import c = require("crypto"); const MongoStore = require('connect-mongo')(session); const mongoose = require('mongoose'); +import * as async from 'async'; const bluebird = require('bluebird'); import { performance } from 'perf_hooks' import * as path from 'path' +import User from './authentication/models/User'; const mongoUrl = 'mongodb://localhost:27017/Dash'; // mongoose.Promise = bluebird; @@ -106,6 +110,40 @@ app.get('/logout', getLogout); // *** +// FORGOT PASSWORD EMAIL HANDLING +app.post('/forgot', function (req, res, next) { + const email = req.body.email; + async.waterfall([ + function (done: any) { + const seed = new Uint16Array(); + seed.set([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + let token = crypto.getRandomValues(seed); + done(token); + }, + function (token: Uint16Array, done: any) { + User.findOne({ email }, function (err, user: User) { + if (!user) { + // NO ACCOUNT WITH SUBMITTED EMAIL + return res.redirect('/forgot'); + } + user.resetPasswordToken = token; + user.resetPasswordExpires = Date.now() + 3600000; // 1 HOUR + user.save(function (err: any) { + done(err, token, user); + }); + }); + }, + function (token: Uint16Array, user: User, done: any) { + const transport = nodemailer.createTransport('SMTP', { + auth: { + user: 'test.nodemailer@gmail.com', + pass: 'placeholder' + } + }); + } + ]) +}) + let FieldStore: ObservableMap = new ObservableMap(); app.get("/hello", (req, res) => { diff --git a/views/layout.pug b/views/layout.pug index fb22ae770..95a5a391f 100644 --- a/views/layout.pug +++ b/views/layout.pug @@ -12,6 +12,4 @@ html(lang='') link(rel='stylesheet', href='/css/main.css') body - - .container - block content \ No newline at end of file + block content \ No newline at end of file diff --git a/views/signup.pug b/views/signup.pug index 9863b453e..374710e6f 100644 --- a/views/signup.pug +++ b/views/signup.pug @@ -4,7 +4,6 @@ extends ./layout block content style include ./stylesheets/authentication.css - .page-header form.form-horizontal(id='signup-form', method='POST') input(type='hidden', name='_csrf', value=_csrf) .overlay(id='overlay_signup') -- cgit v1.2.3-70-g09d2 From 3113bd7dfee404eea113633f97027a2370f8d817 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Mon, 25 Feb 2019 00:48:43 -0500 Subject: Got user workspaces working --- package-lock.json | 154 +++++++++++++------------- package.json | 2 + src/client/views/Main.tsx | 178 +++++++++++++++++-------------- src/server/authentication/models/User.ts | 2 +- src/server/index.ts | 15 +++ 5 files changed, 197 insertions(+), 154 deletions(-) (limited to 'src/server/authentication/models') diff --git a/package-lock.json b/package-lock.json index 94e478e1b..7081834f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -62,6 +62,11 @@ "@types/node": "*" } }, + "@types/caseless": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.1.tgz", + "integrity": "sha512-FhlMa34NHp9K5MY1Uz8yb+ZvuX0pnvn3jScRSNAb75KHGB8d3rEU6hqMs3Z2vjuytcMfRg6c5CHMc3wtYyD2/A==" + }, "@types/chai": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.7.tgz", @@ -137,6 +142,14 @@ "express-validator": "*" } }, + "@types/form-data": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz", + "integrity": "sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ==", + "requires": { + "@types/node": "*" + } + }, "@types/jquery": { "version": "3.3.29", "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.29.tgz", @@ -352,6 +365,17 @@ "@types/react": "*" } }, + "@types/request": { + "version": "2.48.1", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.1.tgz", + "integrity": "sha512-ZgEZ1TiD+KGA9LiAAPPJL68Id2UWfeSO62ijSXZjFJArVV+2pKcsVHmrcu+1oiE3q6eDGiFiSolRc4JHoerBBg==", + "requires": { + "@types/caseless": "*", + "@types/form-data": "*", + "@types/node": "*", + "@types/tough-cookie": "*" + } + }, "@types/serve-static": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.2.tgz", @@ -384,6 +408,11 @@ "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.4.tgz", "integrity": "sha512-78AdXtlhpCHT0K3EytMpn4JNxaf5tbqbLcbIRoQIHzpTIyjpxLQKRoxU55ujBXAtg3Nl2h/XWvfDa9dsMOd0pQ==" }, + "@types/tough-cookie": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.5.tgz", + "integrity": "sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==" + }, "@types/typescript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/typescript/-/typescript-2.0.0.tgz", @@ -919,7 +948,7 @@ }, "util": { "version": "0.10.3", - "resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", "dev": true, "requires": { @@ -951,7 +980,7 @@ }, "async": { "version": "1.5.2", - "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", "dev": true }, @@ -1359,7 +1388,7 @@ }, "browserify-aes": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "requires": { @@ -1396,7 +1425,7 @@ }, "browserify-rsa": { "version": "4.0.1", - "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "dev": true, "requires": { @@ -1435,7 +1464,7 @@ }, "buffer": { "version": "4.9.1", - "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "dev": true, "requires": { @@ -1545,7 +1574,7 @@ }, "camelcase-keys": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "requires": { "camelcase": "^2.0.0", @@ -1980,7 +2009,7 @@ }, "content-disposition": { "version": "0.5.2", - "resolved": "http://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" }, "content-type": { @@ -2077,7 +2106,7 @@ }, "create-hash": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, "requires": { @@ -2090,7 +2119,7 @@ }, "create-hmac": { "version": "1.1.7", - "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, "requires": { @@ -2191,7 +2220,7 @@ }, "d": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/d/-/d-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", "dev": true, "requires": { @@ -2449,7 +2478,7 @@ }, "diffie-hellman": { "version": "5.0.3", - "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "dev": true, "requires": { @@ -2933,7 +2962,7 @@ "dependencies": { "array-flatten": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" } } @@ -3137,7 +3166,7 @@ }, "finalhandler": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", "requires": { "debug": "2.6.9", @@ -3331,8 +3360,7 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "optional": true + "bundled": true }, "aproba": { "version": "1.2.0", @@ -3350,13 +3378,11 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, - "optional": true + "bundled": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3369,18 +3395,15 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", - "bundled": true, - "optional": true + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -3483,8 +3506,7 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -3494,7 +3516,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3507,20 +3528,17 @@ "minimatch": { "version": "3.0.4", "bundled": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true, - "optional": true + "bundled": true }, "minipass": { "version": "2.3.5", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3537,7 +3555,6 @@ "mkdirp": { "version": "0.5.1", "bundled": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -3610,8 +3627,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -3621,7 +3637,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -3697,8 +3712,7 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, - "optional": true + "bundled": true }, "safer-buffer": { "version": "2.1.2", @@ -3728,7 +3742,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3746,7 +3759,6 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3785,13 +3797,11 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true }, "yallist": { "version": "3.0.3", - "bundled": true, - "optional": true + "bundled": true } } }, @@ -4216,7 +4226,7 @@ }, "http-errors": { "version": "1.6.3", - "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "requires": { "depd": "~1.1.2", @@ -4252,7 +4262,7 @@ }, "http-proxy-middleware": { "version": "0.18.0", - "resolved": "http://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", "integrity": "sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==", "dev": true, "requires": { @@ -4486,7 +4496,7 @@ }, "is-accessor-descriptor": { "version": "0.1.6", - "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "requires": { "kind-of": "^3.0.2" @@ -4522,7 +4532,7 @@ }, "is-builtin-module": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "requires": { "builtin-modules": "^1.0.0" @@ -4538,7 +4548,7 @@ }, "is-data-descriptor": { "version": "0.1.4", - "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "requires": { "kind-of": "^3.0.2" @@ -4785,7 +4795,7 @@ }, "jsesc": { "version": "0.5.0", - "resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", "dev": true }, @@ -4976,7 +4986,7 @@ }, "load-json-file": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "requires": { "graceful-fs": "^4.1.2", @@ -5240,7 +5250,7 @@ }, "media-typer": { "version": "0.3.0", - "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "mem": { @@ -5272,7 +5282,7 @@ }, "meow": { "version": "3.7.0", - "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "requires": { "camelcase-keys": "^2.0.0", @@ -5433,7 +5443,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" @@ -5685,7 +5695,7 @@ }, "next-tick": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", "dev": true }, @@ -5722,7 +5732,7 @@ "dependencies": { "semver": { "version": "5.3.0", - "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" } } @@ -9501,7 +9511,7 @@ }, "os-homedir": { "version": "1.0.2", - "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, "os-locale": { @@ -9514,7 +9524,7 @@ }, "os-tmpdir": { "version": "1.0.2", - "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "osenv": { @@ -9679,7 +9689,7 @@ }, "path-browserify": { "version": "0.0.0", - "resolved": "http://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", "dev": true }, @@ -9698,7 +9708,7 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-is-inside": { @@ -10427,7 +10437,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -10508,7 +10518,7 @@ }, "regexpu-core": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", "dev": true, "requires": { @@ -10536,13 +10546,13 @@ }, "regjsgen": { "version": "0.2.0", - "resolved": "http://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", "dev": true }, "regjsparser": { "version": "0.1.5", - "resolved": "http://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", "dev": true, "requires": { @@ -10721,7 +10731,7 @@ }, "safe-regex": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "requires": { "ret": "~0.1.10" @@ -10934,7 +10944,7 @@ }, "sha.js": { "version": "2.4.11", - "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { @@ -11505,7 +11515,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -11513,7 +11523,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { "ansi-regex": "^2.0.0" @@ -11529,7 +11539,7 @@ }, "strip-eof": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, "strip-indent": { @@ -11568,7 +11578,7 @@ }, "tar": { "version": "2.2.1", - "resolved": "http://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", "requires": { "block-stream": "*", @@ -11987,7 +11997,7 @@ }, "tty-browserify": { "version": "0.0.0", - "resolved": "http://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", "dev": true }, @@ -12379,7 +12389,7 @@ }, "vm-browserify": { "version": "0.0.4", - "resolved": "http://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", "dev": true, "requires": { @@ -13177,7 +13187,7 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "requires": { "string-width": "^1.0.1", diff --git a/package.json b/package.json index fbf20230d..3bfb23dd5 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "@types/prosemirror-view": "^1.3.0", "@types/pug": "^2.0.4", "@types/react-table": "^6.7.21", + "@types/request": "^2.48.1", "@types/socket.io": "^2.1.2", "@types/socket.io-client": "^1.4.32", "@types/typescript": "^2.0.0", @@ -109,6 +110,7 @@ "react-mosaic": "0.0.20", "react-split-pane": "^0.1.85", "react-table": "^6.9.0", + "request": "^2.88.0", "socket.io": "^2.2.0", "socket.io-client": "^2.2.0", "url-loader": "^1.1.2", diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 2a3e2780b..97f68f4f5 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -19,6 +19,7 @@ import { Utils } from '../../Utils'; import { ServerUtils } from '../../server/ServerUtil'; import { MessageStore, DocumentTransfer } from '../../server/Message'; import { Database } from '../../server/database'; +import * as request from 'request' configure({ @@ -42,6 +43,21 @@ document.addEventListener("pointerdown", action(function (e: PointerEvent) { } }), true) +let mainDocId: string; +request.get(window.location.origin + "/getUserDocId", (error, response, body) => { + if (body) { + mainDocId = body; + } else { + mainDocId = Utils.GenerateGuid(); + request.post(window.location.origin + "/setUserDocId", { + body: { + userDocumentId: mainDocId + }, + json: true + }) + } + init(); +}) //runInAction(() => // let doc1 = Documents.TextDocument({ title: "hello" }); @@ -59,91 +75,91 @@ document.addEventListener("pointerdown", action(function (e: PointerEvent) { // schemaDocs[4].SetData(KS.Author, "Bob", TextField); // schemaDocs.push(doc2); // const doc7 = Documents.SchemaDocument(schemaDocs) +function init() { + Documents.initProtos(() => { + Utils.EmitCallback(Server.Socket, MessageStore.GetField, mainDocId, (res: any) => { + console.log("HELLO WORLD") + console.log("RESPONSE: " + res) + let mainContainer: Document; + if (res) { + var lid = KeyStore.Layout.Id; + let obj = ServerUtils.FromJson(res) as Document + mainContainer = obj + } + else { + const docset: Document[] = []; + mainContainer = Documents.CollectionDocument(docset, { x: 0, y: 400, title: "mini collection" }, mainDocId); + let args = new DocumentTransfer(mainContainer.ToJson()) + Utils.Emit(Server.Socket, MessageStore.AddDocument, args) + } -const mainDocId = "mainDoc"; -Documents.initProtos(() => { - Utils.EmitCallback(Server.Socket, MessageStore.GetField, mainDocId, (res: any) => { - console.log("HELLO WORLD") - console.log("RESPONSE: " + res) - let mainContainer: Document; - if (res) { - var lid = KeyStore.Layout.Id; - let obj = ServerUtils.FromJson(res) as Document - mainContainer = obj - } - else { - const docset: Document[] = []; - mainContainer = Documents.CollectionDocument(docset, { x: 0, y: 400, title: "mini collection" }, mainDocId); - let args = new DocumentTransfer(mainContainer.ToJson()) - Utils.Emit(Server.Socket, MessageStore.AddDocument, args) - } + let addImageNode = action(() => { + mainContainer.GetList(KeyStore.Data, []).push(Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { + x: 0, y: 300, width: 200, height: 200, title: "added note" + })); + }) + let addTextNode = action(() => { + mainContainer.GetList(KeyStore.Data, []).push(Documents.TextDocument({ + x: 0, y: 300, width: 200, height: 200, title: "added note" + })); + }) + let addColNode = action(() => { + mainContainer.GetList(KeyStore.Data, []).push(Documents.CollectionDocument([], { + x: 0, y: 300, width: 200, height: 200, title: "added note" + })); + }) - let addImageNode = action(() => { - mainContainer.GetList(KeyStore.Data, []).push(Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { - x: 0, y: 300, width: 200, height: 200, title: "added note" - })); - }) - let addTextNode = action(() => { - mainContainer.GetList(KeyStore.Data, []).push(Documents.TextDocument({ - x: 0, y: 300, width: 200, height: 200, title: "added note" - })); - }) - let addColNode = action(() => { - mainContainer.GetList(KeyStore.Data, []).push(Documents.CollectionDocument([], { - x: 0, y: 300, width: 200, height: 200, title: "added note" - })); - }) + let clearDatabase = action(() => { + Utils.Emit(Server.Socket, MessageStore.DeleteAll, {}); + }) - let clearDatabase = action(() => { - Utils.Emit(Server.Socket, MessageStore.DeleteAll, {}); + ReactDOM.render(( +
+ + + + + + + + + + +
), + document.getElementById('root')); }) - - ReactDOM.render(( -
- - - - - - - - - - -
), - document.getElementById('root')); - }) -}); + }); +} // let doc5 = Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { // x: 650, y: 500, width: 600, height: 600, title: "cat 2" // }); diff --git a/src/server/authentication/models/User.ts b/src/server/authentication/models/User.ts index ed2952e48..bc838bb47 100644 --- a/src/server/authentication/models/User.ts +++ b/src/server/authentication/models/User.ts @@ -47,7 +47,7 @@ const userSchema = new mongoose.Schema({ passwordResetToken: String, passwordResetExpires: Date, - workspaces: Array, + userDocumentId: String, facebook: String, twitter: String, diff --git a/src/server/index.ts b/src/server/index.ts index 7189b32a0..87b6b0005 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -89,6 +89,21 @@ app.get("/home", (req, res) => { res.sendFile(path.join(__dirname, '../../deploy/index.html')); }); +app.get("/getUserDocId", (req, res) => { + console.log(req.user) + if (!req.user) { + return; + } + res.send(req.user.userDocumentId || ""); +}) + +app.post("/setUserDocId", (req, res) => { + if (!req.user) { + return; + } + req.user.update({ $set: { userDocumentId: req.body.userDocumentId } }, () => { }); +}) + // anyone attempting to navigate to localhost at this port will // first have to login app.get("/", getEntry); -- cgit v1.2.3-70-g09d2 From 0d36924c90682c57e96c1e7bfd95a3cc10e6c662 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Mon, 25 Feb 2019 03:38:02 -0500 Subject: after pop, hard reset --- src/client/views/Main.tsx | 6 ++ src/server/authentication/controllers/user.ts | 135 +++++++++++++++++++++++++- src/server/authentication/models/User.ts | 4 +- src/server/index.ts | 68 ++----------- views/reset.pug | 22 +++++ views/signup.pug | 2 +- views/stylesheets/authentication.css | 6 +- 7 files changed, 176 insertions(+), 67 deletions(-) create mode 100644 views/reset.pug (limited to 'src/server/authentication/models') diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index cbc19d7fe..567f17752 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -171,6 +171,12 @@ function init() { right: '0px', width: '150px' }} onClick={() => UndoManager.Redo()}>Redo + ), document.getElementById('root')); }) diff --git a/src/server/authentication/controllers/user.ts b/src/server/authentication/controllers/user.ts index a496959d1..19cd09676 100644 --- a/src/server/authentication/controllers/user.ts +++ b/src/server/authentication/controllers/user.ts @@ -7,6 +7,10 @@ import * as request from "express-validator"; const flash = require("express-flash"); import * as session from "express-session"; import * as pug from 'pug'; +import * as async from 'async'; +import * as nodemailer from 'nodemailer'; +import c = require("crypto"); + /** * GET / @@ -46,8 +50,6 @@ export let postSignup = (req: Request, res: Response, next: NextFunction) => { req.assert("confirmPassword", "Passwords do not match").equals(req.body.password); req.sanitize("email").normalizeEmail({ gmail_remove_dots: false }); - req.flash("Working on something!!!"); - const errors = req.validationErrors(); if (errors) { @@ -146,4 +148,133 @@ export let getLogout = (req: Request, res: Response) => { sess.destroy((err) => { if (err) { console.log(err); } }); } res.redirect('/login'); +} + +export let getForgot = function (req: Request, res: Response) { + res.render("forgot.pug", { + title: "Recover Password", + user: req.user, + }); +} + +export let postForgot = function (req: Request, res: Response, next: NextFunction) { + const email = req.body.email; + async.waterfall([ + function (done: any) { + let token: string; + c.randomBytes(20, function (err: any, buffer: Buffer) { + if (err) { + done(null); + return; + } + done(null, buffer.toString('hex')); + }) + }, + function (token: Buffer, done: any) { + User.findOne({ email }, function (err, user: UserModel) { + if (!user) { + // NO ACCOUNT WITH SUBMITTED EMAIL + return res.redirect('/forgot'); + } + user.passwordResetToken = token.toString('utf8'); + console.log(user.passwordResetToken); + user.passwordResetExpires = new Date(Date.now() + 3600000); // 1 HOUR + user.save(function (err: any) { + done(null, token, user); + }); + }); + }, + function (token: Uint16Array, user: UserModel, done: any) { + const smtpTransport = nodemailer.createTransport({ + service: 'Gmail', + auth: { + user: 'brownptcdash@gmail.com', + pass: 'browngfx1' + } + }); + const mailOptions = { + to: user.email, + from: 'brownptcdash@gmail.com', + subject: 'Dash Password Reset', + text: 'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n' + + 'Please click on the following link, or paste this into your browser to complete the process:\n\n' + + 'http://' + req.headers.host + '/reset/' + token + '\n\n' + + 'If you did not request this, please ignore this email and your password will remain unchanged.\n' + }; + smtpTransport.sendMail(mailOptions, function (err) { + // req.flash('info', 'An e-mail has been sent to ' + user.email + ' with further instructions.'); + done(null, err, 'done'); + }); + } + ], function (err) { + if (err) return next(err); + res.redirect('/forgot'); + }) +} + +export let getReset = function (req: Request, res: Response) { + User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } }, function (err, user: UserModel) { + if (!user || err) { + return res.redirect('/forgot'); + } + res.render("reset.pug", { + title: "Reset Password", + user: req.user, + }); + }); +} + +export let postReset = function (req: Request, res: Response) { + async.waterfall([ + function (done: any) { + User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } }, function (err, user: UserModel) { + if (!user || err) { + return res.redirect('back'); + } + + req.assert("password", "Password must be at least 4 characters long").len({ min: 4 }); + req.assert("confirmPassword", "Passwords do not match").equals(req.body.password); + + if (req.validationErrors()) { + return res.redirect('back'); + } + + user.password = req.body.password; + user.passwordResetToken = undefined; + user.passwordResetExpires = undefined; + + user.save(function (err) { + req.logIn(user, function (err) { + if (err) { + console.log(err); + return; + } + }); + done(user, err); + }); + }); + }, + function (user: UserModel, done: any) { + console.log(`SENDING EMAIL TO ${user.email}`); + const smtpTransport = nodemailer.createTransport({ + service: 'Gmail', + auth: { + user: 'brownptcdash@gmail.com', + pass: 'browngfx1' + } + }); + const mailOptions = { + to: user.email, + from: 'brownptcdash@gmail.com', + subject: 'Your password has been changed', + text: 'Hello,\n\n' + + 'This is a confirmation that the password for your account ' + user.email + ' has just been changed.\n' + }; + smtpTransport.sendMail(mailOptions, function (err) { + done(null, err); + }); + } + ], function (err) { + res.redirect('/login'); + }); } \ No newline at end of file diff --git a/src/server/authentication/models/User.ts b/src/server/authentication/models/User.ts index 30fcecd81..433e2f6c3 100644 --- a/src/server/authentication/models/User.ts +++ b/src/server/authentication/models/User.ts @@ -18,8 +18,8 @@ mongoose.connection.on('disconnected', function () { export type UserModel = mongoose.Document & { email: string, password: string, - passwordResetToken: string, - passwordResetExpires: Date, + passwordResetToken: string | undefined, + passwordResetExpires: Date | undefined, tokens: AuthToken[], profile: { diff --git a/src/server/index.ts b/src/server/index.ts index baf360ffa..f2b26afec 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -17,7 +17,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 } from './authentication/controllers/user'; +import { getLogin, postLogin, getSignup, postSignup, getLogout, getEntry, postReset, getForgot, postForgot, getReset } from './authentication/controllers/user'; const config = require('../../webpack.config'); const compiler = webpack(config); const port = 1050; // default port to listen @@ -28,11 +28,9 @@ import flash = require('express-flash'); import * as bodyParser from 'body-parser'; import * as session from 'express-session'; import * as cookieParser from 'cookie-parser'; -import * as nodemailer from 'nodemailer'; import c = require("crypto"); const MongoStore = require('connect-mongo')(session); const mongoose = require('mongoose'); -import * as async from 'async'; const bluebird = require('bluebird'); import { performance } from 'perf_hooks' import * as path from 'path' @@ -88,7 +86,6 @@ app.get("/home", (req, res) => { }); app.get("/getUserDocId", (req, res) => { - console.log(req.user) if (!req.user) { return; } @@ -119,64 +116,15 @@ app.get('/logout', getLogout); // *** -app.get('/forgot', function (req, res) { - res.render("forgot.pug", { - title: "Recover Password", - user: req.user, - }); -}) - // FORGOT PASSWORD EMAIL HANDLING -app.post('/forgot', function (req, res, next) { - const email = req.body.email; - async.waterfall([ - function (done: any) { - const seed = new Uint32Array(20); - let token = seed; - done(null, token); - }, - function (token: Uint32Array, done: any) { - User.findOne({ email }, function (err, user: UserModel) { - if (!user) { - // NO ACCOUNT WITH SUBMITTED EMAIL - return res.redirect('/forgot'); - } - user.passwordResetToken = token.toString(); - user.passwordResetExpires = new Date(Date.now() + 3600000); // 1 HOUR - user.save(function (err: any) { - done(null, token, user); - }); - }); - }, - function (token: Uint16Array, user: UserModel, done: any) { - const smptTransport = nodemailer.createTransport({ - service: 'Gmail', - auth: { - user: 'brownptcdash@gmail.com', - pass: 'browngfx1' - } - }); - const mailOptions = { - to: user.email, - from: 'brownptcdash@gmail.com', - subject: 'Dash Password Reset', - text: 'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n' + - 'Please click on the following link, or paste this into your browser to complete the process:\n\n' + - 'http://' + req.headers.host + '/reset/' + token + '\n\n' + - 'If you did not request this, please ignore this email and your password will remain unchanged.\n' - }; - smptTransport.sendMail(mailOptions, function (err) { - // req.flash('info', 'An e-mail has been sent to ' + user.email + ' with further instructions.'); - done(null, err, 'done'); - }); - } - ], function (err) { - if (err) return next(err); - res.redirect('/forgot'); - }) -}) -let FieldStore: ObservableMap = new ObservableMap(); +app.get('/forgot', getForgot) +app.post('/forgot', postForgot) +// RESET PASSWORD EMAIL HANDLING +app.get('/reset/:token', getReset); +app.post('/reset/:token', postReset); + +let FieldStore: ObservableMap = new ObservableMap(); app.get("/hello", (req, res) => { res.send("

Hello

"); }) diff --git a/views/reset.pug b/views/reset.pug new file mode 100644 index 000000000..8b6fa952b --- /dev/null +++ b/views/reset.pug @@ -0,0 +1,22 @@ + +extends ./layout + +block content + style + include ./stylesheets/authentication.css + form.form-horizontal(id='reset-form', method='POST') + input(type='hidden', name='_csrf', value=_csrf) + .overlay(id='overlay_reset') + .inner.reset + h3.auth_header Reset Password + .form-group + .col-sm-7 + input.form-control(type='password', name='password', id='password', placeholder='Password', required) + .form-group + .col-sm-7 + input.form-control(type='password', name='confirmPassword', id='confirmPassword', placeholder='Confirm Password', required) + .form-group + .col-sm-offset-3.col-sm-7 + button.btn.btn-success(type='submit') + i.fa.fa-user-plus + | Reset \ No newline at end of file diff --git a/views/signup.pug b/views/signup.pug index 374710e6f..11b02a5eb 100644 --- a/views/signup.pug +++ b/views/signup.pug @@ -24,4 +24,4 @@ block content .col-sm-offset-3.col-sm-7 button.btn.btn-success(type='submit') i.fa.fa-user-plus - | Signup \ No newline at end of file + | Sign Up \ No newline at end of file diff --git a/views/stylesheets/authentication.css b/views/stylesheets/authentication.css index 0922ad730..bce8223ec 100644 --- a/views/stylesheets/authentication.css +++ b/views/stylesheets/authentication.css @@ -26,7 +26,8 @@ body { text-align: left; } -.login { +.login, +.reset { height: 220px; } @@ -46,7 +47,8 @@ body { font-style: oblique; } -#overlay_signup { +#overlay_signup, +#overlay_reset { height: 345px; } -- cgit v1.2.3-70-g09d2 From 042eb977ad7733919daf468001b17dbee4e1fa9f Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 26 Feb 2019 18:58:07 -0500 Subject: beginning multiple workspaces backend and ui --- src/client/views/Main.tsx | 13 +- src/server/authentication/config/passport.ts | 2 +- src/server/authentication/controllers/user.ts | 279 -------------------- .../authentication/controllers/user_controller.ts | 291 +++++++++++++++++++++ src/server/authentication/models/User.ts | 90 ------- src/server/authentication/models/user_model.ts | 100 +++++++ src/server/index.ts | 43 ++- views/stylesheets/authentication.css | 29 +- views/workspace.pug | 13 + 9 files changed, 475 insertions(+), 385 deletions(-) delete mode 100644 src/server/authentication/controllers/user.ts create mode 100644 src/server/authentication/controllers/user_controller.ts delete mode 100644 src/server/authentication/models/User.ts create mode 100644 src/server/authentication/models/user_model.ts create mode 100644 views/workspace.pug (limited to 'src/server/authentication/models') diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 567f17752..e66816b6b 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -37,18 +37,25 @@ document.addEventListener("pointerdown", action(function (e: PointerEvent) { }), true) let mainDocId: string; -request.get(window.location.origin + "/getUserDocId", (error, response, body) => { +request.get(window.location.origin + "/getActiveWorkspaceId", (error, response, body) => { + const here = window.location.origin; if (body) { mainDocId = body; } else { mainDocId = Utils.GenerateGuid(); - request.post(window.location.origin + "/setUserDocId", { + request.post(here + "/addWorkspaceId", { body: { - userDocumentId: mainDocId + target: mainDocId }, json: true }) } + request.post(here + "/setActiveWorkspaceId", { + body: { + target: mainDocId + }, + json: true + }) init(); }) diff --git a/src/server/authentication/config/passport.ts b/src/server/authentication/config/passport.ts index 9f1303135..d90bedb18 100644 --- a/src/server/authentication/config/passport.ts +++ b/src/server/authentication/config/passport.ts @@ -2,7 +2,7 @@ import * as passport from 'passport' import * as passportLocal from 'passport-local'; import * as mongodb from 'mongodb'; import * as _ from "lodash"; -import { default as User } from '../models/User'; +import { default as User } from '../models/user_model'; import { Request, Response, NextFunction } from "express"; const LocalStrategy = passportLocal.Strategy; diff --git a/src/server/authentication/controllers/user.ts b/src/server/authentication/controllers/user.ts deleted file mode 100644 index 6c4139b2e..000000000 --- a/src/server/authentication/controllers/user.ts +++ /dev/null @@ -1,279 +0,0 @@ -import { default as User, UserModel, AuthToken } from "../models/User"; -import { Request, Response, NextFunction } from "express"; -import * as passport from "passport"; -import { IVerifyOptions } from "passport-local"; -import "../config/passport"; -import * as request from "express-validator"; -const flash = require("express-flash"); -import * as session from "express-session"; -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"); -} - -/** - * GET /signup - * Directs user to the signup page - * modeled by signup.pug in views - */ -export let getSignup = (req: Request, res: Response) => { - if (req.user) { - let user = req.user; - return res.redirect("/home"); - } - res.render("signup.pug", { - title: "Sign Up", - user: req.user, - }); -}; - -/** - * POST /signup - * Create a new local account. - */ -export let postSignup = (req: Request, res: Response, next: NextFunction) => { - req.assert("email", "Email is not valid").isEmail(); - req.assert("password", "Password must be at least 4 characters long").len({ min: 4 }); - req.assert("confirmPassword", "Passwords do not match").equals(req.body.password); - req.sanitize("email").normalizeEmail({ gmail_remove_dots: false }); - - const errors = req.validationErrors(); - - if (errors) { - res.render("signup.pug", { - title: "Sign Up", - user: req.user, - }); - return res.redirect("/signup"); - } - - const email = req.body.email; - const password = req.body.password; - - const user = new User({ - email, - password, - userDoc: "document here" - }); - - User.findOne({ email }, (err, existingUser) => { - if (err) { return next(err); } - if (existingUser) { - if (existingUser) { - // existingUser.update({ $set: { email: please_work } }, (err, res) => { }); - } - req.flash("errors", "Account with that email address already exists."); - return res.redirect("/signup"); - } - user.save((err) => { - if (err) { return next(err); } - req.logIn(user, (err) => { - if (err) { - return next(err); - } - res.redirect("/"); - }); - }); - }); - -}; - - -/** - * GET /login - * Login page. - */ -export let getLogin = (req: Request, res: Response) => { - if (req.user) { - return res.redirect("/home"); - } - res.render("login.pug", { - title: "Log In", - user: req.user - }); -}; - -/** - * POST /login - * Sign in using email and password. - * On failure, redirect to login page - */ -export let postLogin = (req: Request, res: Response, next: NextFunction) => { - req.assert("email", "Email is not valid").isEmail(); - req.assert("password", "Password cannot be blank").notEmpty(); - req.sanitize("email").normalizeEmail({ gmail_remove_dots: false }); - - const errors = req.validationErrors(); - - if (errors) { - req.flash("errors", "Unable to login at this time. Please try again."); - return res.redirect("/signup"); - } - - passport.authenticate("local", (err: Error, user: UserModel, info: IVerifyOptions) => { - if (err) { return next(err); } - if (!user) { - return res.redirect("/signup"); - } - req.logIn(user, (err) => { - if (err) { return next(err); } - req.flash("success", "Success! You are logged in."); - res.redirect("/home"); - }); - })(req, res, next); -}; - -/** - * GET /logout - * Invokes the logout function on the request - * and destroys the user's current session. - */ -export let getLogout = (req: Request, res: Response) => { - req.logout(); - const sess = req.session; - if (sess) { - sess.destroy((err) => { if (err) { console.log(err); } }); - } - res.redirect('/login'); -} - -export let getForgot = function (req: Request, res: Response) { - res.render("forgot.pug", { - title: "Recover Password", - user: req.user, - }); -} - -export let postForgot = function (req: Request, res: Response, next: NextFunction) { - const email = req.body.email; - async.waterfall([ - function (done: any) { - let token: string; - c.randomBytes(20, function (err: any, buffer: Buffer) { - if (err) { - done(null); - return; - } - done(null, buffer.toString('hex')); - }) - }, - function (token: string, done: any) { - User.findOne({ email }, function (err, user: UserModel) { - if (!user) { - // NO ACCOUNT WITH SUBMITTED EMAIL - return res.redirect('/forgot'); - } - user.passwordResetToken = token; - user.passwordResetExpires = new Date(Date.now() + 3600000); // 1 HOUR - user.save(function (err: any) { - done(null, token, user); - }); - }); - }, - function (token: Uint16Array, user: UserModel, done: any) { - const smtpTransport = nodemailer.createTransport({ - service: 'Gmail', - auth: { - user: 'brownptcdash@gmail.com', - pass: 'browngfx1' - } - }); - const mailOptions = { - to: user.email, - from: 'brownptcdash@gmail.com', - subject: 'Dash Password Reset', - text: 'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n' + - 'Please click on the following link, or paste this into your browser to complete the process:\n\n' + - 'http://' + req.headers.host + '/reset/' + token + '\n\n' + - 'If you did not request this, please ignore this email and your password will remain unchanged.\n' - }; - smtpTransport.sendMail(mailOptions, function (err) { - // req.flash('info', 'An e-mail has been sent to ' + user.email + ' with further instructions.'); - done(null, err, 'done'); - }); - } - ], function (err) { - if (err) return next(err); - res.redirect('/forgot'); - }) -} - -export let getReset = function (req: Request, res: Response) { - User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } }, function (err, user: UserModel) { - if (!user || err) { - return res.redirect('/forgot'); - } - res.render("reset.pug", { - title: "Reset Password", - user: req.user, - }); - }); -} - -export let postReset = function (req: Request, res: Response) { - async.waterfall([ - function (done: any) { - User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } }, function (err, user: UserModel) { - if (!user || err) { - return res.redirect('back'); - } - - req.assert("password", "Password must be at least 4 characters long").len({ min: 4 }); - req.assert("confirmPassword", "Passwords do not match").equals(req.body.password); - - if (req.validationErrors()) { - return res.redirect('back'); - } - - user.password = req.body.password; - user.passwordResetToken = undefined; - user.passwordResetExpires = undefined; - - user.save(function (err) { - if (err) { - return res.redirect("/login"); - } - req.logIn(user, function (err) { - if (err) { - return; - } - }); - done(null, user); - }); - }); - }, - function (user: UserModel, done: any) { - const smtpTransport = nodemailer.createTransport({ - service: 'Gmail', - auth: { - user: 'brownptcdash@gmail.com', - pass: 'browngfx1' - } - }); - const mailOptions = { - to: user.email, - from: 'brownptcdash@gmail.com', - subject: 'Your password has been changed', - text: 'Hello,\n\n' + - 'This is a confirmation that the password for your account ' + user.email + ' has just been changed.\n' - }; - smtpTransport.sendMail(mailOptions, function (err) { - done(null, err); - }); - } - ], function (err) { - res.redirect('/login'); - }); -} \ No newline at end of file diff --git a/src/server/authentication/controllers/user_controller.ts b/src/server/authentication/controllers/user_controller.ts new file mode 100644 index 000000000..899912ab7 --- /dev/null +++ b/src/server/authentication/controllers/user_controller.ts @@ -0,0 +1,291 @@ +import { default as User, DashUserModel, AuthToken } from "../models/user_model"; +import { Request, Response, NextFunction } from "express"; +import * as passport from "passport"; +import { IVerifyOptions } from "passport-local"; +import "../config/passport"; +import * as request from "express-validator"; +const flash = require("express-flash"); +import * as session from "express-session"; +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"); +} + +/** + * GET /signup + * Directs user to the signup page + * modeled by signup.pug in views + */ +export let getSignup = (req: Request, res: Response) => { + if (req.user) { + let user = req.user; + return res.redirect("/home"); + } + res.render("signup.pug", { + title: "Sign Up", + user: req.user, + }); +}; + +/** + * POST /signup + * Create a new local account. + */ +export let postSignup = (req: Request, res: Response, next: NextFunction) => { + req.assert("email", "Email is not valid").isEmail(); + req.assert("password", "Password must be at least 4 characters long").len({ min: 4 }); + req.assert("confirmPassword", "Passwords do not match").equals(req.body.password); + req.sanitize("email").normalizeEmail({ gmail_remove_dots: false }); + + const errors = req.validationErrors(); + + if (errors) { + res.render("signup.pug", { + title: "Sign Up", + user: req.user, + }); + return res.redirect("/signup"); + } + + const email = req.body.email; + const password = req.body.password; + + const user = new User({ + email, + password, + userDoc: "document here" + }); + + User.findOne({ email }, (err, existingUser) => { + if (err) { return next(err); } + if (existingUser) { + return res.redirect("/login"); + } + user.save((err) => { + if (err) { return next(err); } + req.logIn(user, (err) => { + if (err) { + return next(err); + } + res.redirect("/home"); + }); + }); + }); + +}; + + +/** + * GET /login + * Login page. + */ +export let getLogin = (req: Request, res: Response) => { + if (req.user) { + return res.redirect("/home"); + } + res.render("login.pug", { + title: "Log In", + user: req.user + }); +}; + +/** + * POST /login + * Sign in using email and password. + * On failure, redirect to login page + */ +export let postLogin = (req: Request, res: Response, next: NextFunction) => { + req.assert("email", "Email is not valid").isEmail(); + req.assert("password", "Password cannot be blank").notEmpty(); + req.sanitize("email").normalizeEmail({ gmail_remove_dots: false }); + + const errors = req.validationErrors(); + + if (errors) { + req.flash("errors", "Unable to login at this time. Please try again."); + return res.redirect("/signup"); + } + + passport.authenticate("local", (err: Error, user: DashUserModel, info: IVerifyOptions) => { + if (err) { return next(err); } + if (!user) { + return res.redirect("/signup"); + } + req.logIn(user, (err) => { + if (err) { return next(err); } + res.redirect("/home"); + }); + })(req, res, next); +}; + +export let getWorkspaces = (req: Request, res: Response) => { + const user: DashUserModel = req.user; + if (!user) { + return res.redirect("/login"); + } + res.render("workspace.pug", { + ids: user.allWorkspaceIds + }); +} + +/** + * GET /logout + * Invokes the logout function on the request + * 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 } }, () => { }) + console.log("UPDATED :)"); + } else { + console.log("NO USER BY LOGOUT"); + } + req.logout(); + const sess = req.session; + if (sess) { + sess.destroy((err) => { if (err) { console.log(err); } }); + } + res.redirect('/login'); +} + +export let getForgot = function (req: Request, res: Response) { + res.render("forgot.pug", { + title: "Recover Password", + user: req.user, + }); +} + +export let postForgot = function (req: Request, res: Response, next: NextFunction) { + const email = req.body.email; + async.waterfall([ + function (done: any) { + let token: string; + c.randomBytes(20, function (err: any, buffer: Buffer) { + if (err) { + done(null); + return; + } + done(null, buffer.toString('hex')); + }) + }, + function (token: string, done: any) { + User.findOne({ email }, function (err, user: DashUserModel) { + if (!user) { + // NO ACCOUNT WITH SUBMITTED EMAIL + return res.redirect('/forgot'); + } + user.passwordResetToken = token; + user.passwordResetExpires = new Date(Date.now() + 3600000); // 1 HOUR + user.save(function (err: any) { + done(null, token, user); + }); + }); + }, + function (token: Uint16Array, user: DashUserModel, done: any) { + const smtpTransport = nodemailer.createTransport({ + service: 'Gmail', + auth: { + user: 'brownptcdash@gmail.com', + pass: 'browngfx1' + } + }); + const mailOptions = { + to: user.email, + from: 'brownptcdash@gmail.com', + subject: 'Dash Password Reset', + text: 'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n' + + 'Please click on the following link, or paste this into your browser to complete the process:\n\n' + + 'http://' + req.headers.host + '/reset/' + token + '\n\n' + + 'If you did not request this, please ignore this email and your password will remain unchanged.\n' + }; + smtpTransport.sendMail(mailOptions, function (err) { + // req.flash('info', 'An e-mail has been sent to ' + user.email + ' with further instructions.'); + done(null, err, 'done'); + }); + } + ], function (err) { + if (err) return next(err); + res.redirect('/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'); + } + res.render("reset.pug", { + title: "Reset Password", + user: req.user, + }); + }); +} + +export let postReset = function (req: Request, res: Response) { + async.waterfall([ + function (done: any) { + User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } }, function (err, user: DashUserModel) { + if (!user || err) { + return res.redirect('back'); + } + + req.assert("password", "Password must be at least 4 characters long").len({ min: 4 }); + req.assert("confirmPassword", "Passwords do not match").equals(req.body.password); + + if (req.validationErrors()) { + return res.redirect('back'); + } + + user.password = req.body.password; + user.passwordResetToken = undefined; + user.passwordResetExpires = undefined; + + user.save(function (err) { + if (err) { + return res.redirect("/login"); + } + req.logIn(user, function (err) { + if (err) { + return; + } + }); + done(null, user); + }); + }); + }, + function (user: DashUserModel, done: any) { + const smtpTransport = nodemailer.createTransport({ + service: 'Gmail', + auth: { + user: 'brownptcdash@gmail.com', + pass: 'browngfx1' + } + }); + const mailOptions = { + to: user.email, + from: 'brownptcdash@gmail.com', + subject: 'Your password has been changed', + text: 'Hello,\n\n' + + 'This is a confirmation that the password for your account ' + user.email + ' has just been changed.\n' + }; + smtpTransport.sendMail(mailOptions, function (err) { + done(null, err); + }); + } + ], function (err) { + res.redirect('/login'); + }); +} \ No newline at end of file diff --git a/src/server/authentication/models/User.ts b/src/server/authentication/models/User.ts deleted file mode 100644 index 433e2f6c3..000000000 --- a/src/server/authentication/models/User.ts +++ /dev/null @@ -1,90 +0,0 @@ -//@ts-ignore -import * as bcrypt from "bcrypt-nodejs"; -//@ts-ignore -import * as mongoose from "mongoose"; -var url = 'mongodb://localhost:27017/Dash' - -mongoose.connect(url, { useNewUrlParser: true }); - -mongoose.connection.on('connected', function () { - console.log('Stablished connection on ' + url); -}); -mongoose.connection.on('error', function (error) { - console.log('Something wrong happened: ' + error); -}); -mongoose.connection.on('disconnected', function () { - console.log('connection closed'); -}); -export type UserModel = mongoose.Document & { - email: string, - password: string, - passwordResetToken: string | undefined, - passwordResetExpires: Date | undefined, - tokens: AuthToken[], - - profile: { - name: string, - gender: string, - location: string, - website: string, - picture: string - }, - - comparePassword: comparePasswordFunction, -}; - -type comparePasswordFunction = (candidatePassword: string, cb: (err: any, isMatch: any) => {}) => void; - -export type AuthToken = { - accessToken: string, - kind: string -}; - -const userSchema = new mongoose.Schema({ - email: { type: String, unique: true }, - password: String, - passwordResetToken: String, - passwordResetExpires: Date, - - userDocumentId: String, - - facebook: String, - twitter: String, - google: String, - tokens: Array, - - profile: { - name: String, - gender: String, - location: String, - website: String, - picture: String - } -}, { timestamps: true }); - -/** - * Password hash middleware. - */ -userSchema.pre("save", function save(next) { - const user = this as UserModel; - if (!user.isModified("password")) { return next(); } - bcrypt.genSalt(10, (err, salt) => { - if (err) { return next(err); } - bcrypt.hash(user.password, salt, () => void {}, (err: mongoose.Error, hash) => { - if (err) { return next(err); } - user.password = hash; - next(); - }); - }); -}); - -const comparePassword: comparePasswordFunction = function (this: UserModel, candidatePassword, cb) { - bcrypt.compare(candidatePassword, this.password, (err: mongoose.Error, isMatch: boolean) => { - cb(err, isMatch); - }); -}; - -userSchema.methods.comparePassword = comparePassword; - -const User = mongoose.model("User", userSchema); -export default User; \ No newline at end of file diff --git a/src/server/authentication/models/user_model.ts b/src/server/authentication/models/user_model.ts new file mode 100644 index 000000000..dfd104ef8 --- /dev/null +++ b/src/server/authentication/models/user_model.ts @@ -0,0 +1,100 @@ +//@ts-ignore +import * as bcrypt from "bcrypt-nodejs"; +//@ts-ignore +import * as mongoose from "mongoose"; +var url = 'mongodb://localhost:27017/Dash' + +mongoose.connect(url, { useNewUrlParser: true }); + +mongoose.connection.on('connected', function () { + console.log('Stablished connection on ' + url); +}); +mongoose.connection.on('error', function (error) { + console.log('Something wrong happened: ' + error); +}); +mongoose.connection.on('disconnected', function () { + console.log('connection closed'); +}); +export type DashUserModel = mongoose.Document & { + email: string, + password: string, + passwordResetToken: string | undefined, + passwordResetExpires: Date | undefined, + + allWorkspaceIds: Array, + activeWorkspaceId: String, + didSelectSessionWorkspace: Boolean, + + profile: { + name: string, + gender: string, + location: string, + website: string, + picture: string + }, + + comparePassword: comparePasswordFunction, +}; + +type comparePasswordFunction = (candidatePassword: string, cb: (err: any, isMatch: any) => {}) => void; + +export type AuthToken = { + accessToken: string, + kind: string +}; + +const userSchema = new mongoose.Schema({ + email: { type: String, unique: true }, + password: String, + passwordResetToken: String, + passwordResetExpires: Date, + + allWorkspaceIds: { + type: Array, + default: [] + }, + activeWorkspaceId: String, + didSelectSessionWorkspace: { + type: Boolean, + default: false + }, + + facebook: String, + twitter: String, + google: String, + + profile: { + name: String, + gender: String, + location: String, + website: String, + picture: String + } +}, { timestamps: true }); + +/** + * Password hash middleware. + */ +userSchema.pre("save", function save(next) { + const user = this as DashUserModel; + if (!user.isModified("password")) { return next(); } + bcrypt.genSalt(10, (err, salt) => { + if (err) { return next(err); } + bcrypt.hash(user.password, salt, () => void {}, (err: mongoose.Error, hash) => { + if (err) { return next(err); } + user.password = hash; + next(); + }); + }); +}); + +const comparePassword: comparePasswordFunction = function (this: DashUserModel, candidatePassword, cb) { + bcrypt.compare(candidatePassword, this.password, (err: mongoose.Error, isMatch: boolean) => { + cb(err, isMatch); + }); +}; + +userSchema.methods.comparePassword = comparePassword; + +const User = mongoose.model("User", userSchema); +export default User; \ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index 50f01fe31..6136b8d94 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -17,7 +17,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'; +import { getLogin, postLogin, getSignup, postSignup, getLogout, getEntry, postReset, getForgot, postForgot, getReset, getWorkspaces } from './authentication/controllers/user_controller'; const config = require('../../webpack.config'); const compiler = webpack(config); const port = 1050; // default port to listen @@ -34,7 +34,7 @@ const MongoStore = require('connect-mongo')(session); const mongoose = require('mongoose'); import { performance } from 'perf_hooks' import * as path from 'path' -import User, { UserModel } from './authentication/models/User'; +import User, { DashUserModel } from './authentication/models/user_model'; const mongoUrl = 'mongodb://localhost:27017/Dash'; mongoose.connect(mongoUrl) @@ -81,27 +81,48 @@ app.use((req, res, next) => { // /home defines destination after a successful log in app.get("/home", (req, res) => { // if user is not logged in, redirect to log in page - if (!req.user) { - res.redirect("/login"); - return; + const dashUser: DashUserModel = req.user; + if (!dashUser) { + return res.redirect("/login"); } // otherwise, connect them to Dash // TODO: store and manage users' workspaces + if (dashUser.allWorkspaceIds.length > 0) { + if (!dashUser.didSelectSessionWorkspace) { + return res.redirect("/workspaces"); + } + } else { + console.log("OK, UPDATED TO TRUE"); + dashUser.update({ $set: { didSelectSessionWorkspace: true } }, () => { }) + } res.sendFile(path.join(__dirname, '../../deploy/index.html')); }); -app.get("/getUserDocId", (req, res) => { - if (!req.user) { +app.get("/workspaces", getWorkspaces); + +app.get("/getActiveWorkspaceId", (req, res) => { + const dashUser: DashUserModel = req.user; + if (!dashUser) { + return; + } + res.send(dashUser.activeWorkspaceId || ""); +}); + +app.post("/setActiveWorkspaceId", (req, res) => { + const dashUser: DashUserModel = req.user; + if (!dashUser) { return; } - res.send(req.user.userDocumentId || ""); + console.log(`Updating active workspace ID to ${req.body.target}`); + dashUser.update({ $set: { activeWorkspaceId: req.body.target } }, () => { }); }) -app.post("/setUserDocId", (req, res) => { - if (!req.user) { +app.post("/addWorkspaceId", (req, res) => { + const dashUser: DashUserModel = req.user; + if (!dashUser) { return; } - req.user.update({ $set: { userDocumentId: req.body.userDocumentId } }, () => { }); + dashUser.update({ $push: { allWorkspaceIds: req.body.target } }, () => { }); }) // anyone attempting to navigate to localhost at this port will diff --git a/views/stylesheets/authentication.css b/views/stylesheets/authentication.css index bce8223ec..dea0474e4 100644 --- a/views/stylesheets/authentication.css +++ b/views/stylesheets/authentication.css @@ -48,10 +48,37 @@ body { } #overlay_signup, -#overlay_reset { +#overlay_reset, +#overlay_workspaces { height: 345px; } +.workspace-header { + margin-left: 20px; +} + +.select-workspace { + margin-top: 15px; + margin-left: 20px; +} + +#overlay_workspaces { + overflow-y: scroll; + text-align: left; +} + +.workspaceId { + list-style-type: none; + font-family: Arial, Helvetica, sans-serif; + margin-left: -20px; + cursor: grab; + padding-bottom: 15px; +} + +.workspaceId:hover { + color: red; +} + #overlay_login { height: 300px; } diff --git a/views/workspace.pug b/views/workspace.pug new file mode 100644 index 000000000..8bbc3e02c --- /dev/null +++ b/views/workspace.pug @@ -0,0 +1,13 @@ + +extends ./layout + +block content + style + include ./stylesheets/authentication.css + form.form-horizontal(id='workspace-form', method='POST', action='/home') + input(type='hidden', name='_csrf', value=_csrf) + .overlay(id='overlay_workspaces') + h3.workspace-header Select A Workspace + ul.workspaceList + each val, index in ids + li.workspaceId(onclick='console.log("' + val + '")')= (index + 1) + ') ' + val \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 8bdef5c07d3ba7e6c6a8940bb6786805893812c7 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 27 Feb 2019 10:29:53 -0500 Subject: workspaces menu initialized --- src/client/views/Main.tsx | 22 ++++++---- .../authentication/controllers/WorkspacesMenu.tsx | 48 ++++++++++++++++++++++ src/server/authentication/models/user_model.ts | 5 --- src/server/index.ts | 23 +++++++---- 4 files changed, 77 insertions(+), 21 deletions(-) create mode 100644 src/server/authentication/controllers/WorkspacesMenu.tsx (limited to 'src/server/authentication/models') diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index e66816b6b..e55bc693e 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -1,4 +1,4 @@ -import { action, configure, reaction, computed } from 'mobx'; +import { action, configure, reaction, computed, observable } from 'mobx'; import "normalize.css"; import * as React from 'react'; import * as ReactDOM from 'react-dom'; @@ -19,6 +19,7 @@ import { Transform } from '../util/Transform'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { FieldWaiting } from '../../fields/Field'; import { UndoManager } from '../util/UndoManager'; +import { WorkspacesMenu } from '../../server/authentication/controllers/WorkspacesMenu'; configure({ @@ -49,13 +50,13 @@ request.get(window.location.origin + "/getActiveWorkspaceId", (error, response, }, json: true }) + request.post(here + "/setActiveWorkspaceId", { + body: { + target: mainDocId + }, + json: true + }) } - request.post(here + "/setActiveWorkspaceId", { - body: { - target: mainDocId - }, - json: true - }) init(); }) @@ -184,6 +185,13 @@ function init() { right: '0px', width: '150px' }} onClick={() => window.location.pathname = "/logout"}>Logout + + ), document.getElementById('root')); }) diff --git a/src/server/authentication/controllers/WorkspacesMenu.tsx b/src/server/authentication/controllers/WorkspacesMenu.tsx new file mode 100644 index 000000000..77e3a9778 --- /dev/null +++ b/src/server/authentication/controllers/WorkspacesMenu.tsx @@ -0,0 +1,48 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import { observable, action, configure, reaction, computed } from 'mobx'; +import { observer } from "mobx-react"; +import * as request from 'request' + +@observer +export class WorkspacesMenu extends React.Component { + static Instance: WorkspacesMenu; + @observable private workspacesExposed: boolean = false; + @observable private workspaceIds: Array = []; + + constructor(props: Readonly<{}>) { + super(props); + WorkspacesMenu.Instance = this; + } + + toggle() { + action(() => { + if (!this.workspacesExposed) { + request.get(window.location.origin + "/getAllWorkspaceIds", (error, response, body) => { + this.workspaceIds = body; + console.log(this.workspaceIds); + }) + } + this.workspacesExposed = !this.workspacesExposed; + }); + } + + render() { + return ( +
+ {this.workspaceIds.map(s =>
  • ${s}
  • )} +
    + ); + } +} \ 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 dfd104ef8..29076ba19 100644 --- a/src/server/authentication/models/user_model.ts +++ b/src/server/authentication/models/user_model.ts @@ -23,7 +23,6 @@ export type DashUserModel = mongoose.Document & { allWorkspaceIds: Array, activeWorkspaceId: String, - didSelectSessionWorkspace: Boolean, profile: { name: string, @@ -54,10 +53,6 @@ const userSchema = new mongoose.Schema({ default: [] }, activeWorkspaceId: String, - didSelectSessionWorkspace: { - type: Boolean, - default: false - }, facebook: String, twitter: String, diff --git a/src/server/index.ts b/src/server/index.ts index 6136b8d94..8f740b1d7 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -87,18 +87,15 @@ app.get("/home", (req, res) => { } // otherwise, connect them to Dash // TODO: store and manage users' workspaces - if (dashUser.allWorkspaceIds.length > 0) { - if (!dashUser.didSelectSessionWorkspace) { - return res.redirect("/workspaces"); - } - } else { - console.log("OK, UPDATED TO TRUE"); - dashUser.update({ $set: { didSelectSessionWorkspace: true } }, () => { }) - } + // if (dashUser.allWorkspaceIds.length > 0) { + // if (!dashUser.didSelectSessionWorkspace) { + // return res.redirect("/workspaces"); + // } + // } res.sendFile(path.join(__dirname, '../../deploy/index.html')); }); -app.get("/workspaces", getWorkspaces); +// app.get("/workspaces", getWorkspaces); app.get("/getActiveWorkspaceId", (req, res) => { const dashUser: DashUserModel = req.user; @@ -108,6 +105,14 @@ app.get("/getActiveWorkspaceId", (req, res) => { res.send(dashUser.activeWorkspaceId || ""); }); +app.get("/getAllWorkspaceIds", (req, res) => { + const dashUser: DashUserModel = req.user; + if (!dashUser) { + return; + } + res.send(dashUser.allWorkspaceIds); +}) + app.post("/setActiveWorkspaceId", (req, res) => { const dashUser: DashUserModel = req.user; if (!dashUser) { -- cgit v1.2.3-70-g09d2 From 748412d972bd466a372fcf384448d3a00b42ee9f Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Mon, 18 Mar 2019 00:29:20 -0400 Subject: implemented multi-client remote cursor --- src/client/views/Main.tsx | 24 ++- .../views/collections/CollectionFreeFormView.tsx | 38 +++-- .../views/collections/CollectionViewBase.tsx | 44 ++--- src/fields/TupleField.ts | 2 +- src/server/ServerUtil.ts | 2 + src/server/authentication/models/user_utils.ts | 22 +++ src/server/index.ts | 179 ++++++++++++--------- 7 files changed, 179 insertions(+), 132 deletions(-) create mode 100644 src/server/authentication/models/user_utils.ts (limited to 'src/server/authentication/models') 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(); let pdfRef = React.createRef(); @@ -233,7 +227,7 @@ export class Main extends React.Component {
    -
    + {/* 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 (
    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 ( -
    - ); - })}
    @@ -351,11 +337,33 @@ export class CollectionFreeFormView extends CollectionViewBase { {this.views} + {super.getCursors().map(entry => { + if (entry.Data.length > 0) { + let point = entry.Data[1] + return ( +
    + ); + } + })}
    {this.overlayView} +
    ); } 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; +export type CursorEntry = TupleField; export class CollectionViewBase extends React.Component { private dropDisposer?: DragManager.DragDropDisposer; @@ -50,31 +49,34 @@ export class CollectionViewBase extends React.Component } } + @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>(KeyStore.Cursors, ListField, false).Data; - let entry = new TupleField([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>(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([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(KeyStore.Cursors, []).filter(entry => entry.Data[0] !== user.id); - } - return []; + let doc = this.props.Document; + let id = UserUtils.currentUserId; + let cursors = doc.GetList(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 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 -- cgit v1.2.3-70-g09d2 From 34f3b837334eb3d4a9416a8397c88cbd1ca421e0 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Mon, 18 Mar 2019 01:43:50 -0400 Subject: flashier remote cursors --- src/client/views/Main.tsx | 4 +- .../views/collections/CollectionFreeFormView.tsx | 75 +++++++++++++++++++--- .../views/collections/CollectionViewBase.tsx | 6 +- .../authentication/models/current_user_utils.ts | 29 +++++++++ src/server/authentication/models/user_utils.ts | 22 ------- src/server/index.ts | 7 +- 6 files changed, 106 insertions(+), 37 deletions(-) create mode 100644 src/server/authentication/models/current_user_utils.ts delete mode 100644 src/server/authentication/models/user_utils.ts (limited to 'src/server/authentication/models') diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index fd756972b..7942367f5 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -40,7 +40,7 @@ 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'; +import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; @observer export class Main extends React.Component { @@ -63,7 +63,7 @@ export class Main extends React.Component { this.mainDocId = pathname[pathname.length - 1]; } - UserUtils.loadCurrentUserId(); + CurrentUserUtils.loadCurrentUser(); library.add(faFont); library.add(faImage); diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx index 89edd1397..4a82bdb77 100644 --- a/src/client/views/collections/CollectionFreeFormView.tsx +++ b/src/client/views/collections/CollectionFreeFormView.tsx @@ -29,7 +29,10 @@ import { CollectionViewBase } from "./CollectionViewBase"; import { MarqueeView } from "./MarqueeView"; import { PreviewCursor } from "./PreviewCursor"; import React = require("react"); +import { Utils } from "../../../Utils"; const JsxParser = require('react-jsx-parser').default;//TODO Why does this need to be imported like this? +import v5 = require("uuid/v5"); +import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; @observer export class CollectionFreeFormView extends CollectionViewBase { @@ -310,6 +313,40 @@ export class CollectionFreeFormView extends CollectionViewBase { this.PreviewCursorVisible = false; } + private crosshairs?: HTMLCanvasElement; + drawCrosshairs = (backgroundColor: string) => { + if (this.crosshairs) { + let c = this.crosshairs; + let ctx = c.getContext('2d'); + if (ctx) { + ctx.fillStyle = backgroundColor; + ctx.fillRect(0, 0, 20, 20); + + ctx.fillStyle = "black"; + ctx.lineWidth = 0.5; + + ctx.beginPath(); + + ctx.moveTo(10, 0); + ctx.lineTo(10, 8); + + ctx.moveTo(10, 20); + ctx.lineTo(10, 12); + + ctx.moveTo(0, 10); + ctx.lineTo(8, 10); + + ctx.moveTo(20, 10); + ctx.lineTo(12, 10); + + ctx.stroke(); + + // ctx.font = "10px Arial"; + // ctx.fillText(CurrentUserUtils.email[0].toUpperCase(), 10, 10); + } + } + } + render() { let [dx, dy] = [this.centeringShiftX, this.centeringShiftY]; @@ -339,22 +376,42 @@ export class CollectionFreeFormView extends CollectionViewBase { {this.views} {super.getCursors().map(entry => { if (entry.Data.length > 0) { - let point = entry.Data[1] + let id = entry.Data[0]; + let point = entry.Data[1]; + this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22") return (
    + > + { if (el) this.crosshairs = el }} + width={20} + height={20} + style={{ + position: 'absolute', + width: "20px", + height: "20px", + opacity: 0.5, + borderRadius: "50%", + border: "2px solid black" + }} + /> +

    {CurrentUserUtils.email[0].toUpperCase()}

    +
    ); } })} diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx index 02ee49a38..9b5c88d14 100644 --- a/src/client/views/collections/CollectionViewBase.tsx +++ b/src/client/views/collections/CollectionViewBase.tsx @@ -14,7 +14,7 @@ import { CollectionView } from "./CollectionView"; import { RouteStore } from "../../../server/RouteStore"; import { TupleField } from "../../../fields/TupleField"; import { DashUserModel } from "../../../server/authentication/models/user_model"; -import { UserUtils } from "../../../server/authentication/models/user_utils"; +import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; export interface CollectionViewProps { fieldKey: Key; @@ -53,7 +53,7 @@ export class CollectionViewBase extends React.Component protected setCursorPosition(position: [number, number]) { let ind; let doc = this.props.Document; - let id = UserUtils.currentUserId; + let id = CurrentUserUtils.id; if (id) { doc.GetOrCreateAsync>(KeyStore.Cursors, ListField, field => { let cursors = field.Data; @@ -71,7 +71,7 @@ export class CollectionViewBase extends React.Component protected getCursors(): CursorEntry[] { let doc = this.props.Document; - let id = UserUtils.currentUserId; + let id = CurrentUserUtils.id; let cursors = doc.GetList(KeyStore.Cursors, []); let notMe = cursors.filter(entry => entry.Data[0] !== id); return id ? notMe : []; diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts new file mode 100644 index 000000000..cc433eb73 --- /dev/null +++ b/src/server/authentication/models/current_user_utils.ts @@ -0,0 +1,29 @@ +import { DashUserModel } from "./user_model"; +import * as request from 'request' +import { RouteStore } from "../../RouteStore"; +import { ServerUtils } from "../../ServerUtil"; + +export class CurrentUserUtils { + private static curr_email: string; + private static curr_id: string; + + public static get email() { + return CurrentUserUtils.curr_email; + } + + public static get id() { + return CurrentUserUtils.curr_id; + } + + public static loadCurrentUser() { + request.get(ServerUtils.prepend(RouteStore.getCurrUser), (error, response, body) => { + if (body) { + let obj = JSON.parse(body); + CurrentUserUtils.curr_id = obj.id as string; + CurrentUserUtils.curr_email = obj.email 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/authentication/models/user_utils.ts b/src/server/authentication/models/user_utils.ts deleted file mode 100644 index 1497a4ba4..000000000 --- a/src/server/authentication/models/user_utils.ts +++ /dev/null @@ -1,22 +0,0 @@ -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 0512ebf72..6d8dd2d6c 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -151,7 +151,12 @@ addSecureRoute( addSecureRoute( Method.GET, - (user, res) => res.send(JSON.stringify(user.id)), + (user, res) => { + res.send(JSON.stringify({ + id: user.id, + email: user.email + })); + }, undefined, RouteStore.getCurrUser ); -- cgit v1.2.3-70-g09d2 From bece3110a3d6fa18dd89415a5e636c6792aea4e1 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Wed, 20 Mar 2019 05:07:52 -0400 Subject: Refactored users to have a single user document instead of a list of workspaces --- src/client/views/Main.tsx | 132 ++++++++++++--------- src/fields/KeyStore.ts | 2 + src/mobile/ImageUpload.tsx | 38 +++--- src/server/RouteStore.ts | 5 +- .../authentication/controllers/WorkspacesMenu.tsx | 19 +-- .../authentication/controllers/user_controller.ts | 3 +- .../authentication/models/current_user_utils.ts | 46 +++++-- src/server/authentication/models/user_model.ts | 11 +- src/server/index.ts | 33 +----- 9 files changed, 144 insertions(+), 145 deletions(-) (limited to 'src/server/authentication/models') diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index ac51a7d87..a1a6cc475 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -8,6 +8,7 @@ import "./Main.scss"; import { MessageStore } from '../../server/Message'; import { Utils } from '../../Utils'; import * as request from 'request' +import * as rp from 'request-promise' import { Documents } from '../documents/Documents'; import { Server } from '../Server'; import { setupDrag } from '../util/DragManager'; @@ -40,7 +41,7 @@ import Measure from 'react-measure'; import { DashUserModel } from '../../server/authentication/models/user_model'; import { ServerUtils } from '../../server/ServerUtil'; import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; -import { Field, Opt } from '../../fields/Field'; +import { Field, Opt, FieldWaiting } from '../../fields/Field'; import { ListField } from '../../fields/ListField'; import { map } from 'bluebird'; import { Gateway, Settings } from '../northstar/manager/Gateway'; @@ -49,13 +50,26 @@ import { Catalog } from '../northstar/model/idea/idea'; @observer export class Main extends React.Component { // dummy initializations keep the compiler happy - @observable private mainContainer?: Document; @observable private mainfreeform?: Document; - @observable private userWorkspaces: Document[] = []; @observable public pwidth: number = 0; @observable public pheight: number = 0; @observable private _northstarCatalog: Catalog | undefined = undefined; + @computed private get mainContainer(): Document | undefined { + let doc = this.userDocument.GetT(KeyStore.ActiveWorkspace, Document); + return doc == FieldWaiting ? undefined : doc; + } + + private set mainContainer(doc: Document | undefined) { + if (doc) { + this.userDocument.Set(KeyStore.ActiveWorkspace, doc); + } + } + + private get userDocument(): Document { + return CurrentUserUtils.UserDocument; + } + public mainDocId: string | undefined; private currentUser?: DashUserModel; public static Instance: Main; @@ -67,12 +81,12 @@ export class Main extends React.Component { configure({ enforceActions: "observed" }); if (window.location.pathname !== RouteStore.home) { let pathname = window.location.pathname.split("/"); - this.mainDocId = pathname[pathname.length - 1]; + if (pathname.length > 1 && pathname[pathname.length - 2] == 'doc') { + this.mainDocId = pathname[pathname.length - 1]; + } }; - this.initializeNorthstar(); - - CurrentUserUtils.loadCurrentUser(); + // this.initializeNorthstar(); library.add(faFont); library.add(faImage); @@ -143,63 +157,50 @@ 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(ServerUtils.prepend(RouteStore.getActiveWorkspace), (error, response, body) => { - if (this.mainDocId || body) { - Server.GetField(this.mainDocId || body, field => { - if (field instanceof Document) { - this.openWorkspace(field); - this.populateWorkspaces(); - } else { - this.createNewWorkspace(true, this.mainDocId); - } - }); - } else { - this.createNewWorkspace(true, this.mainDocId); - } - }); - } - - @action - createNewWorkspace = (init: boolean, id?: string): void => { - let mainDoc = Documents.DockDocument(JSON.stringify({ content: [{ type: 'row', content: [] }] }), { title: `Main Container ${this.userWorkspaces.length + 1}` }, id); - let newId = mainDoc.Id; - request.post(ServerUtils.prepend(RouteStore.addWorkspace), { - body: { target: newId }, - json: true - }, () => { if (init) this.populateWorkspaces(); }); - - // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container) - setTimeout(() => { - let freeformDoc = Documents.FreeformDocument([], { x: 0, y: 400, title: "mini collection" }); - var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(freeformDoc)] }] }; - mainDoc.SetText(KeyStore.Data, JSON.stringify(dockingLayout)); - mainDoc.Set(KeyStore.ActiveFrame, freeformDoc); - this.openWorkspace(mainDoc); - let pendingDocument = Documents.SchemaDocument([], { title: "New Mobile Uploads" }) - mainDoc.Set(KeyStore.OptionalRightCollection, pendingDocument); - }, 0); - this.userWorkspaces.push(mainDoc); + if (!this.mainDocId) { + this.userDocument.GetTAsync(KeyStore.ActiveWorkspace, Document).then(doc => { + if (doc) { + this.openWorkspace(doc); + } else { + this.createNewWorkspace(); + } + }) + } else { + Server.GetField(this.mainDocId).then(field => { + if (field instanceof Document) { + this.openWorkspace(field) + } else { + this.createNewWorkspace(this.mainDocId); + } + }) + } } @action - populateWorkspaces = () => { - // retrieve all workspace documents from the server - 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))); - }); + createNewWorkspace = (id?: string): void => { + this.userDocument.GetTAsync>(KeyStore.Workspaces, ListField).then(action((list: Opt>) => { + if (list) { + let freeformDoc = Documents.FreeformDocument([], { x: 0, y: 400, title: "mini collection" }); + var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(freeformDoc)] }] }; + let mainDoc = Documents.DockDocument(JSON.stringify(dockingLayout), { title: `Main Container ${list.Data.length + 1}` }, id); + list.Data.push(mainDoc); + // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container) + setTimeout(() => { + mainDoc.Set(KeyStore.ActiveFrame, freeformDoc); + this.openWorkspace(mainDoc); + let pendingDocument = Documents.SchemaDocument([], { title: "New Mobile Uploads" }) + mainDoc.Set(KeyStore.OptionalRightCollection, pendingDocument); + }, 0); + } + })); } @action openWorkspace = (doc: Document, fromHistory = false): void => { - request.post(ServerUtils.prepend(RouteStore.setActiveWorkspace), { - body: { target: doc.Id }, - json: true - }); this.mainContainer = doc; fromHistory || window.history.pushState(null, doc.Title, "/doc/" + doc.Id); this.mainContainer.GetTAsync(KeyStore.ActiveFrame, Document, field => this.mainfreeform = field); - this.mainContainer.GetTAsync(KeyStore.OptionalRightCollection, Document, col => { + this.userDocument.GetTAsync(KeyStore.OptionalRightCollection, Document).then(col => { // if there is a pending doc, and it has new data, show it (syip: we use a timeout to prevent collection docking view from being uninitialized) setTimeout(() => { if (col) { @@ -213,10 +214,15 @@ export class Main extends React.Component { }); } + @observable + workspacesShown: boolean = false; + + areWorkspacesShown = () => { + return this.workspacesShown; + } + @action toggleWorkspaces = () => { - if (WorkspacesMenu.Instance) { - WorkspacesMenu.Instance.toggle() - } + this.workspacesShown = !this.workspacesShown; } screenToLocalTransform = () => Transform.Identity @@ -310,6 +316,12 @@ export class Main extends React.Component { } render() { + let workspaceMenu: any = null; + let workspaces = this.userDocument.GetT>(KeyStore.Workspaces, ListField); + if (workspaces && workspaces !== FieldWaiting) { + workspaceMenu = + } return (
    runInAction(() => { @@ -326,11 +338,13 @@ export class Main extends React.Component { {this.nodesMenu} {this.miscButtons} - + {workspaceMenu}
    ); } } -ReactDOM.render(
    , document.getElementById('root')); +CurrentUserUtils.loadCurrentUser().then(() => { + ReactDOM.render(
    , document.getElementById('root')); +}); diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts index 68883d6f1..891caaa81 100644 --- a/src/fields/KeyStore.ts +++ b/src/fields/KeyStore.ts @@ -28,6 +28,7 @@ export namespace KeyStore { export const SchemaSplitPercentage = new Key("SchemaSplitPercentage"); export const Caption = new Key("Caption"); export const ActiveFrame = new Key("ActiveFrame"); + export const ActiveWorkspace = new Key("ActiveWorkspace"); export const DocumentText = new Key("DocumentText"); export const LinkedToDocs = new Key("LinkedToDocs"); export const LinkedFromDocs = new Key("LinkedFromDocs"); @@ -41,4 +42,5 @@ export namespace KeyStore { export const OptionalRightCollection = new Key("OptionalRightCollection"); export const Archives = new Key("Archives"); export const Updated = new Key("Updated"); + export const Workspaces = new Key("Workspaces"); } diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx index 16808a598..47b9d8f0b 100644 --- a/src/mobile/ImageUpload.tsx +++ b/src/mobile/ImageUpload.tsx @@ -7,7 +7,7 @@ import { Server } from '../client/Server'; import { Documents } from '../client/documents/Documents'; import { ListField } from '../fields/ListField'; import { ImageField } from '../fields/ImageField'; -import request = require('request'); +import * as rp from 'request-promise' import { ServerUtils } from '../server/ServerUtil'; import { RouteStore } from '../server/RouteStore'; @@ -40,28 +40,24 @@ const onFileLoad = (file: any) => { json.map((file: any) => { let path = window.location.origin + file var doc: Document = Documents.ImageDocument(path, { nativeWidth: 200, width: 200 }) - doc.GetTAsync(KeyStore.Data, ImageField, (i) => { - if (i) { - document.getElementById("message")!.innerText = i.Data.href; + + rp.get(ServerUtils.prepend(RouteStore.getUserDocumentId)).then(res => { + if (res) { + return Server.GetField(res); + } + throw new Error("No user id returned"); + }).then(field => { + if (field instanceof Document) { + return field.GetTAsync(KeyStore.OptionalRightCollection, Document) } - }) - request.get(ServerUtils.prepend(RouteStore.getActiveWorkspace), (error, response, body) => { - if (body) { - Server.GetField(body, field => { - if (field instanceof Document) { - field.GetTAsync(KeyStore.OptionalRightCollection, Document, - pending => { - if (pending) { - pending.GetOrCreateAsync(KeyStore.Data, ListField, list => { - list.Data.push(doc); - }) - } - }) - } - } - ); + }).then(pending => { + if (pending) { + pending.GetOrCreateAsync(KeyStore.Data, ListField, list => { + list.Data.push(doc); + }) } - }) + }); + // console.log(window.location.origin + file[0]) //imgPrev.setAttribute("src", window.location.origin + files[0].name) diff --git a/src/server/RouteStore.ts b/src/server/RouteStore.ts index fb06b878b..fdf5b6a5c 100644 --- a/src/server/RouteStore.ts +++ b/src/server/RouteStore.ts @@ -15,10 +15,7 @@ export enum RouteStore { // USER AND WORKSPACES getCurrUser = "/getCurrentUser", - addWorkspace = "/addWorkspaceId", - getAllWorkspaces = "/getAllWorkspaceIds", - getActiveWorkspace = "/getActiveWorkspaceId", - setActiveWorkspace = "/setActiveWorkspaceId", + getUserDocumentId = "/getUserDocumentId", updateCursor = "/updateCursor", openDocumentWithId = "/doc/:docId", diff --git a/src/server/authentication/controllers/WorkspacesMenu.tsx b/src/server/authentication/controllers/WorkspacesMenu.tsx index 1533b1e62..8e14cf98e 100644 --- a/src/server/authentication/controllers/WorkspacesMenu.tsx +++ b/src/server/authentication/controllers/WorkspacesMenu.tsx @@ -9,30 +9,23 @@ import { KeyStore } from '../../../fields/KeyStore'; export interface WorkspaceMenuProps { active: Document | undefined; open: (workspace: Document) => void; - new: (init: boolean) => void; + new: () => void; allWorkspaces: Document[]; + isShown: () => boolean; + toggle: () => void; } @observer export class WorkspacesMenu extends React.Component { - static Instance: WorkspacesMenu; - @observable private workspacesExposed: boolean = false; - constructor(props: WorkspaceMenuProps) { super(props); - WorkspacesMenu.Instance = this; this.addNewWorkspace = this.addNewWorkspace.bind(this); } @action addNewWorkspace() { - this.props.new(false); - this.toggle(); - } - - @action - toggle() { - this.workspacesExposed = !this.workspacesExposed; + this.props.new(); + this.props.toggle(); } render() { @@ -45,7 +38,7 @@ export class WorkspacesMenu extends React.Component { borderRadius: 5, position: "absolute", top: 78, - left: this.workspacesExposed ? 11 : -500, + left: this.props.isShown() ? 11 : -500, background: "white", border: "black solid 2px", transition: "all 1s ease", diff --git a/src/server/authentication/controllers/user_controller.ts b/src/server/authentication/controllers/user_controller.ts index 2cef958e8..e365b8dce 100644 --- a/src/server/authentication/controllers/user_controller.ts +++ b/src/server/authentication/controllers/user_controller.ts @@ -11,6 +11,7 @@ import * as async from 'async'; import * as nodemailer from 'nodemailer'; import c = require("crypto"); import { RouteStore } from "../../RouteStore"; +import { Utils } from "../../../Utils"; /** * GET /signup @@ -54,7 +55,7 @@ export let postSignup = (req: Request, res: Response, next: NextFunction) => { const user = new User({ email, password, - userDoc: "document here" + userDocumentId: Utils.GenerateGuid() }); User.findOne({ email }, (err, existingUser) => { diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index cc433eb73..17a6d493b 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -1,29 +1,61 @@ import { DashUserModel } from "./user_model"; -import * as request from 'request' +import * as rp from 'request-promise'; import { RouteStore } from "../../RouteStore"; import { ServerUtils } from "../../ServerUtil"; +import { Server } from "../../../client/Server"; +import { Document } from "../../../fields/Document"; +import { KeyStore } from "../../../fields/KeyStore"; +import { ListField } from "../../../fields/ListField"; +import { Documents } from "../../../client/documents/Documents"; export class CurrentUserUtils { private static curr_email: string; private static curr_id: string; + private static user_document: Document; - public static get email() { + public static get email(): string { return CurrentUserUtils.curr_email; } - public static get id() { + public static get id(): string { return CurrentUserUtils.curr_id; } - public static loadCurrentUser() { - request.get(ServerUtils.prepend(RouteStore.getCurrUser), (error, response, body) => { - if (body) { - let obj = JSON.parse(body); + public static get UserDocument(): Document { + return CurrentUserUtils.user_document; + } + + private static createUserDocument(id: string): Document { + let doc = new Document(id); + + doc.Set(KeyStore.Workspaces, new ListField()); + doc.Set(KeyStore.OptionalRightCollection, Documents.SchemaDocument([], { title: "Pending documents" })) + return doc; + } + + public static loadCurrentUser(): Promise { + let userPromise = rp.get(ServerUtils.prepend(RouteStore.getCurrUser)).then((response) => { + if (response) { + let obj = JSON.parse(response); CurrentUserUtils.curr_id = obj.id as string; CurrentUserUtils.curr_email = obj.email as string; } else { throw new Error("There should be a user! Why does Dash think there isn't one?") } }); + let userDocPromise = rp.get(ServerUtils.prepend(RouteStore.getUserDocumentId)).then(id => { + if (id) { + return Server.GetField(id).then(field => { + if (field instanceof Document) { + this.user_document = field; + } else { + this.user_document = this.createUserDocument(id); + } + }) + } else { + throw new Error("There should be a user id! Why does Dash think there isn't one?") + } + }); + return Promise.all([userPromise, userDocPromise]); } } \ 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 3d4ed6896..81580aad5 100644 --- a/src/server/authentication/models/user_model.ts +++ b/src/server/authentication/models/user_model.ts @@ -21,9 +21,7 @@ export type DashUserModel = mongoose.Document & { passwordResetToken: string | undefined, passwordResetExpires: Date | undefined, - allWorkspaceIds: Array, - activeWorkspaceId: String, - activeUsersId: String, + userDocumentId: string; profile: { name: string, @@ -49,12 +47,7 @@ const userSchema = new mongoose.Schema({ passwordResetToken: String, passwordResetExpires: Date, - allWorkspaceIds: { - type: Array, - default: [] - }, - activeWorkspaceId: String, - activeUsersId: String, + userDocumentId: String, facebook: String, twitter: String, diff --git a/src/server/index.ts b/src/server/index.ts index d1eb6847d..16304f1c5 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -156,16 +156,9 @@ addSecureRoute( addSecureRoute( Method.GET, - (user, res) => res.send(user.activeWorkspaceId || ""), + (user, res) => res.send(user.userDocumentId || ""), undefined, - RouteStore.getActiveWorkspace, -); - -addSecureRoute( - Method.GET, - (user, res) => res.send(JSON.stringify(user.allWorkspaceIds)), - undefined, - RouteStore.getAllWorkspaces + RouteStore.getUserDocumentId, ); addSecureRoute( @@ -182,28 +175,6 @@ addSecureRoute( // SETTERS -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) => { -- cgit v1.2.3-70-g09d2 From 3eefc8c7e901242ac6b7614bf1163858568d53b0 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Fri, 22 Mar 2019 05:19:37 -0400 Subject: Moved main doc id --- src/client/views/Main.tsx | 14 ++++++-------- src/client/views/collections/CollectionView.tsx | 4 ++-- src/server/authentication/models/current_user_utils.ts | 16 +++++++++++++--- 3 files changed, 21 insertions(+), 13 deletions(-) (limited to 'src/server/authentication/models') diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index fdbad6ce1..cb49bc4e6 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -73,8 +73,6 @@ export class Main extends React.Component { return CurrentUserUtils.UserDocument; } - public mainDocId: string | undefined; - private currentUser?: DashUserModel; public static Instance: Main; constructor(props: Readonly<{}>) { @@ -85,7 +83,7 @@ export class Main extends React.Component { if (window.location.pathname !== RouteStore.home) { let pathname = window.location.pathname.split("/"); if (pathname.length > 1 && pathname[pathname.length - 2] == 'doc') { - this.mainDocId = pathname[pathname.length - 1]; + CurrentUserUtils.MainDocId = pathname[pathname.length - 1]; } }; @@ -117,8 +115,8 @@ export class Main extends React.Component { onHistory = () => { if (window.location.pathname !== RouteStore.home) { let pathname = window.location.pathname.split("/"); - this.mainDocId = pathname[pathname.length - 1]; - Server.GetField(this.mainDocId, action((field: Opt) => { + CurrentUserUtils.MainDocId = pathname[pathname.length - 1]; + Server.GetField(CurrentUserUtils.MainDocId, action((field: Opt) => { if (field instanceof Document) { this.openWorkspace(field, true); } @@ -148,7 +146,7 @@ export class Main extends React.Component { initAuthenticationRouters = () => { // Load the user's active workspace, or create a new one if initial session after signup - if (!this.mainDocId) { + if (!CurrentUserUtils.MainDocId) { this.userDocument.GetTAsync(KeyStore.ActiveWorkspace, Document).then(doc => { if (doc) { this.openWorkspace(doc); @@ -157,11 +155,11 @@ export class Main extends React.Component { } }) } else { - Server.GetField(this.mainDocId).then(field => { + Server.GetField(CurrentUserUtils.MainDocId).then(field => { if (field instanceof Document) { this.openWorkspace(field) } else { - this.createNewWorkspace(this.mainDocId); + this.createNewWorkspace(CurrentUserUtils.MainDocId); } }) } diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 7e1d31018..c72633175 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -13,7 +13,7 @@ import { CollectionSchemaView } from "./CollectionSchemaView"; import { CollectionViewProps } from "./CollectionViewBase"; import { CollectionTreeView } from "./CollectionTreeView"; import { Field, FieldId, FieldWaiting } from "../../../fields/Field"; -import { Main } from "../Main"; +import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; export enum CollectionViewType { Invalid, @@ -96,7 +96,7 @@ export class CollectionView extends React.Component { } specificContextMenu = (e: React.MouseEvent): void => { - if (!e.isPropagationStopped() && this.props.Document.Id != Main.Instance.mainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 + if (!e.isPropagationStopped() && this.props.Document.Id != CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 ContextMenu.Instance.addItem({ description: "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) }) diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 17a6d493b..3291c671c 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -12,17 +12,27 @@ export class CurrentUserUtils { private static curr_email: string; private static curr_id: string; private static user_document: Document; + //TODO tfs: this should be temporary... + private static mainDocId: string | undefined; public static get email(): string { - return CurrentUserUtils.curr_email; + return this.curr_email; } public static get id(): string { - return CurrentUserUtils.curr_id; + return this.curr_id; } public static get UserDocument(): Document { - return CurrentUserUtils.user_document; + return this.user_document; + } + + public static get MainDocId(): string | undefined { + return this.mainDocId; + } + + public static set MainDocId(id: string | undefined) { + this.mainDocId = id; } private static createUserDocument(id: string): Document { -- cgit v1.2.3-70-g09d2 From dfe70d4f21a8122a6608e127203de2572a9a25fb Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Fri, 22 Mar 2019 05:32:20 -0400 Subject: Moved active schema out of main --- src/client/northstar/model/ModelHelpers.ts | 10 ++++---- .../northstar/operations/HistogramOperation.ts | 4 ++-- src/client/views/Main.tsx | 21 ++--------------- src/client/views/nodes/HistogramBox.tsx | 4 ++-- .../authentication/models/current_user_utils.ts | 27 +++++++++++++++++++++- 5 files changed, 37 insertions(+), 29 deletions(-) (limited to 'src/server/authentication/models') diff --git a/src/client/northstar/model/ModelHelpers.ts b/src/client/northstar/model/ModelHelpers.ts index e1241b3ef..914e03255 100644 --- a/src/client/northstar/model/ModelHelpers.ts +++ b/src/client/northstar/model/ModelHelpers.ts @@ -9,7 +9,7 @@ import { AlphabeticVisualBinRange } from "./binRanges/AlphabeticVisualBinRange"; import { NominalVisualBinRange } from "./binRanges/NominalVisualBinRange"; import { VisualBinRangeHelper } from "./binRanges/VisualBinRangeHelper"; import { AttributeTransformationModel } from "../core/attribute/AttributeTransformationModel"; -import { Main } from "../../views/Main"; +import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; export class ModelHelpers { @@ -39,19 +39,19 @@ export class ModelHelpers { if (atm.AggregateFunction === AggregateFunction.Avg) { var avg = new AverageAggregateParameters(); avg.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel); - avg.distinctAttributeParameters = Main.Instance.ActiveSchema!.distinctAttributeParameters; + avg.distinctAttributeParameters = CurrentUserUtils.ActiveSchema!.distinctAttributeParameters; aggParam = avg; } else if (atm.AggregateFunction === AggregateFunction.Count) { var cnt = new CountAggregateParameters(); cnt.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel); - cnt.distinctAttributeParameters = Main.Instance.ActiveSchema!.distinctAttributeParameters; + cnt.distinctAttributeParameters = CurrentUserUtils.ActiveSchema!.distinctAttributeParameters; aggParam = cnt; } else if (atm.AggregateFunction === AggregateFunction.Sum) { var sum = new SumAggregateParameters(); sum.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel); - sum.distinctAttributeParameters = Main.Instance.ActiveSchema!.distinctAttributeParameters; + sum.distinctAttributeParameters = CurrentUserUtils.ActiveSchema!.distinctAttributeParameters; aggParam = sum; } return aggParam; @@ -66,7 +66,7 @@ export class ModelHelpers { var margin = new MarginAggregateParameters(); margin.attributeParameters = ModelHelpers.GetAttributeParameters(agg.AttributeModel); - margin.distinctAttributeParameters = Main.Instance.ActiveSchema!.distinctAttributeParameters; + margin.distinctAttributeParameters = CurrentUserUtils.ActiveSchema!.distinctAttributeParameters; margin.aggregateFunction = agg.AggregateFunction; aggregateParameters.push(margin); } diff --git a/src/client/northstar/operations/HistogramOperation.ts b/src/client/northstar/operations/HistogramOperation.ts index a4f5cac70..120a84dad 100644 --- a/src/client/northstar/operations/HistogramOperation.ts +++ b/src/client/northstar/operations/HistogramOperation.ts @@ -5,8 +5,8 @@ import { CalculatedAttributeManager } from "../core/attribute/CalculatedAttribut import { ModelHelpers } from "../model/ModelHelpers"; import { SETTINGS_X_BINS, SETTINGS_Y_BINS, SETTINGS_SAMPLE_SIZE } from "../model/binRanges/VisualBinRangeHelper"; import { AttributeTransformationModel } from "../core/attribute/AttributeTransformationModel"; -import { Main } from "../../views/Main"; import { BaseOperation } from "./BaseOperation"; +import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; export class HistogramOperation extends BaseOperation { @@ -83,7 +83,7 @@ export class HistogramOperation extends BaseOperation { let [perBinAggregateParameters, globalAggregateParameters] = this.GetAggregateParameters(this.X, this.Y, this.V); return new HistogramOperationParameters({ enableBrushComputation: true, - adapterName: Main.Instance.ActiveSchema!.displayName, + adapterName: CurrentUserUtils.ActiveSchema!.displayName, filter: this.FilterString, brushes: this.BrushString, binningParameters: [ModelHelpers.GetBinningParameters(this.X, SETTINGS_X_BINS, this.QRange ? this.QRange.minValue : undefined, this.QRange ? this.QRange.maxValue : undefined), diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index cb49bc4e6..6534cb4f7 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -55,7 +55,6 @@ export class Main extends React.Component { @observable private mainfreeform?: Document; @observable public pwidth: number = 0; @observable public pheight: number = 0; - @observable ActiveSchema: Schema | undefined; private _northstarColumns: Document[] = []; @computed private get mainContainer(): Document | undefined { @@ -339,8 +338,8 @@ export class Main extends React.Component { @action SetNorthstarCatalog(ctlog: Catalog) { if (ctlog && ctlog.schemas) { - this.ActiveSchema = ArrayUtil.FirstOrDefault(ctlog.schemas!, (s: Schema) => s.displayName === "mimic"); - this._northstarColumns = this.GetAllNorthstarColumnAttributes().map(a => Documents.HistogramDocument({ width: 200, height: 200, title: a.displayName! })); + CurrentUserUtils.ActiveSchema = ArrayUtil.FirstOrDefault(ctlog.schemas!, (s: Schema) => s.displayName === "mimic"); + this._northstarColumns = CurrentUserUtils.GetAllNorthstarColumnAttributes().map(a => Documents.HistogramDocument({ width: 200, height: 200, title: a.displayName! })); } } async initializeNorthstar(): Promise { @@ -355,22 +354,6 @@ export class Main extends React.Component { let cat = Gateway.Instance.ClearCatalog(); cat.then(async () => this.SetNorthstarCatalog(await Gateway.Instance.GetCatalog())); } - public GetAllNorthstarColumnAttributes() { - if (!this.ActiveSchema || !this.ActiveSchema.rootAttributeGroup) { - return []; - } - const recurs = (attrs: Attribute[], g: AttributeGroup) => { - if (g.attributes) { - attrs.push.apply(attrs, g.attributes); - if (g.attributeGroups) { - g.attributeGroups.forEach(ng => recurs(attrs, ng)); - } - } - }; - const allAttributes: Attribute[] = new Array(); - recurs(allAttributes, this.ActiveSchema.rootAttributeGroup); - return allAttributes; - } } Documents.initProtos().then(() => { diff --git a/src/client/views/nodes/HistogramBox.tsx b/src/client/views/nodes/HistogramBox.tsx index 223fdf0d8..cc43899c1 100644 --- a/src/client/views/nodes/HistogramBox.tsx +++ b/src/client/views/nodes/HistogramBox.tsx @@ -4,11 +4,11 @@ import { FieldView, FieldViewProps } from './FieldView'; import "./VideoBox.scss"; import { observable, reaction } from "mobx"; import { HistogramOperation } from "../../northstar/operations/HistogramOperation"; -import { Main } from "../Main"; import { ColumnAttributeModel } from "../../northstar/core/attribute/AttributeModel"; import { AttributeTransformationModel } from "../../northstar/core/attribute/AttributeTransformationModel"; import { AggregateFunction, HistogramResult, DoubleValueAggregateResult } from "../../northstar/model/idea/idea"; import { ModelHelpers } from "../../northstar/model/ModelHelpers"; +import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; @observer export class HistogramBox extends React.Component { @@ -23,7 +23,7 @@ export class HistogramBox extends React.Component { _histoOp?: HistogramOperation; componentDidMount() { - Main.Instance.GetAllNorthstarColumnAttributes().map(a => { + CurrentUserUtils.GetAllNorthstarColumnAttributes().map(a => { if (a.displayName == this.props.doc.Title) { var atmod = new ColumnAttributeModel(a); this._histoOp = new HistogramOperation(new AttributeTransformationModel(atmod, AggregateFunction.None), diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 3291c671c..055e4cc97 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -7,13 +7,15 @@ import { Document } from "../../../fields/Document"; import { KeyStore } from "../../../fields/KeyStore"; import { ListField } from "../../../fields/ListField"; import { Documents } from "../../../client/documents/Documents"; +import { Schema, Attribute, AttributeGroup } from "../../../client/northstar/model/idea/idea"; export class CurrentUserUtils { private static curr_email: string; private static curr_id: string; private static user_document: Document; - //TODO tfs: this should be temporary... + //TODO tfs: these should be temporary... private static mainDocId: string | undefined; + private static activeSchema: Schema | undefined; public static get email(): string { return this.curr_email; @@ -35,6 +37,29 @@ export class CurrentUserUtils { this.mainDocId = id; } + public static get ActiveSchema(): Schema | undefined { + return this.activeSchema; + } + public static GetAllNorthstarColumnAttributes() { + if (!this.ActiveSchema || !this.ActiveSchema.rootAttributeGroup) { + return []; + } + const recurs = (attrs: Attribute[], g: AttributeGroup) => { + if (g.attributes) { + attrs.push.apply(attrs, g.attributes); + if (g.attributeGroups) { + g.attributeGroups.forEach(ng => recurs(attrs, ng)); + } + } + }; + const allAttributes: Attribute[] = new Array(); + recurs(allAttributes, this.ActiveSchema.rootAttributeGroup); + return allAttributes; + } + + public static set ActiveSchema(id: Schema | undefined) { + this.activeSchema = id; + } private static createUserDocument(id: string): Document { let doc = new Document(id); -- cgit v1.2.3-70-g09d2 From 8335f0ba0b780a0ed0619e52076f051f122e4865 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 26 Mar 2019 12:37:26 -0400 Subject: added HistogramField --- package.json | 1 + src/client/documents/Documents.ts | 9 ++-- src/client/northstar/core/filter/FilterModel.ts | 21 ++++---- .../northstar/core/filter/IBaseFilterConsumer.ts | 2 + .../northstar/operations/HistogramOperation.ts | 42 ++++++++------- src/client/views/Main.tsx | 22 +++++++- src/client/views/nodes/HistogramBox.tsx | 60 ++++++++++++++-------- src/fields/HistogramField.ts | 59 +++++++++++++++++++++ src/fields/KeyStore.ts | 2 +- src/server/Message.ts | 2 +- src/server/ServerUtil.ts | 3 ++ .../authentication/models/current_user_utils.ts | 3 ++ 12 files changed, 166 insertions(+), 60 deletions(-) create mode 100644 src/fields/HistogramField.ts (limited to 'src/server/authentication/models') diff --git a/package.json b/package.json index 4f75d139d..27b3eead1 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,7 @@ "bluebird": "^3.5.3", "body-parser": "^1.18.3", "bootstrap": "^4.3.1", + "class-transformer": "^0.2.0", "connect-flash": "^0.1.1", "connect-mongo": "^2.0.3", "cookie-parser": "^1.4.4", diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index bc0a18d50..1d23b8c2c 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -24,6 +24,8 @@ import { VideoBox } from "../views/nodes/VideoBox"; import { WebBox } from "../views/nodes/WebBox"; import { HistogramBox } from "../views/nodes/HistogramBox"; import { FieldView } from "../views/nodes/FieldView"; +import { HistogramField } from "../../fields/HistogramField"; +import { HistogramOperation } from "../northstar/operations/HistogramOperation"; export interface DocumentOptions { x?: number; @@ -42,6 +44,7 @@ export interface DocumentOptions { layoutKeys?: Key[]; viewType?: number; backgroundColor?: string; + northstarSchema?: string; } export namespace Documents { @@ -85,6 +88,7 @@ export namespace Documents { if (options.ink !== undefined) { doc.Set(KeyStore.Ink, new InkField(options.ink)); } if (options.layout !== undefined) { doc.SetText(KeyStore.Layout, options.layout); } if (options.layoutKeys !== undefined) { doc.Set(KeyStore.LayoutKeys, new ListField(options.layoutKeys)); } + if (options.northstarSchema !== undefined) { doc.SetText(KeyStore.NorthstarSchema, options.northstarSchema); } return doc; } @@ -120,7 +124,6 @@ export namespace Documents { } function GetHistogramPrototype(): Document { if (!histoProto) { - histoProto = setupPrototypeOptions(histoProtoId, "HISTO PROTO", CollectionView.LayoutString("AnnotationsKey"), { x: 0, y: 0, width: 300, height: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] }); histoProto.SetText(KeyStore.BackgroundLayout, HistogramBox.LayoutString()); @@ -189,8 +192,8 @@ export namespace Documents { return assignToDelegate(SetInstanceOptions(GetAudioPrototype(), options, [new URL(url), AudioField]), options); } - export function HistogramDocument(options: DocumentOptions = {}) { - return assignToDelegate(SetInstanceOptions(GetHistogramPrototype(), options, ["", TextField]).MakeDelegate(), options); + export function HistogramDocument(histoOp: HistogramOperation, options: DocumentOptions = {}, id?: string) { + return assignToDelegate(SetInstanceOptions(GetHistogramPrototype(), options, [histoOp, HistogramField], id).MakeDelegate(), options); } export function TextDocument(options: DocumentOptions = {}) { return assignToDelegate(SetInstanceOptions(GetTextPrototype(), options, ["", TextField]).MakeDelegate(), options); diff --git a/src/client/northstar/core/filter/FilterModel.ts b/src/client/northstar/core/filter/FilterModel.ts index 3c4cfc4a7..01bf2a809 100644 --- a/src/client/northstar/core/filter/FilterModel.ts +++ b/src/client/northstar/core/filter/FilterModel.ts @@ -1,5 +1,8 @@ import { ValueComparison } from "./ValueComparision"; import { Utils } from "../../utils/Utils"; +import { IBaseFilterProvider } from "./IBaseFilterProvider"; +import { BaseOperation } from "../../operations/BaseOperation"; +import { FilterOperand } from "./FilterOperand"; export class FilterModel { public ValueComparisons: ValueComparison[]; @@ -36,20 +39,20 @@ export class FilterModel { return ret; } - // public static GetFilterModelsRecursive(filterGraphNode: GraphNode, - // visitedFilterProviders: Set>, filterModels: FilterModel[], isFirst: boolean): string { + // public static GetFilterModelsRecursive(baseOperation: BaseOperation, + // visitedFilterProviders: Set, filterModels: FilterModel[], isFirst: boolean): string { // let ret = ""; - // if (Utils.isBaseFilterProvider(filterGraphNode.Data)) { - // visitedFilterProviders.add(filterGraphNode); - // let filtered = filterGraphNode.Data.FilterModels.filter(fm => fm && fm.ValueComparisons.length > 0); + // if (Utils.isBaseFilterProvider(baseOperation)) { + // visitedFilterProviders.add(baseOperation); + // let filtered = baseOperation.FilterModels.filter(fm => fm && fm.ValueComparisons.length > 0); // if (!isFirst && filtered.length > 0) { // filterModels.push(...filtered); - // ret = "(" + filterGraphNode.Data.FilterModels.filter(fm => fm != null).map(fm => fm.ToPythonString()).join(" || ") + ")"; + // ret = "(" + baseOperation.FilterModels.filter(fm => fm != null).map(fm => fm.ToPythonString()).join(" || ") + ")"; // } // } - // if (Utils.isBaseFilterConsumer(filterGraphNode.Data) && filterGraphNode.Links != null) { + // if (Utils.isBaseFilterConsumer(baseOperation) && baseOperation.Links) { // let children = new Array(); - // let linkedGraphNodes = filterGraphNode.Links.get(LinkType.Filter); + // let linkedGraphNodes = baseOperation.Links.get(LinkType.Filter); // if (linkedGraphNodes != null) { // for (let i = 0; i < linkedGraphNodes.length; i++) { // let linkVm = linkedGraphNodes[i].Data; @@ -66,7 +69,7 @@ export class FilterModel { // } // } - // let childrenJoined = children.join(filterGraphNode.Data.FilterOperand === FilterOperand.AND ? " && " : " || "); + // let childrenJoined = children.join(baseOperation.FilterOperand === FilterOperand.AND ? " && " : " || "); // if (children.length > 0) { // if (ret !== "") { // ret = "(" + ret + " && (" + childrenJoined + "))"; diff --git a/src/client/northstar/core/filter/IBaseFilterConsumer.ts b/src/client/northstar/core/filter/IBaseFilterConsumer.ts index e687acb8a..3eb32b6db 100644 --- a/src/client/northstar/core/filter/IBaseFilterConsumer.ts +++ b/src/client/northstar/core/filter/IBaseFilterConsumer.ts @@ -1,8 +1,10 @@ import { FilterOperand } from '../filter/FilterOperand' import { IEquatable } from '../../utils/IEquatable' +import { IBaseFilterProvider } from './IBaseFilterProvider'; export interface IBaseFilterConsumer extends IEquatable { FilterOperand: FilterOperand; + Links: IBaseFilterProvider[]; } export function instanceOfIBaseFilterConsumer(object: any): object is IBaseFilterConsumer { diff --git a/src/client/northstar/operations/HistogramOperation.ts b/src/client/northstar/operations/HistogramOperation.ts index cf2571285..0c38679e5 100644 --- a/src/client/northstar/operations/HistogramOperation.ts +++ b/src/client/northstar/operations/HistogramOperation.ts @@ -9,9 +9,15 @@ import { BaseOperation } from "./BaseOperation"; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; import { FilterModel } from "../core/filter/FilterModel"; import { BrushLinkModel } from "../core/brusher/BrushLinkModel"; +import { IBaseFilterConsumer } from "../core/filter/IBaseFilterConsumer"; +import { FilterOperand } from "../core/filter/FilterOperand"; +import { IBaseFilterProvider } from "../core/filter/IBaseFilterProvider"; +import { AttributeModel, ColumnAttributeModel } from "../core/attribute/AttributeModel"; -export class HistogramOperation extends BaseOperation { +export class HistogramOperation extends BaseOperation implements IBaseFilterConsumer, IBaseFilterProvider { + @observable public FilterOperand: FilterOperand = FilterOperand.AND; + @observable public Links: IBaseFilterProvider[] = []; @observable public BrushColors: number[] = []; @observable public Normalization: number = -1; @observable public FilterModels: FilterModel[] = []; @@ -21,12 +27,18 @@ export class HistogramOperation extends BaseOperation { @observable public BrusherModels: BrushLinkModel[] = []; @observable public BrushableModels: BrushLinkModel[] = []; - constructor(x: AttributeTransformationModel, y: AttributeTransformationModel, v: AttributeTransformationModel) { + public static Empty = new HistogramOperation(new AttributeTransformationModel(new ColumnAttributeModel(new Attribute())), new AttributeTransformationModel(new ColumnAttributeModel(new Attribute())), new AttributeTransformationModel(new ColumnAttributeModel(new Attribute()))); + + Equals(other: Object): boolean { + throw new Error("Method not implemented."); + } + + constructor(x: AttributeTransformationModel, y: AttributeTransformationModel, v: AttributeTransformationModel, normalized?: number) { super(); this.X = x; this.Y = y; this.V = v; - reaction(() => this.createOperationParamsCache, () => this.Update()); + this.Normalization = normalized ? normalized : -1; } @computed.struct @@ -47,20 +59,11 @@ export class HistogramOperation extends BaseOperation { } - @computed.struct - public get SelectionString() { - return ""; - // let filterModels = new Array(); - // let rdg = MainManager.Instance.MainViewModel.FilterReverseDependencyGraph; - // let graphNode: GraphNode; - // if (rdg.has(this.TypedViewModel)) { - // graphNode = MainManager.Instance.MainViewModel.FilterReverseDependencyGraph.get(this.TypedViewModel); - // } - // else { - // graphNode = new GraphNode(this.TypedViewModel); - // } - // return FilterModel.GetFilterModelsRecursive(graphNode, new Set>(), filterModels, false); - } + // @computed.struct + // public get SelectionString() { + // let filterModels = new Array(); + // return FilterModel.GetFilterModelsRecursive(this, new Set>(), filterModels, false); + // } GetAggregateParameters(histoX: AttributeTransformationModel, histoY: AttributeTransformationModel, histoValue: AttributeTransformationModel) { let allAttributes = new Array(histoX, histoY, histoValue); @@ -79,11 +82,6 @@ export class HistogramOperation extends BaseOperation { return [perBinAggregateParameters, globalAggregateParameters]; } - @computed - get createOperationParamsCache() { - return this.CreateOperationParameters(); - } - public QRange: QuantitativeBinRange | undefined; public CreateOperationParameters(): HistogramOperationParameters | undefined { diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 6534cb4f7..3e0e02f42 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -44,10 +44,13 @@ import { CurrentUserUtils } from '../../server/authentication/models/current_use import { Field, Opt, FieldWaiting } from '../../fields/Field'; import { ListField } from '../../fields/ListField'; import { Gateway, Settings } from '../northstar/manager/Gateway'; -import { Catalog, Schema, Attribute, AttributeGroup } from '../northstar/model/idea/idea'; +import { Catalog, Schema, Attribute, AttributeGroup, AggregateFunction } from '../northstar/model/idea/idea'; import { ArrayUtil } from '../northstar/utils/ArrayUtil'; import '../northstar/model/ModelExtensions' import '../northstar/utils/Extensions' +import { HistogramOperation } from '../northstar/operations/HistogramOperation'; +import { AttributeTransformationModel } from '../northstar/core/attribute/AttributeTransformationModel'; +import { ColumnAttributeModel } from '../northstar/core/attribute/AttributeModel'; @observer export class Main extends React.Component { @@ -339,7 +342,22 @@ export class Main extends React.Component { @action SetNorthstarCatalog(ctlog: Catalog) { if (ctlog && ctlog.schemas) { CurrentUserUtils.ActiveSchema = ArrayUtil.FirstOrDefault(ctlog.schemas!, (s: Schema) => s.displayName === "mimic"); - this._northstarColumns = CurrentUserUtils.GetAllNorthstarColumnAttributes().map(a => Documents.HistogramDocument({ width: 200, height: 200, title: a.displayName! })); + CurrentUserUtils.GetAllNorthstarColumnAttributes().map(attr => { + Server.GetField(attr.displayName!, action((field: Opt) => { + if (field instanceof Document) { + this._northstarColumns.push(field); + } else { + var atmod = new ColumnAttributeModel(attr); + let histoOp = new HistogramOperation( + new AttributeTransformationModel(atmod, AggregateFunction.None), + new AttributeTransformationModel(atmod, AggregateFunction.Count), + new AttributeTransformationModel(atmod, AggregateFunction.Count)); + this._northstarColumns.push(Documents.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName!, northstarSchema: CurrentUserUtils.ActiveSchema!.displayName! }, attr.displayName!)); + } + })); + }) + console.log("Activating schema " + CurrentUserUtils.ActiveSchema!.displayName!) + CurrentUserUtils.ActiveSchemaName = CurrentUserUtils.ActiveSchema!.displayName!; } } async initializeNorthstar(): Promise { diff --git a/src/client/views/nodes/HistogramBox.tsx b/src/client/views/nodes/HistogramBox.tsx index 675bf30b2..73d3f3bc3 100644 --- a/src/client/views/nodes/HistogramBox.tsx +++ b/src/client/views/nodes/HistogramBox.tsx @@ -1,17 +1,17 @@ import React = require("react") -import { computed, observable, reaction, runInAction } from "mobx"; +import { computed, observable, reaction, runInAction, action, observe } from "mobx"; import { observer } from "mobx-react"; import Measure from "react-measure"; import { Dictionary } from "typescript-collections"; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; import { Utils as DashUtils } from '../../../Utils'; -import { ColumnAttributeModel } from "../../northstar/core/attribute/AttributeModel"; +import { ColumnAttributeModel, AttributeModel } from "../../northstar/core/attribute/AttributeModel"; import { AttributeTransformationModel } from "../../northstar/core/attribute/AttributeTransformationModel"; import { FilterModel } from '../../northstar/core/filter/FilterModel'; import { NominalVisualBinRange } from "../../northstar/model/binRanges/NominalVisualBinRange"; import { ChartType, VisualBinRange } from '../../northstar/model/binRanges/VisualBinRange'; import { VisualBinRangeHelper } from "../../northstar/model/binRanges/VisualBinRangeHelper"; -import { AggregateBinRange, AggregateFunction, Bin, Brush, DoubleValueAggregateResult, HistogramResult, MarginAggregateParameters, MarginAggregateResult } from "../../northstar/model/idea/idea"; +import { AggregateBinRange, AggregateFunction, Bin, Brush, DoubleValueAggregateResult, HistogramResult, MarginAggregateParameters, MarginAggregateResult, Attribute, BinRange } from "../../northstar/model/idea/idea"; import { ModelHelpers } from "../../northstar/model/ModelHelpers"; import { HistogramOperation } from "../../northstar/operations/HistogramOperation"; import { ArrayUtil } from "../../northstar/utils/ArrayUtil"; @@ -22,6 +22,10 @@ import { StyleConstants } from "../../northstar/utils/StyleContants"; import { FieldView, FieldViewProps } from './FieldView'; import "./HistogramBox.scss"; import { KeyStore } from "../../../fields/KeyStore"; +import { ListField } from "../../../fields/ListField"; +import { Document } from "../../../fields/Document" +import { HistogramField } from "../../../fields/HistogramField"; +import { FieldWaiting, Opt } from "../../../fields/Field"; @observer export class HistogramBox extends React.Component { @@ -38,37 +42,26 @@ export class HistogramBox extends React.Component { @observable public ChartType: ChartType = ChartType.VerticalBar; public HitTargets: Dictionary = new Dictionary(); - constructor(props: FieldViewProps) { - super(props); - } - @computed get xaxislines() { return this.renderGridLinesAndLabels(0); } @computed get yaxislines() { return this.renderGridLinesAndLabels(1); } componentDidMount() { - reaction(() => CurrentUserUtils.GetAllNorthstarColumnAttributes().filter(a => a.displayName == this.props.doc.Title), - (columnAttrs) => columnAttrs.map(a => { - var atmod = new ColumnAttributeModel(a); - this.HistoOp = new HistogramOperation(new AttributeTransformationModel(atmod, AggregateFunction.None), - new AttributeTransformationModel(atmod, AggregateFunction.Count), - new AttributeTransformationModel(atmod, AggregateFunction.Count)); - this.HistoOp.Update(); - }) - , { fireImmediately: true }); + reaction(() => [CurrentUserUtils.ActiveSchemaName, this.props.doc.GetText(KeyStore.NorthstarSchema, "?")], + () => CurrentUserUtils.ActiveSchemaName == this.props.doc.GetText(KeyStore.NorthstarSchema, "?") && this.activateHistogramOperation(), + { fireImmediately: true }); reaction(() => [this.VisualBinRanges && this.VisualBinRanges.slice(), this._panelHeight, this._panelWidth], () => this.SizeConverter = new SizeConverter({ x: this._panelWidth, y: this._panelHeight }, this.VisualBinRanges, Math.PI / 4)); - reaction(() => [this.HistoOp && this.HistoOp.Result], - () => { - if (!this.HistoOp || !(this.HistoOp.Result instanceof HistogramResult) || !this.HistoOp.Result.binRanges) + reaction(() => this.HistoOp && this.HistoOp.Result instanceof HistogramResult ? this.HistoOp.Result.binRanges : undefined, + (binRanges: BinRange[] | undefined) => { + if (!binRanges || !this.HistoOp || !(this.HistoOp!.Result instanceof HistogramResult)) return; - let binRanges = this.HistoOp.Result.binRanges; this.ChartType = binRanges[0] instanceof AggregateBinRange ? (binRanges[1] instanceof AggregateBinRange ? ChartType.SinglePoint : ChartType.HorizontalBar) : binRanges[1] instanceof AggregateBinRange ? ChartType.VerticalBar : ChartType.HeatMap; this.VisualBinRanges.length = 0; - this.VisualBinRanges.push(VisualBinRangeHelper.GetVisualBinRange(this.HistoOp.Result.binRanges[0], this.HistoOp.Result, this.HistoOp.X, this.ChartType)); - this.VisualBinRanges.push(VisualBinRangeHelper.GetVisualBinRange(this.HistoOp.Result.binRanges[1], this.HistoOp.Result, this.HistoOp.Y, this.ChartType)); + this.VisualBinRanges.push(VisualBinRangeHelper.GetVisualBinRange(binRanges[0], this.HistoOp.Result, this.HistoOp.X, this.ChartType)); + this.VisualBinRanges.push(VisualBinRangeHelper.GetVisualBinRange(binRanges[1], this.HistoOp.Result, this.HistoOp.Y, this.ChartType)); if (!this.HistoOp.Result.isEmpty) { this.MaxValue = Number.MIN_VALUE; @@ -89,6 +82,29 @@ export class HistogramBox extends React.Component { ); } + @computed + get createOperationParamsCache() { + return this.HistoOp!.CreateOperationParameters(); + } + + activateHistogramOperation() { + this.props.doc.GetTAsync(this.props.fieldKey, HistogramField).then((histoOp: Opt) => { + if (histoOp) { + runInAction(() => this.HistoOp = histoOp.Data); + this.HistoOp!.Update(); + reaction(() => this.createOperationParamsCache, () => this.HistoOp!.Update()); + reaction(() => this.props.doc.GetList(KeyStore.LinkedFromDocs, []), + () => { + let linkFrom: Document[] = this.props.doc.GetData(KeyStore.LinkedFromDocs, ListField, []); + this.HistoOp!.Links.length = 0; + linkFrom.map(l => this.HistoOp!.Links.push(l.GetData(KeyStore.Data, HistogramField, HistogramOperation.Empty))); + }, + { fireImmediately: true } + ); + } + }) + } + drawLine(xFrom: number, yFrom: number, width: number, height: number) { return
    ; } diff --git a/src/fields/HistogramField.ts b/src/fields/HistogramField.ts new file mode 100644 index 000000000..bb0014ab3 --- /dev/null +++ b/src/fields/HistogramField.ts @@ -0,0 +1,59 @@ +import { BasicField } from "./BasicField"; +import { Field, FieldId } from "./Field"; +import { Types } from "../server/Message"; +import { HistogramOperation } from "../client/northstar/operations/HistogramOperation"; +import { action } from "mobx"; +import { AttributeTransformationModel } from "../client/northstar/core/attribute/AttributeTransformationModel"; +import { ColumnAttributeModel } from "../client/northstar/core/attribute/AttributeModel"; +import { CurrentUserUtils } from "../server/authentication/models/current_user_utils"; + + +export class HistogramField extends BasicField { + constructor(data?: HistogramOperation, id?: FieldId, save: boolean = true) { + super(data ? data : HistogramOperation.Empty, save, id); + } + + toString(): string { + return JSON.stringify(this.Data); + } + + Copy(): Field { + return new HistogramField(this.Data); + } + + ToScriptString(): string { + return `new HistogramField("${this.Data}")`; + } + + ToJson(): { type: Types, data: string, _id: string } { + return { + type: Types.HistogramOp, + data: JSON.stringify(this.Data), + _id: this.Id + } + } + + @action + static FromJson(id: string, data: any): HistogramField { + let jp = JSON.parse(data); + let X: AttributeTransformationModel | undefined; + let Y: AttributeTransformationModel | undefined; + let V: AttributeTransformationModel | undefined; + + CurrentUserUtils.GetAllNorthstarColumnAttributes().map(attr => { + if (attr.displayName == jp.X.AttributeModel.Attribute.DisplayName) { + X = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.X.AggregateFunction); + } + if (attr.displayName == jp.Y.AttributeModel.Attribute.DisplayName) { + Y = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.Y.AggregateFunction); + } + if (attr.displayName == jp.V.AttributeModel.Attribute.DisplayName) { + V = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.V.AggregateFunction); + } + }); + if (X && Y && V) { + return new HistogramField(new HistogramOperation(X, Y, V, jp.Normalization), id, false); + } + return new HistogramField(HistogramOperation.Empty, id, false); + } +} \ No newline at end of file diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts index f9684b212..20e8cd930 100644 --- a/src/fields/KeyStore.ts +++ b/src/fields/KeyStore.ts @@ -29,7 +29,6 @@ export namespace KeyStore { export const Caption = new Key("Caption"); export const ActiveFrame = new Key("ActiveFrame"); export const ActiveWorkspace = new Key("ActiveWorkspace"); - export const ActiveDB = new Key("ActiveDB"); export const DocumentText = new Key("DocumentText"); export const LinkedToDocs = new Key("LinkedToDocs"); export const LinkedFromDocs = new Key("LinkedFromDocs"); @@ -46,4 +45,5 @@ export namespace KeyStore { export const Archives = new Key("Archives"); export const Updated = new Key("Updated"); export const Workspaces = new Key("Workspaces"); + export const NorthstarSchema = new Key("NorthstarSchema"); } diff --git a/src/server/Message.ts b/src/server/Message.ts index a2d1ab829..05ae0f19a 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, Tuple + Number, List, Key, Image, Web, Document, Text, RichText, DocumentReference, Html, Video, Audio, Ink, PDF, Tuple, HistogramOp } export class DocumentTransfer implements Transferable { diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts index f10f82deb..f958df04b 100644 --- a/src/server/ServerUtil.ts +++ b/src/server/ServerUtil.ts @@ -17,6 +17,7 @@ import { VideoField } from '../fields/VideoField'; import { InkField } from '../fields/InkField'; import { PDFField } from '../fields/PDFField'; import { TupleField } from '../fields/TupleField'; +import { HistogramField } from '../fields/HistogramField'; @@ -50,6 +51,8 @@ export class ServerUtils { return new Key(data, id, false) case Types.Image: return new ImageField(new URL(data), id, false) + case Types.HistogramOp: + return HistogramField.FromJson(id, data); case Types.PDF: return new PDFField(new URL(data), id, false) case Types.List: diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 055e4cc97..4b42e40b6 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -8,6 +8,7 @@ import { KeyStore } from "../../../fields/KeyStore"; import { ListField } from "../../../fields/ListField"; import { Documents } from "../../../client/documents/Documents"; import { Schema, Attribute, AttributeGroup } from "../../../client/northstar/model/idea/idea"; +import { observable, computed, action } from "mobx"; export class CurrentUserUtils { private static curr_email: string; @@ -16,6 +17,7 @@ export class CurrentUserUtils { //TODO tfs: these should be temporary... private static mainDocId: string | undefined; private static activeSchema: Schema | undefined; + @observable public static ActiveSchemaName: string = ""; public static get email(): string { return this.curr_email; @@ -37,6 +39,7 @@ export class CurrentUserUtils { this.mainDocId = id; } + public static get ActiveSchema(): Schema | undefined { return this.activeSchema; } -- cgit v1.2.3-70-g09d2 From 1bd678851632bbbb302363574eb5e3e19dc343e9 Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 29 Mar 2019 13:18:35 -0400 Subject: reorganized and mostly working northstar histograms. --- src/client/documents/Documents.ts | 13 +- src/client/northstar/core/filter/FilterModel.ts | 2 +- src/client/northstar/dash-fields/HistogramField.ts | 64 ++++ src/client/northstar/dash-nodes/HistogramBox.scss | 34 ++ src/client/northstar/dash-nodes/HistogramBox.tsx | 161 ++++++++++ .../dash-nodes/HistogramBoxPrimitives.scss | 26 ++ .../dash-nodes/HistogramBoxPrimitives.tsx | 341 +++++++++++++++++++++ .../dash-nodes/HistogramLabelPrimitives.scss | 13 + .../dash-nodes/HistogramLabelPrimitives.tsx | 78 +++++ src/client/northstar/model/ModelHelpers.ts | 18 +- .../model/binRanges/VisualBinRangeHelper.ts | 6 +- src/client/northstar/operations/BaseOperation.ts | 4 +- .../northstar/operations/HistogramOperation.ts | 13 +- src/client/northstar/utils/SizeConverter.ts | 6 +- src/client/views/Main.tsx | 39 +-- src/client/views/nodes/DocumentContentsView.tsx | 5 +- src/client/views/nodes/DocumentView.tsx | 3 - src/client/views/nodes/HistogramBox.scss | 22 -- src/client/views/nodes/HistogramBox.tsx | 105 ------- src/client/views/nodes/HistogramBoxPrimitives.scss | 25 -- src/client/views/nodes/HistogramBoxPrimitives.tsx | 336 -------------------- .../views/nodes/HistogramLabelPrimitives.scss | 12 - .../views/nodes/HistogramLabelPrimitives.tsx | 78 ----- src/fields/HistogramField.ts | 59 ---- src/fields/KeyStore.ts | 1 - src/server/ServerUtil.ts | 3 +- .../authentication/models/current_user_utils.ts | 27 +- 27 files changed, 789 insertions(+), 705 deletions(-) create mode 100644 src/client/northstar/dash-fields/HistogramField.ts create mode 100644 src/client/northstar/dash-nodes/HistogramBox.scss create mode 100644 src/client/northstar/dash-nodes/HistogramBox.tsx create mode 100644 src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss create mode 100644 src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx create mode 100644 src/client/northstar/dash-nodes/HistogramLabelPrimitives.scss create mode 100644 src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx delete mode 100644 src/client/views/nodes/HistogramBox.scss delete mode 100644 src/client/views/nodes/HistogramBox.tsx delete mode 100644 src/client/views/nodes/HistogramBoxPrimitives.scss delete mode 100644 src/client/views/nodes/HistogramBoxPrimitives.tsx delete mode 100644 src/client/views/nodes/HistogramLabelPrimitives.scss delete mode 100644 src/client/views/nodes/HistogramLabelPrimitives.tsx delete mode 100644 src/fields/HistogramField.ts (limited to 'src/server/authentication/models') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 837dfe815..663ccae61 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1,6 +1,6 @@ import { AudioField } from "../../fields/AudioField"; import { Document } from "../../fields/Document"; -import { Field, FieldWaiting } from "../../fields/Field"; +import { Field } from "../../fields/Field"; import { HtmlField } from "../../fields/HtmlField"; import { ImageField } from "../../fields/ImageField"; import { InkField, StrokeData } from "../../fields/InkField"; @@ -11,6 +11,9 @@ import { PDFField } from "../../fields/PDFField"; import { TextField } from "../../fields/TextField"; import { VideoField } from "../../fields/VideoField"; import { WebField } from "../../fields/WebField"; +import { HistogramField } from "../northstar/dash-fields/HistogramField"; +import { HistogramBox } from "../northstar/dash-nodes/HistogramBox"; +import { HistogramOperation } from "../northstar/operations/HistogramOperation"; import { Server } from "../Server"; import { CollectionPDFView } from "../views/collections/CollectionPDFView"; import { CollectionVideoView } from "../views/collections/CollectionVideoView"; @@ -22,10 +25,6 @@ import { KeyValueBox } from "../views/nodes/KeyValueBox"; import { PDFBox } from "../views/nodes/PDFBox"; import { VideoBox } from "../views/nodes/VideoBox"; import { WebBox } from "../views/nodes/WebBox"; -import { HistogramBox } from "../views/nodes/HistogramBox"; -import { FieldView } from "../views/nodes/FieldView"; -import { HistogramField } from "../../fields/HistogramField"; -import { HistogramOperation } from "../northstar/operations/HistogramOperation"; export interface DocumentOptions { x?: number; @@ -44,7 +43,6 @@ export interface DocumentOptions { layoutKeys?: Key[]; viewType?: number; backgroundColor?: string; - northstarSchema?: string; } export namespace Documents { @@ -88,7 +86,6 @@ export namespace Documents { if (options.ink !== undefined) { doc.Set(KeyStore.Ink, new InkField(options.ink)); } if (options.layout !== undefined) { doc.SetText(KeyStore.Layout, options.layout); } if (options.layoutKeys !== undefined) { doc.Set(KeyStore.LayoutKeys, new ListField(options.layoutKeys)); } - if (options.northstarSchema !== undefined) { doc.SetText(KeyStore.NorthstarSchema, options.northstarSchema); } return doc; } @@ -126,7 +123,7 @@ export namespace Documents { function GetHistogramPrototype(): Document { if (!histoProto) { histoProto = setupPrototypeOptions(histoProtoId, "HISTO PROTO", CollectionView.LayoutString("AnnotationsKey"), - { x: 0, y: 0, width: 300, height: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] }); + { x: 0, y: 0, width: 300, height: 300, backgroundColor: "black", layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] }); histoProto.SetText(KeyStore.BackgroundLayout, HistogramBox.LayoutString()); } return histoProto; diff --git a/src/client/northstar/core/filter/FilterModel.ts b/src/client/northstar/core/filter/FilterModel.ts index bc7938947..aee99d2b6 100644 --- a/src/client/northstar/core/filter/FilterModel.ts +++ b/src/client/northstar/core/filter/FilterModel.ts @@ -2,10 +2,10 @@ import { ValueComparison } from "./ValueComparision"; import { Utils } from "../../utils/Utils"; import { IBaseFilterProvider } from "./IBaseFilterProvider"; import { FilterOperand } from "./FilterOperand"; -import { HistogramField } from "../../../../fields/HistogramField"; import { KeyStore } from "../../../../fields/KeyStore"; import { FieldWaiting } from "../../../../fields/Field"; import { Document } from "../../../../fields/Document"; +import { HistogramField } from "../../dash-fields/HistogramField"; export class FilterModel { public ValueComparisons: ValueComparison[]; diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts new file mode 100644 index 000000000..00912c595 --- /dev/null +++ b/src/client/northstar/dash-fields/HistogramField.ts @@ -0,0 +1,64 @@ +import { BasicField } from "../../../fields/BasicField"; +import { Field, FieldId } from "../../../fields/Field"; +import { Types } from "../../../server/Message"; +import { HistogramOperation } from "../../../client/northstar/operations/HistogramOperation"; +import { action } from "mobx"; +import { AttributeTransformationModel } from "../../../client/northstar/core/attribute/AttributeTransformationModel"; +import { ColumnAttributeModel } from "../../../client/northstar/core/attribute/AttributeModel"; +import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; +import { ArrayUtil } from "../utils/ArrayUtil"; +import { Schema } from "../model/idea/idea"; + + +export class HistogramField extends BasicField { + constructor(data?: HistogramOperation, id?: FieldId, save: boolean = true) { + super(data ? data : HistogramOperation.Empty, save, id); + } + + toString(): string { + return JSON.stringify(this.Data); + } + + Copy(): Field { + return new HistogramField(this.Data); + } + + ToScriptString(): string { + return `new HistogramField("${this.Data}")`; + } + + ToJson(): { type: Types, data: string, _id: string } { + return { + type: Types.HistogramOp, + data: JSON.stringify(this.Data), + _id: this.Id + } + } + + @action + static FromJson(id: string, data: any): HistogramField { + let jp = JSON.parse(data); + let X: AttributeTransformationModel | undefined; + let Y: AttributeTransformationModel | undefined; + let V: AttributeTransformationModel | undefined; + + let schema = CurrentUserUtils.GetNorthstarSchema(jp.SchemaName); + if (schema) { + CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => { + if (attr.displayName == jp.X.AttributeModel.Attribute.DisplayName) { + X = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.X.AggregateFunction); + } + if (attr.displayName == jp.Y.AttributeModel.Attribute.DisplayName) { + Y = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.Y.AggregateFunction); + } + if (attr.displayName == jp.V.AttributeModel.Attribute.DisplayName) { + V = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.V.AggregateFunction); + } + }); + if (X && Y && V) { + return new HistogramField(new HistogramOperation(jp.SchemaName, X, Y, V, jp.Normalization), id, false); + } + } + return new HistogramField(HistogramOperation.Empty, id, false); + } +} \ No newline at end of file diff --git a/src/client/northstar/dash-nodes/HistogramBox.scss b/src/client/northstar/dash-nodes/HistogramBox.scss new file mode 100644 index 000000000..b11840a65 --- /dev/null +++ b/src/client/northstar/dash-nodes/HistogramBox.scss @@ -0,0 +1,34 @@ +.histogrambox-container { + padding: 0vw; + position: absolute; + text-align: center; + width: 100%; + height: 100%; + background: black; + } + .histogrambox-xaxislabel { + position:absolute; + width:100%; + text-align: center; + bottom:0; + background: lightgray; + font-size: 14; + font-weight: bold; + } + .histogrambox-yaxislabel { + position:absolute; + height:100%; + width: 25px; + bottom:0; + background: lightgray; + } + .histogrambox-yaxislabel-text { + position:absolute; + transform-origin: left; + transform: rotate(-90deg); + text-align: center; + font-size: 14; + font-weight: bold; + bottom: calc(50% - 25px); + } + \ No newline at end of file diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx new file mode 100644 index 000000000..9f8c2cfd0 --- /dev/null +++ b/src/client/northstar/dash-nodes/HistogramBox.tsx @@ -0,0 +1,161 @@ +import React = require("react") +import { action, computed, observable, reaction, runInAction } from "mobx"; +import { observer } from "mobx-react"; +import Measure from "react-measure"; +import { Dictionary } from "typescript-collections"; +import { FieldWaiting, Opt } from "../../../fields/Field"; +import { KeyStore } from "../../../fields/KeyStore"; +import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; +import { FilterModel } from '../../northstar/core/filter/FilterModel'; +import { ChartType, VisualBinRange } from '../../northstar/model/binRanges/VisualBinRange'; +import { VisualBinRangeHelper } from "../../northstar/model/binRanges/VisualBinRangeHelper"; +import { AggregateBinRange, BinRange, DoubleValueAggregateResult, HistogramResult, Catalog } from "../../northstar/model/idea/idea"; +import { ModelHelpers } from "../../northstar/model/ModelHelpers"; +import { HistogramOperation } from "../../northstar/operations/HistogramOperation"; +import { PIXIRectangle } from "../../northstar/utils/MathUtil"; +import { SizeConverter } from "../../northstar/utils/SizeConverter"; +import { DragManager } from "../../util/DragManager"; +import { FieldView, FieldViewProps } from "../../views/nodes/FieldView"; +import { HistogramField } from "../dash-fields/HistogramField"; +import "../utils/Extensions" +import "./HistogramBox.scss"; +import { HistogramBoxPrimitives } from './HistogramBoxPrimitives'; +import { HistogramLabelPrimitives } from "./HistogramLabelPrimitives"; + +export interface HistogramPrimitivesProps { + HistoBox: HistogramBox; +} + +@observer +export class HistogramBox extends React.Component { + public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(HistogramBox, fieldStr) } + private _dropXRef = React.createRef(); + private _dropYRef = React.createRef(); + private _dropXDisposer?: DragManager.DragDropDisposer; + private _dropYDisposer?: DragManager.DragDropDisposer; + + @observable public PanelWidth: number = 100; + @observable public PanelHeight: number = 100; + @observable public HistoOp: HistogramOperation = HistogramOperation.Empty; + @observable public VisualBinRanges: VisualBinRange[] = []; + @observable public ValueRange: number[] = []; + @observable public SizeConverter: SizeConverter = new SizeConverter(); + + @computed get createOperationParamsCache() { return this.HistoOp.CreateOperationParameters(); } + @computed get HistogramResult() { return this.HistoOp ? this.HistoOp.Result as HistogramResult : undefined; } + @computed get BinRanges() { return this.HistogramResult ? this.HistogramResult.binRanges : undefined; } + @computed get ChartType() { + return !this.BinRanges ? ChartType.SinglePoint : this.BinRanges[0] instanceof AggregateBinRange ? + (this.BinRanges[1] instanceof AggregateBinRange ? ChartType.SinglePoint : ChartType.HorizontalBar) : + this.BinRanges[1] instanceof AggregateBinRange ? ChartType.VerticalBar : ChartType.HeatMap; + } + + constructor(props: FieldViewProps) { + super(props); + } + + @action + dropX = (e: Event, de: DragManager.DropEvent) => { + if (de.data instanceof DragManager.DocumentDragData) { + let h = de.data.draggedDocument.GetT(KeyStore.Data, HistogramField); + if (h && h != FieldWaiting) { + this.HistoOp.X = h.Data.X; + } + e.stopPropagation(); + e.preventDefault(); + } + } + @action + dropY = (e: Event, de: DragManager.DropEvent) => { + if (de.data instanceof DragManager.DocumentDragData) { + let h = de.data.draggedDocument.GetT(KeyStore.Data, HistogramField); + if (h && h != FieldWaiting) { + this.HistoOp.Y = h.Data.X; + } + e.stopPropagation(); + e.preventDefault(); + } + } + + componentDidMount() { + if (this._dropXRef.current) { + this._dropXDisposer = DragManager.MakeDropTarget(this._dropXRef.current, { handlers: { drop: this.dropX.bind(this) } }); + } + if (this._dropYRef.current) { + this._dropYDisposer = DragManager.MakeDropTarget(this._dropYRef.current, { handlers: { drop: this.dropY.bind(this) } }); + } + reaction(() => CurrentUserUtils.NorthstarDBCatalog, (catalog?: Catalog) => this.activateHistogramOperation(catalog), { fireImmediately: true }); + reaction(() => [this.VisualBinRanges && this.VisualBinRanges.slice()], () => this.SizeConverter.SetVisualBinRanges(this.VisualBinRanges)); + reaction(() => [this.PanelHeight, this.PanelWidth], () => this.SizeConverter.SetIsSmall(this.PanelWidth < 40 && this.PanelHeight < 40)) + reaction(() => this.HistogramResult ? this.HistogramResult.binRanges : undefined, + (binRanges: BinRange[] | undefined) => { + if (binRanges) { + this.VisualBinRanges.splice(0, this.VisualBinRanges.length, ...binRanges.map((br, ind) => + VisualBinRangeHelper.GetVisualBinRange(this.HistoOp.Schema!.distinctAttributeParameters, br, this.HistogramResult!, ind ? this.HistoOp.Y : this.HistoOp.X, this.ChartType))); + + let valueAggregateKey = ModelHelpers.CreateAggregateKey(this.HistoOp.Schema!.distinctAttributeParameters, this.HistoOp.V, this.HistogramResult!, ModelHelpers.AllBrushIndex(this.HistogramResult!)); + this.ValueRange = Object.values(this.HistogramResult!.bins!).reduce((prev, cur) => { + let value = ModelHelpers.GetAggregateResult(cur, valueAggregateKey) as DoubleValueAggregateResult; + return value && value.hasResult ? [Math.min(prev[0], value.result!), Math.max(prev[1], value.result!)] : prev; + }, [Number.MAX_VALUE, Number.MIN_VALUE]); + } + }); + } + + @action + xLabelPointerDown = (e: React.PointerEvent) => { + + } + + componentWillUnmount() { + if (this._dropXDisposer) + this._dropXDisposer(); + if (this._dropYDisposer) + this._dropYDisposer(); + } + + activateHistogramOperation(catalog?: Catalog) { + if (catalog) { + this.props.doc.GetTAsync(this.props.fieldKey, HistogramField).then((histoOp: Opt) => runInAction(() => { + this.HistoOp = histoOp ? histoOp.Data : HistogramOperation.Empty; + if (this.HistoOp != HistogramOperation.Empty) { + reaction(() => this.props.doc.GetList(KeyStore.LinkedFromDocs, []), docs => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true }); + reaction(() => this.createOperationParamsCache, () => this.HistoOp.Update(), { fireImmediately: true }); + } + })); + } + } + render() { + let labelY = this.HistoOp && this.HistoOp.Y ? this.HistoOp.Y.PresentedName : "<...>"; + let labelX = this.HistoOp && this.HistoOp.X ? this.HistoOp.X.PresentedName : "<...>"; + var h = this.props.isTopMost ? this.PanelHeight : this.props.doc.GetNumber(KeyStore.Height, 0); + var w = this.props.isTopMost ? this.PanelWidth : this.props.doc.GetNumber(KeyStore.Width, 0); + let loff = this.SizeConverter.LeftOffset; + let toff = this.SizeConverter.TopOffset; + let roff = this.SizeConverter.RightOffset; + let boff = this.SizeConverter.BottomOffset; + return ( + runInAction(() => { this.PanelWidth = r.entry.width; this.PanelHeight = r.entry.height })}> + {({ measureRef }) => +
    +
    + + {labelY} + +
    +
    + + +
    +
    {labelX}
    +
    + } +
    + ) + } +} + diff --git a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss new file mode 100644 index 000000000..9d42219cc --- /dev/null +++ b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss @@ -0,0 +1,26 @@ +.histogramboxprimitives-border { + border: 3px; + border-style: solid; + border-color: white; + pointer-events: none; + position: absolute; +} +.histogramboxprimitives-bar { + position: absolute; + border: 1px; + border-style: solid; + border-color: #282828; + pointer-events: all; +} + +.histogramboxprimitives-placer { + position: absolute; + pointer-events: none; + width: 100%; + height: 100%; +} +.histogramboxprimitives-line { + position: absolute; + background: darkGray; + opacity: 0.4; +} \ No newline at end of file diff --git a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx new file mode 100644 index 000000000..d2f1be4fd --- /dev/null +++ b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx @@ -0,0 +1,341 @@ +import React = require("react") +import { computed, observable, runInAction } from "mobx"; +import { observer } from "mobx-react"; +import { Utils as DashUtils } from '../../../Utils'; +import { AttributeTransformationModel } from "../../northstar/core/attribute/AttributeTransformationModel"; +import { FilterModel } from "../../northstar/core/filter/FilterModel"; +import { ChartType } from '../../northstar/model/binRanges/VisualBinRange'; +import { AggregateFunction, Bin, Brush, HistogramResult, MarginAggregateParameters, MarginAggregateResult } from "../../northstar/model/idea/idea"; +import { ModelHelpers } from "../../northstar/model/ModelHelpers"; +import { ArrayUtil } from "../../northstar/utils/ArrayUtil"; +import { LABColor } from '../../northstar/utils/LABcolor'; +import { PIXIRectangle } from "../../northstar/utils/MathUtil"; +import { StyleConstants } from "../../northstar/utils/StyleContants"; +import { HistogramBox, HistogramPrimitivesProps } from "./HistogramBox"; +import "./HistogramBoxPrimitives.scss"; + + +@observer +export class HistogramBoxPrimitives extends React.Component { + private get histoOp() { return this.props.HistoBox.HistoOp; } + private get renderDimension() { return this.props.HistoBox.SizeConverter.RenderDimension; } + @observable _selectedPrims: HistogramBinPrimitive[] = []; + @computed get xaxislines() { return this.renderGridLinesAndLabels(0); } + @computed get yaxislines() { return this.renderGridLinesAndLabels(1); } + @computed get selectedPrimitives() { return this._selectedPrims.map(bp => this.drawRect(bp.Rect, bp.BarAxis, undefined, "border")); } + @computed get binPrimitives() { + let histoResult = this.props.HistoBox.HistogramResult; + if (!histoResult || !histoResult.bins || !this.props.HistoBox.VisualBinRanges.length) + return (null); + let allBrushIndex = ModelHelpers.AllBrushIndex(histoResult); + return Object.keys(histoResult.bins).reduce((prims, key) => { + let drawPrims = new HistogramBinPrimitiveCollection(histoResult!.bins![key], this.props.HistoBox); + + let toggle = this.getSelectionToggle(drawPrims.BinPrimitives, allBrushIndex, + ModelHelpers.GetBinFilterModel(histoResult!.bins![key], allBrushIndex, histoResult!, this.histoOp.X, this.histoOp.Y)); + drawPrims.BinPrimitives.filter(bp => bp.DataValue && bp.BrushIndex !== allBrushIndex).map(bp => + prims.push(...[{ r: bp.Rect, c: bp.Color }, { r: bp.MarginRect, c: StyleConstants.MARGIN_BARS_COLOR }].map(pair => this.drawRect(pair.r, bp.BarAxis, pair.c, "bar", toggle)))); + return prims; + }, [] as JSX.Element[]); + } + + private getSelectionToggle(binPrimitives: HistogramBinPrimitive[], allBrushIndex: number, filterModel: FilterModel) { + let allBrushPrim = ArrayUtil.FirstOrDefault(binPrimitives, bp => bp.BrushIndex == allBrushIndex); + return !allBrushPrim ? () => { } : () => runInAction(() => { + if (ArrayUtil.Contains(this.histoOp.FilterModels, filterModel)) { + this._selectedPrims.splice(this._selectedPrims.indexOf(allBrushPrim!), 1); + this.histoOp.RemoveFilterModels([filterModel]); + } + else { + this._selectedPrims.push(allBrushPrim!); + this.histoOp.AddFilterModels([filterModel]); + } + }) + } + + private renderGridLinesAndLabels(axis: number) { + if (!this.props.HistoBox.SizeConverter.Initialized) + return (null); + let labels = this.props.HistoBox.VisualBinRanges[axis].GetLabels(); + return labels.reduce((prims, binLabel, i) => { + let r = this.props.HistoBox.SizeConverter.DataToScreenRange(binLabel.minValue!, binLabel.maxValue!, axis); + prims.push(this.drawLine(r.xFrom, r.yFrom, axis == 0 ? 0 : r.xTo - r.xFrom, axis == 0 ? r.yTo - r.yFrom : 0)); + if (i == labels.length - 1) + prims.push(this.drawLine(axis == 0 ? r.xTo : r.xFrom, axis == 0 ? r.yFrom : r.yTo, axis == 0 ? 0 : r.xTo - r.xFrom, axis == 0 ? r.yTo - r.yFrom : 0)); + return prims; + }, [] as JSX.Element[]); + } + + drawEntity(xFrom: number, yFrom: number, entity: JSX.Element) { + let transXpercent = xFrom / this.renderDimension * 100; + let transYpercent = yFrom / this.renderDimension * 100; + return (
    + {entity} +
    ); + } + drawLine(xFrom: number, yFrom: number, width: number, height: number) { + if (height < 0) { + yFrom += height; + height = -height; + } + if (width < 0) { + xFrom += width; + width = -width; + } + let trans2Xpercent = width == 0 ? `1px` : `${(xFrom + width) / this.renderDimension * 100}%`; + let trans2Ypercent = height == 0 ? `1px` : `${(yFrom + height) / this.renderDimension * 100}%`; + let line = (
    ); + return this.drawEntity(xFrom, yFrom, line); + } + + drawRect(r: PIXIRectangle, barAxis: number, color: number | undefined, classExt: string, tapHandler: () => void = () => { }) { + if (r.height < 0) { + r.y += r.height; + r.height = -r.height; + } + if (r.width < 0) { + r.x += r.width; + r.width = -r.width; + } + let widthPercent = r.width / this.renderDimension * 100; + let heightPercent = r.height / this.renderDimension * 100; + let rect = (
    { if (e.button == 0) tapHandler() }} + style={{ + borderBottomStyle: barAxis == 1 ? "none" : "solid", + borderLeftStyle: barAxis == 0 ? "none" : "solid", + width: `${widthPercent}%`, + height: `${heightPercent}%`, + background: color ? `${LABColor.RGBtoHexString(color)}` : "" + }} + />); + return this.drawEntity(r.x, r.y, rect); + } + render() { + return
    + {this.xaxislines} + {this.yaxislines} + {this.binPrimitives} + {this.selectedPrimitives} +
    + } +} + +class HistogramBinPrimitive { + constructor(init?: Partial) { + Object.assign(this, init); + } + public DataValue: number = 0; + public Rect: PIXIRectangle = PIXIRectangle.EMPTY; + public MarginRect: PIXIRectangle = PIXIRectangle.EMPTY; + public MarginPercentage: number = 0; + public Color: number = StyleConstants.WARNING_COLOR; + public Opacity: number = 1; + public BrushIndex: number = 0; + public BarAxis: number = -1; +} + +export class HistogramBinPrimitiveCollection { + private static TOLERANCE: number = 0.0001; + + private _histoBox: HistogramBox; + private get histoOp() { return this._histoBox.HistoOp; } + private get histoResult() { return this.histoOp.Result as HistogramResult; } + private get sizeConverter() { return this._histoBox.SizeConverter!; } + public BinPrimitives: Array = new Array(); + public HitGeom: PIXIRectangle = PIXIRectangle.EMPTY; + + constructor(bin: Bin, histoBox: HistogramBox) { + this._histoBox = histoBox; + let brushing = this.setupBrushing(bin, this.histoOp.Normalization); // X= 0, Y = 1, V = 2 + + brushing.orderedBrushes.reduce((brushFactorSum, brush) => { + switch (histoBox.ChartType) { + case ChartType.VerticalBar: return this.createVerticalBarChartBinPrimitives(bin, brush, brushing.maxAxis, this.histoOp.Normalization); + case ChartType.HorizontalBar: return this.createHorizontalBarChartBinPrimitives(bin, brush, brushing.maxAxis, this.histoOp.Normalization); + case ChartType.SinglePoint: return this.createSinglePointChartBinPrimitives(bin, brush); + case ChartType.HeatMap: return this.createHeatmapBinPrimitives(bin, brush, brushFactorSum); + } + }, 0); + + // adjust brush rects (stacking or not) + var allBrushIndex = ModelHelpers.AllBrushIndex(this.histoResult); + var filteredBinPrims = this.BinPrimitives.filter(b => b.BrushIndex != allBrushIndex && b.DataValue != 0.0); + filteredBinPrims.reduce((sum, fbp) => { + if (histoBox.ChartType == ChartType.VerticalBar) { + if (this.histoOp.X.AggregateFunction == AggregateFunction.Count) { + fbp.Rect = new PIXIRectangle(fbp.Rect.x, fbp.Rect.y - sum, fbp.Rect.width, fbp.Rect.height); + fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x, fbp.MarginRect.y - sum, fbp.MarginRect.width, fbp.MarginRect.height); + return sum + fbp.Rect.height; + } + if (this.histoOp.Y.AggregateFunction == AggregateFunction.Avg) { + var w = fbp.Rect.width / 2.0; + fbp.Rect = new PIXIRectangle(fbp.Rect.x + sum, fbp.Rect.y, fbp.Rect.width / filteredBinPrims.length, fbp.Rect.height); + fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x - w + sum + (fbp.Rect.width / 2.0), fbp.MarginRect.y, fbp.MarginRect.width, fbp.MarginRect.height); + return sum + fbp.Rect.width; + } + } + else if (histoBox.ChartType == ChartType.HorizontalBar) { + if (this.histoOp.X.AggregateFunction == AggregateFunction.Count) { + fbp.Rect = new PIXIRectangle(fbp.Rect.x + sum, fbp.Rect.y, fbp.Rect.width, fbp.Rect.height); + fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x + sum, fbp.MarginRect.y, fbp.MarginRect.width, fbp.MarginRect.height); + return sum + fbp.Rect.width; + } + if (this.histoOp.X.AggregateFunction == AggregateFunction.Avg) { + var h = fbp.Rect.height / 2.0; + fbp.Rect = new PIXIRectangle(fbp.Rect.x, fbp.Rect.y + sum, fbp.Rect.width, fbp.Rect.height / filteredBinPrims.length); + fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x, fbp.MarginRect.y - h + sum + (fbp.Rect.height / 2.0), fbp.MarginRect.width, fbp.MarginRect.height); + return sum + fbp.Rect.height; + } + } + return 0; + }, 0); + this.BinPrimitives = this.BinPrimitives.reverse(); + var f = this.BinPrimitives.filter(b => b.BrushIndex == allBrushIndex); + this.HitGeom = f.length > 0 ? f[0].Rect : PIXIRectangle.EMPTY; + } + private setupBrushing(bin: Bin, normalization: number) { + var overlapBrushIndex = ModelHelpers.OverlapBrushIndex(this.histoResult); + var orderedBrushes = [this.histoResult.brushes![0], this.histoResult.brushes![overlapBrushIndex]]; + this.histoResult.brushes!.map(brush => brush.brushIndex != 0 && brush.brushIndex != overlapBrushIndex && orderedBrushes.push(brush)); + return { + orderedBrushes, + maxAxis: orderedBrushes.reduce((prev, Brush) => { + let aggResult = this.histoOp.getValue(normalization, bin, this.histoResult, Brush.brushIndex!); + return aggResult != undefined && aggResult > prev ? aggResult : prev; + }, Number.MIN_VALUE) + }; + } + private createHeatmapBinPrimitives(bin: Bin, brush: Brush, brushFactorSum: number): number { + + let unNormalizedValue = this.histoOp.getValue(2, bin, this.histoResult, brush.brushIndex!); + if (unNormalizedValue == undefined) + return brushFactorSum; + + var normalizedValue = (unNormalizedValue - this._histoBox.ValueRange[0]) / (Math.abs((this._histoBox.ValueRange[1] - this._histoBox.ValueRange[0])) < HistogramBinPrimitiveCollection.TOLERANCE ? + unNormalizedValue : this._histoBox.ValueRange[1] - this._histoBox.ValueRange[0]); + + let allUnNormalizedValue = this.histoOp.getValue(2, bin, this.histoResult, ModelHelpers.AllBrushIndex(this.histoResult)) + + // bcz: are these calls needed? + let [xFrom, xTo] = this.sizeConverter.DataToScreenXAxisRange(this._histoBox.VisualBinRanges, 0, bin); + let [yFrom, yTo] = this.sizeConverter.DataToScreenYAxisRange(this._histoBox.VisualBinRanges, 1, bin); + + var returnBrushFactorSum = brushFactorSum; + if (allUnNormalizedValue != undefined) { + var brushFactor = (unNormalizedValue / allUnNormalizedValue); + returnBrushFactorSum += brushFactor; + returnBrushFactorSum = Math.min(returnBrushFactorSum, 1.0); + + var tempRect = new PIXIRectangle(xFrom, yTo, xTo - xFrom, yFrom - yTo); + var ratio = (tempRect.width / tempRect.height); + var newHeight = Math.sqrt((1.0 / ratio) * ((tempRect.width * tempRect.height) * returnBrushFactorSum)); + var newWidth = newHeight * ratio; + + xFrom = (tempRect.x + (tempRect.width - newWidth) / 2.0); + yTo = (tempRect.y + (tempRect.height - newHeight) / 2.0); + xTo = (xFrom + newWidth); + yFrom = (yTo + newHeight); + } + var alpha = 0.0; + var color = this.baseColorFromBrush(brush); + var lerpColor = LABColor.Lerp( + LABColor.FromColor(StyleConstants.MIN_VALUE_COLOR), + LABColor.FromColor(color), + (alpha + Math.pow(normalizedValue, 1.0 / 3.0) * (1.0 - alpha))); + var dataColor = LABColor.ToColor(lerpColor); + + this.createBinPrimitive(-1, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, dataColor, 1, unNormalizedValue); + return returnBrushFactorSum; + } + + private createSinglePointChartBinPrimitives(bin: Bin, brush: Brush): number { + let unNormalizedValue = this._histoBox.HistoOp.getValue(2, bin, this.histoResult, brush.brushIndex!); + if (unNormalizedValue != undefined) { + let [xFrom, xTo] = this.sizeConverter.DataToScreenPointRange(0, bin, ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, this.histoOp.X, this.histoResult, brush.brushIndex!)); + let [yFrom, yTo] = this.sizeConverter.DataToScreenPointRange(1, bin, ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, this.histoOp.Y, this.histoResult, brush.brushIndex!)); + + if (xFrom != undefined && yFrom != undefined && xTo != undefined && yTo != undefined) + this.createBinPrimitive(-1, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, this.baseColorFromBrush(brush), 1, unNormalizedValue); + } + return 0; + } + + private createVerticalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number): number { + let dataValue = this.histoOp.getValue(1, bin, this.histoResult, brush.brushIndex!); + if (dataValue != undefined) { + let [yFrom, yValue, yTo] = this.sizeConverter.DataToScreenNormalizedRange(dataValue, normalization, 1, binBrushMaxAxis); + let [xFrom, xTo] = this.sizeConverter.DataToScreenXAxisRange(this._histoBox.VisualBinRanges, 0, bin); + + var yMarginAbsolute = this.getMargin(bin, brush, this.histoOp.Y); + var marginRect = new PIXIRectangle(xFrom + (xTo - xFrom) / 2.0 - 1, + this.sizeConverter.DataToScreenY(yValue + yMarginAbsolute), 2, + this.sizeConverter.DataToScreenY(yValue - yMarginAbsolute) - this.sizeConverter.DataToScreenY(yValue + yMarginAbsolute)); + + this.createBinPrimitive(1, brush, marginRect, 0, xFrom, xTo, yFrom, yTo, + this.baseColorFromBrush(brush), normalization != 0 ? 1 : 0.6 * binBrushMaxAxis / this.sizeConverter.DataRanges[1] + 0.4, dataValue); + } + return 0; + } + + private createHorizontalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number): number { + let dataValue = this.histoOp.getValue(0, bin, this.histoResult, brush.brushIndex!); + if (dataValue != undefined) { + let [xFrom, xValue, xTo] = this.sizeConverter.DataToScreenNormalizedRange(dataValue, normalization, 0, binBrushMaxAxis); + let [yFrom, yTo] = this.sizeConverter.DataToScreenYAxisRange(this._histoBox.VisualBinRanges, 1, bin); + + var xMarginAbsolute = this.sizeConverter.IsSmall ? 0 : this.getMargin(bin, brush, this.histoOp.X); + var marginRect = new PIXIRectangle(this.sizeConverter.DataToScreenX(xValue - xMarginAbsolute), + yTo + (yFrom - yTo) / 2.0 - 1, + this.sizeConverter.DataToScreenX(xValue + xMarginAbsolute) - this.sizeConverter.DataToScreenX(xValue - xMarginAbsolute), + 2.0); + + this.createBinPrimitive(0, brush, marginRect, 0, xFrom, xTo, yFrom, yTo, + this.baseColorFromBrush(brush), normalization != 1 ? 1 : 0.6 * binBrushMaxAxis / this.sizeConverter.DataRanges[0] + 0.4, dataValue); + } + return 0; + } + + + private getMargin(bin: Bin, brush: Brush, axis: AttributeTransformationModel) { + var marginParams = new MarginAggregateParameters(); + marginParams.aggregateFunction = axis.AggregateFunction; + var marginAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, axis, this.histoResult, brush.brushIndex!, marginParams); + var marginResult = ModelHelpers.GetAggregateResult(bin, marginAggregateKey) as MarginAggregateResult; + return !marginResult ? 0 : marginResult.absolutMargin!; + } + + private createBinPrimitive(barAxis: number, brush: Brush, marginRect: PIXIRectangle, + marginPercentage: number, xFrom: number, xTo: number, yFrom: number, yTo: number, color: number, opacity: number, dataValue: number) { + var binPrimitive = new HistogramBinPrimitive( + { + Rect: new PIXIRectangle(xFrom, yTo, xTo - xFrom, yFrom - yTo), + MarginRect: marginRect, + MarginPercentage: marginPercentage, + BrushIndex: brush.brushIndex, + Color: color, + Opacity: opacity, + DataValue: dataValue, + BarAxis: barAxis + }); + this.BinPrimitives.push(binPrimitive); + } + + private baseColorFromBrush(brush: Brush): number { + if (brush.brushIndex == ModelHelpers.RestBrushIndex(this.histoResult)) { + return StyleConstants.HIGHLIGHT_COLOR; + } + else if (brush.brushIndex == ModelHelpers.OverlapBrushIndex(this.histoResult)) { + return StyleConstants.OVERLAP_COLOR; + } + else if (brush.brushIndex == ModelHelpers.AllBrushIndex(this.histoResult)) { + return 0x00ff00; + } + else if (this.histoOp.BrushColors.length > 0) { + return this.histoOp.BrushColors[brush.brushIndex! % this.histoOp.BrushColors.length]; + } + return StyleConstants.HIGHLIGHT_COLOR; + } +} \ No newline at end of file diff --git a/src/client/northstar/dash-nodes/HistogramLabelPrimitives.scss b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.scss new file mode 100644 index 000000000..304d33771 --- /dev/null +++ b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.scss @@ -0,0 +1,13 @@ + + .histogramLabelPrimitives-gridlabel { + position:absolute; + transform-origin: left top; + font-size: 11; + color:white; + } + .histogramLabelPrimitives-placer { + position:absolute; + width:100%; + height:100%; + pointer-events: none; + } \ No newline at end of file diff --git a/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx new file mode 100644 index 000000000..45b23874d --- /dev/null +++ b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx @@ -0,0 +1,78 @@ +import React = require("react") +import { action, computed, reaction } from "mobx"; +import { observer } from "mobx-react"; +import { Utils as DashUtils } from '../../../Utils'; +import { NominalVisualBinRange } from "../model/binRanges/NominalVisualBinRange"; +import "../utils/Extensions"; +import { StyleConstants } from "../utils/StyleContants"; +import { HistogramBox, HistogramPrimitivesProps } from "./HistogramBox"; +import "./HistogramLabelPrimitives.scss"; + +@observer +export class HistogramLabelPrimitives extends React.Component { + componentDidMount() { + reaction(() => [this.props.HistoBox.PanelWidth, this.props.HistoBox.SizeConverter.LeftOffset, this.props.HistoBox.VisualBinRanges.length], + (fields) => HistogramLabelPrimitives.computeLabelAngle(fields[0] as number, fields[1] as number, this.props.HistoBox), { fireImmediately: true }); + } + + @action + static computeLabelAngle(panelWidth: number, leftOffset: number, histoBox: HistogramBox) { + const textWidth = 30; + if (panelWidth > 0 && histoBox.VisualBinRanges.length && histoBox.VisualBinRanges[0] instanceof NominalVisualBinRange) { + let space = (panelWidth - leftOffset * 2) / histoBox.VisualBinRanges[0].GetBins().length; + histoBox.SizeConverter.SetLabelAngle(Math.min(Math.PI / 2, Math.max(Math.PI / 6, textWidth / space * Math.PI / 2))); + } else if (histoBox.SizeConverter.LabelAngle) { + histoBox.SizeConverter.SetLabelAngle(0); + } + } + @computed get xaxislines() { return this.renderGridLinesAndLabels(0); } + @computed get yaxislines() { return this.renderGridLinesAndLabels(1); } + + private renderGridLinesAndLabels(axis: number) { + let sc = this.props.HistoBox.SizeConverter; + let vb = this.props.HistoBox.VisualBinRanges; + if (!vb.length || !sc.Initialized) + return (null); + let dim = (axis == 0 ? this.props.HistoBox.PanelWidth : this.props.HistoBox.PanelHeight) / ((axis == 0 && vb[axis] instanceof NominalVisualBinRange) ? + (12 + 5) : // (FontStyles.AxisLabel.fontSize + 5))); + sc.MaxLabelSizes[axis].coords[axis] + 5); + + let labels = vb[axis].GetLabels(); + return labels.reduce((prims, binLabel, i) => { + let r = sc.DataToScreenRange(binLabel.minValue!, binLabel.maxValue!, axis); + if (i % Math.ceil(labels.length / dim) === 0 && binLabel.label) { + const label = binLabel.label.Truncate(StyleConstants.MAX_CHAR_FOR_HISTOGRAM_LABELS, "..."); + const textHeight = 14; const textWidth = 30; + let xStart = (axis === 0 ? r.xFrom + (r.xTo - r.xFrom) / 2.0 : r.xFrom - 10 - textWidth); + let yStart = (axis === 1 ? r.yFrom - textHeight / 2 : r.yFrom); + + if (axis == 0 && vb[axis] instanceof NominalVisualBinRange) { + let space = (r.xTo - r.xFrom) / sc.RenderDimension * this.props.HistoBox.PanelWidth; + xStart += Math.max(textWidth / 2, (1 - textWidth / space) * textWidth / 2) - textHeight / 2; + } + + let xPercent = axis == 1 ? `${xStart}px` : `${xStart / sc.RenderDimension * 100}%` + let yPercent = axis == 0 ? `${this.props.HistoBox.PanelHeight - sc.BottomOffset - textHeight}px` : `${yStart / sc.RenderDimension * 100}%` + + prims.push( +
    +
    + {label} +
    +
    + ) + } + return prims; + }, [] as JSX.Element[]); + } + + render() { + let xaxislines = this.xaxislines; + let yaxislines = this.yaxislines; + return
    + {xaxislines} + {yaxislines} +
    + } + +} \ No newline at end of file diff --git a/src/client/northstar/model/ModelHelpers.ts b/src/client/northstar/model/ModelHelpers.ts index 8de0bd260..d0711fb69 100644 --- a/src/client/northstar/model/ModelHelpers.ts +++ b/src/client/northstar/model/ModelHelpers.ts @@ -13,11 +13,11 @@ import { CurrentUserUtils } from "../../../server/authentication/models/current_ export class ModelHelpers { - public static CreateAggregateKey(atm: AttributeTransformationModel, histogramResult: HistogramResult, + public static CreateAggregateKey(distinctAttributeParameters: AttributeParameters | undefined, atm: AttributeTransformationModel, histogramResult: HistogramResult, brushIndex: number, aggParameters?: SingleDimensionAggregateParameters): AggregateKey { { if (aggParameters == undefined) { - aggParameters = ModelHelpers.GetAggregateParameter(atm); + aggParameters = ModelHelpers.GetAggregateParameter(distinctAttributeParameters, atm); } else { aggParameters.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel); @@ -34,40 +34,40 @@ export class ModelHelpers { return ArrayUtil.IndexOfWithEqual(histogramResult.aggregateParameters!, aggParameters); } - public static GetAggregateParameter(atm: AttributeTransformationModel): AggregateParameters | undefined { + public static GetAggregateParameter(distinctAttributeParameters: AttributeParameters | undefined, atm: AttributeTransformationModel): AggregateParameters | undefined { var aggParam: AggregateParameters | undefined; if (atm.AggregateFunction === AggregateFunction.Avg) { var avg = new AverageAggregateParameters(); avg.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel); - avg.distinctAttributeParameters = CurrentUserUtils.ActiveSchema!.distinctAttributeParameters; + avg.distinctAttributeParameters = distinctAttributeParameters; aggParam = avg; } else if (atm.AggregateFunction === AggregateFunction.Count) { var cnt = new CountAggregateParameters(); cnt.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel); - cnt.distinctAttributeParameters = CurrentUserUtils.ActiveSchema!.distinctAttributeParameters; + cnt.distinctAttributeParameters = distinctAttributeParameters; aggParam = cnt; } else if (atm.AggregateFunction === AggregateFunction.Sum) { var sum = new SumAggregateParameters(); sum.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel); - sum.distinctAttributeParameters = CurrentUserUtils.ActiveSchema!.distinctAttributeParameters; + sum.distinctAttributeParameters = distinctAttributeParameters; aggParam = sum; } return aggParam; } - public static GetAggregateParametersWithMargins(atms: Array): Array { + public static GetAggregateParametersWithMargins(distinctAttributeParameters: AttributeParameters | undefined, atms: Array): Array { var aggregateParameters = new Array(); atms.forEach(agg => { - var aggParams = ModelHelpers.GetAggregateParameter(agg); + var aggParams = ModelHelpers.GetAggregateParameter(distinctAttributeParameters, agg); if (aggParams) { aggregateParameters.push(aggParams); var margin = new MarginAggregateParameters() margin.aggregateFunction = agg.AggregateFunction; margin.attributeParameters = ModelHelpers.GetAttributeParameters(agg.AttributeModel); - margin.distinctAttributeParameters = CurrentUserUtils.ActiveSchema!.distinctAttributeParameters; + margin.distinctAttributeParameters = distinctAttributeParameters; aggregateParameters.push(margin); } }); diff --git a/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts b/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts index 9eae39800..53d585bb4 100644 --- a/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts +++ b/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts @@ -1,4 +1,4 @@ -import { BinRange, NominalBinRange, QuantitativeBinRange, Exception, AlphabeticBinRange, DateTimeBinRange, AggregateBinRange, DoubleValueAggregateResult, HistogramResult } from "../idea/idea"; +import { BinRange, NominalBinRange, QuantitativeBinRange, Exception, AlphabeticBinRange, DateTimeBinRange, AggregateBinRange, DoubleValueAggregateResult, HistogramResult, AttributeParameters } from "../idea/idea"; import { VisualBinRange, ChartType } from "./VisualBinRange"; import { NominalVisualBinRange } from "./NominalVisualBinRange"; import { QuantitativeVisualBinRange } from "./QuantitativeVisualBinRange"; @@ -30,13 +30,13 @@ export class VisualBinRangeHelper { throw new Exception() } - public static GetVisualBinRange(dataBinRange: BinRange, histoResult: HistogramResult, attr: AttributeTransformationModel, chartType: ChartType): VisualBinRange { + public static GetVisualBinRange(distinctAttributeParameters: AttributeParameters | undefined, dataBinRange: BinRange, histoResult: HistogramResult, attr: AttributeTransformationModel, chartType: ChartType): VisualBinRange { if (!(dataBinRange instanceof AggregateBinRange)) { return VisualBinRangeHelper.GetNonAggregateVisualBinRange(dataBinRange); } else { - var aggregateKey = ModelHelpers.CreateAggregateKey(attr, histoResult, ModelHelpers.AllBrushIndex(histoResult)); + var aggregateKey = ModelHelpers.CreateAggregateKey(distinctAttributeParameters, attr, histoResult, ModelHelpers.AllBrushIndex(histoResult)); var minValue = Number.MAX_VALUE; var maxValue = Number.MIN_VALUE; for (var b = 0; b < histoResult.brushes!.length; b++) { diff --git a/src/client/northstar/operations/BaseOperation.ts b/src/client/northstar/operations/BaseOperation.ts index 7db6fcb91..94e0849af 100644 --- a/src/client/northstar/operations/BaseOperation.ts +++ b/src/client/northstar/operations/BaseOperation.ts @@ -10,9 +10,9 @@ export abstract class BaseOperation { @observable public Error: string = ""; @observable public OverridingFilters: FilterModel[] = []; - @observable public Result: Result | undefined; + @observable public Result?: Result = undefined; @observable public ComputationStarted: boolean = false; - public OperationReference: OperationReference | undefined = undefined; + public OperationReference?: OperationReference = undefined; private static _nextId = 0; public RequestSalt: string = ""; diff --git a/src/client/northstar/operations/HistogramOperation.ts b/src/client/northstar/operations/HistogramOperation.ts index 6c7288d42..8a0f648f6 100644 --- a/src/client/northstar/operations/HistogramOperation.ts +++ b/src/client/northstar/operations/HistogramOperation.ts @@ -27,6 +27,8 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons @observable public V: AttributeTransformationModel; @observable public BrusherModels: BrushLinkModel[] = []; @observable public BrushableModels: BrushLinkModel[] = []; + @observable public SchemaName: string; + @computed public get Schema() { return CurrentUserUtils.GetNorthstarSchema(this.SchemaName); } @action public AddFilterModels(filterModels: FilterModel[]): void { @@ -38,23 +40,24 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons } public getValue(axis: number, bin: Bin, result: HistogramResult, brushIndex: number) { - var aggregateKey = ModelHelpers.CreateAggregateKey(axis == 0 ? this.X : axis == 1 ? this.Y : this.V, result, brushIndex); + var aggregateKey = ModelHelpers.CreateAggregateKey(this.Schema!.distinctAttributeParameters, axis == 0 ? this.X : axis == 1 ? this.Y : this.V, result, brushIndex); let dataValue = ModelHelpers.GetAggregateResult(bin, aggregateKey) as DoubleValueAggregateResult; return dataValue != null && dataValue.hasResult ? dataValue.result : undefined; } - public static Empty = new HistogramOperation(new AttributeTransformationModel(new ColumnAttributeModel(new Attribute())), new AttributeTransformationModel(new ColumnAttributeModel(new Attribute())), new AttributeTransformationModel(new ColumnAttributeModel(new Attribute()))); + public static Empty = new HistogramOperation("-empty schema-", new AttributeTransformationModel(new ColumnAttributeModel(new Attribute())), new AttributeTransformationModel(new ColumnAttributeModel(new Attribute())), new AttributeTransformationModel(new ColumnAttributeModel(new Attribute()))); Equals(other: Object): boolean { throw new Error("Method not implemented."); } - constructor(x: AttributeTransformationModel, y: AttributeTransformationModel, v: AttributeTransformationModel, normalized?: number) { + constructor(schemaName: string, x: AttributeTransformationModel, y: AttributeTransformationModel, v: AttributeTransformationModel, normalized?: number) { super(); this.X = x; this.Y = y; this.V = v; this.Normalization = normalized ? normalized : -1; + this.SchemaName = schemaName; } @computed @@ -93,7 +96,7 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons allAttributes = ArrayUtil.Distinct(allAttributes.filter(a => a.AggregateFunction !== AggregateFunction.None)); let numericDataTypes = [DataType.Int, DataType.Double, DataType.Float]; - let perBinAggregateParameters: AggregateParameters[] = ModelHelpers.GetAggregateParametersWithMargins(allAttributes); + let perBinAggregateParameters: AggregateParameters[] = ModelHelpers.GetAggregateParametersWithMargins(this.Schema!.distinctAttributeParameters, allAttributes); let globalAggregateParameters: AggregateParameters[] = []; [histoX, histoY] .filter(a => a.AggregateFunction === AggregateFunction.None && ArrayUtil.Contains(numericDataTypes, a.AttributeModel.DataType)) @@ -112,7 +115,7 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons let [perBinAggregateParameters, globalAggregateParameters] = this.GetAggregateParameters(this.X, this.Y, this.V); return new HistogramOperationParameters({ enableBrushComputation: true, - adapterName: CurrentUserUtils.ActiveSchema!.displayName, + adapterName: this.SchemaName, filter: this.FilterString, brushes: this.BrushString, binningParameters: [ModelHelpers.GetBinningParameters(this.X, SETTINGS_X_BINS, this.QRange ? this.QRange.minValue : undefined, this.QRange ? this.QRange.maxValue : undefined), diff --git a/src/client/northstar/utils/SizeConverter.ts b/src/client/northstar/utils/SizeConverter.ts index ffd162a83..30627dfd5 100644 --- a/src/client/northstar/utils/SizeConverter.ts +++ b/src/client/northstar/utils/SizeConverter.ts @@ -68,10 +68,14 @@ export class SizeConverter { return [undefined, undefined]; } - public DataToScreenAxisRange(visualBinRanges: VisualBinRange[], index: number, bin: Bin) { + public DataToScreenXAxisRange(visualBinRanges: VisualBinRange[], index: number, bin: Bin) { var value = visualBinRanges[0].GetValueFromIndex(bin.binIndex!.indices![index]); return [this.DataToScreenX(value), this.DataToScreenX(visualBinRanges[index].AddStep(value))] } + public DataToScreenYAxisRange(visualBinRanges: VisualBinRange[], index: number, bin: Bin) { + var value = visualBinRanges[1].GetValueFromIndex(bin.binIndex!.indices![index]); + return [this.DataToScreenY(value), this.DataToScreenY(visualBinRanges[index].AddStep(value))] + } public DataToScreenX(x: number): number { return ((x - this.DataMins[0]) / this.DataRanges[0]) * this.RenderDimension; diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 09778ac77..2f20b102c 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -58,7 +58,7 @@ export class Main extends React.Component { @observable private mainfreeform?: Document; @observable public pwidth: number = 0; @observable public pheight: number = 0; - private _northstarColumns: Document[] = []; + private _northstarSchemas: Document[] = []; @computed private get mainContainer(): Document | undefined { let doc = this.userDocument.GetT(KeyStore.ActiveWorkspace, Document); @@ -245,7 +245,7 @@ export class Main extends React.Component { let addTextNode = action(() => Documents.TextDocument({ width: 200, height: 200, title: "a text note" })) let addColNode = action(() => Documents.FreeformDocument([], { width: 200, height: 200, title: "a freeform collection" })); let addSchemaNode = action(() => Documents.SchemaDocument([], { width: 200, height: 200, title: "a schema collection" })); - let addTreeNode = action(() => Documents.TreeDocument(this._northstarColumns, { width: 200, height: 200, title: "a tree collection" })); + let addTreeNode = action(() => Documents.TreeDocument(this._northstarSchemas, { width: 100, height: 400, title: "northstar schemas" })); 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" })); @@ -334,24 +334,27 @@ export class Main extends React.Component { // --------------- Northstar hooks ------------- / @action SetNorthstarCatalog(ctlog: Catalog) { + CurrentUserUtils.NorthstarDBCatalog = ctlog; if (ctlog && ctlog.schemas) { - CurrentUserUtils.ActiveSchema = ArrayUtil.FirstOrDefault(ctlog.schemas!, (s: Schema) => s.displayName === "mimic"); - CurrentUserUtils.GetAllNorthstarColumnAttributes().map(attr => { - Server.GetField(attr.displayName!, action((field: Opt) => { - if (field instanceof Document) { - this._northstarColumns.push(field); - } else { - var atmod = new ColumnAttributeModel(attr); - let histoOp = new HistogramOperation( - new AttributeTransformationModel(atmod, AggregateFunction.None), - new AttributeTransformationModel(atmod, AggregateFunction.Count), - new AttributeTransformationModel(atmod, AggregateFunction.Count)); - this._northstarColumns.push(Documents.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName!, northstarSchema: CurrentUserUtils.ActiveSchema!.displayName! }, attr.displayName!)); - } - })); + this._northstarSchemas = ctlog.schemas.map(schema => { + let schemaDoc = Documents.TreeDocument([], { width: 50, height: 100, title: schema.displayName! }); + let schemaDocuments = schemaDoc.GetList(KeyStore.Data, [] as Document[]); + CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => { + Server.GetField(attr.displayName!, action((field: Opt) => { + if (field instanceof Document) { + schemaDocuments.push(field); + } else { + var atmod = new ColumnAttributeModel(attr); + let histoOp = new HistogramOperation(schema!.displayName!, + new AttributeTransformationModel(atmod, AggregateFunction.None), + new AttributeTransformationModel(atmod, AggregateFunction.Count), + new AttributeTransformationModel(atmod, AggregateFunction.Count)); + schemaDocuments.push(Documents.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName! }, attr.displayName!)); + } + })); + }); + return schemaDoc; }) - console.log("Activating schema " + CurrentUserUtils.ActiveSchema!.displayName!) - CurrentUserUtils.ActiveSchemaName = CurrentUserUtils.ActiveSchema!.displayName!; } } async initializeNorthstar(): Promise { diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index b54744337..77551649c 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -19,8 +19,7 @@ import { KeyValueBox } from "./KeyValueBox"; import { PDFBox } from "./PDFBox"; import { VideoBox } from "./VideoBox"; import { WebBox } from "./WebBox"; -import { HistogramBox } from "./HistogramBox"; -import { HistogramBoxPrimitives } from "./HistogramBoxPrimitives"; +import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox"; import React = require("react"); const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? @@ -54,7 +53,7 @@ export class DocumentContentsView extends React.ComponentError loading layout keys

    ; } return { } private dropDisposer?: DragManager.DragDropDisposer; - protected createDropTarget = (ele: HTMLDivElement) => { - - } componentDidMount() { if (this._mainCont.current) { diff --git a/src/client/views/nodes/HistogramBox.scss b/src/client/views/nodes/HistogramBox.scss deleted file mode 100644 index 2660b1b75..000000000 --- a/src/client/views/nodes/HistogramBox.scss +++ /dev/null @@ -1,22 +0,0 @@ -.histogrambox-container { - padding: 0vw; - position: absolute; - text-align: center; - width: 100%; - height: 100%; - } - .histogrambox-xaxislabel { - position:absolute; - width:100%; - text-align: center; - bottom:0; - font-size: 14; - font-weight: bold; - } - - .histogrambox-container { - position:absolute; - width:100%; - height: 100%; - } - \ No newline at end of file diff --git a/src/client/views/nodes/HistogramBox.tsx b/src/client/views/nodes/HistogramBox.tsx deleted file mode 100644 index 3307925a2..000000000 --- a/src/client/views/nodes/HistogramBox.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import React = require("react") -import { computed, observable, reaction, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import Measure from "react-measure"; -import { Dictionary } from "typescript-collections"; -import { Opt } from "../../../fields/Field"; -import { HistogramField } from "../../../fields/HistogramField"; -import { KeyStore } from "../../../fields/KeyStore"; -import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; -import { FilterModel } from '../../northstar/core/filter/FilterModel'; -import { ChartType, VisualBinRange } from '../../northstar/model/binRanges/VisualBinRange'; -import { VisualBinRangeHelper } from "../../northstar/model/binRanges/VisualBinRangeHelper"; -import { AggregateBinRange, BinRange, DoubleValueAggregateResult, HistogramResult } from "../../northstar/model/idea/idea"; -import { ModelHelpers } from "../../northstar/model/ModelHelpers"; -import { HistogramOperation } from "../../northstar/operations/HistogramOperation"; -import { PIXIRectangle } from "../../northstar/utils/MathUtil"; -import { SizeConverter } from "../../northstar/utils/SizeConverter"; -import "./../../northstar/utils/Extensions"; -import { FieldView, FieldViewProps } from './FieldView'; -import "./HistogramBox.scss"; -import { HistogramBoxPrimitives } from './HistogramBoxPrimitives'; -import { HistogramLabelPrimitives } from "./HistogramLabelPrimitives"; - -export interface HistogramPrimitivesProps { - HistoBox: HistogramBox; -} - -@observer -export class HistogramBox extends React.Component { - public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(HistogramBox, fieldStr) } - public HitTargets: Dictionary = new Dictionary(); - - @observable public PanelWidth: number = 100; - @observable public PanelHeight: number = 100; - @observable public HistoOp?: HistogramOperation; - @observable public VisualBinRanges: VisualBinRange[] = []; - @observable public ValueRange: number[] = []; - @observable public SizeConverter: SizeConverter = new SizeConverter(); - - @computed get createOperationParamsCache() { return this.HistoOp!.CreateOperationParameters(); } - @computed get HistogramResult() { return this.HistoOp ? this.HistoOp.Result as HistogramResult : undefined; } - @computed get BinRanges() { return this.HistogramResult ? this.HistogramResult.binRanges : undefined; } - @computed get ChartType() { - return !this.BinRanges ? ChartType.SinglePoint : this.BinRanges[0] instanceof AggregateBinRange ? - (this.BinRanges[1] instanceof AggregateBinRange ? ChartType.SinglePoint : ChartType.HorizontalBar) : - this.BinRanges[1] instanceof AggregateBinRange ? ChartType.VerticalBar : ChartType.HeatMap; - } - - componentDidMount() { - reaction(() => [CurrentUserUtils.ActiveSchemaName, this.props.doc.GetText(KeyStore.NorthstarSchema, "?")], - (params: string[]) => params[0] === params[1] && this.activateHistogramOperation(), { fireImmediately: true }); - reaction(() => [this.VisualBinRanges && this.VisualBinRanges.slice()], () => this.SizeConverter.SetVisualBinRanges(this.VisualBinRanges)); - reaction(() => [this.PanelHeight, this.PanelWidth], () => this.SizeConverter.SetIsSmall(this.PanelWidth < 40 && this.PanelHeight < 40)) - reaction(() => this.HistogramResult ? this.HistogramResult.binRanges : undefined, - (binRanges: BinRange[] | undefined) => { - if (binRanges) { - this.VisualBinRanges.splice(0, this.VisualBinRanges.length, ...binRanges.map((br, ind) => - VisualBinRangeHelper.GetVisualBinRange(br, this.HistogramResult!, ind ? this.HistoOp!.Y : this.HistoOp!.X, this.ChartType))); - - let valueAggregateKey = ModelHelpers.CreateAggregateKey(this.HistoOp!.V, this.HistogramResult!, ModelHelpers.AllBrushIndex(this.HistogramResult!)); - this.ValueRange = Object.values(this.HistogramResult!.bins!).reduce((prev, cur) => { - let value = ModelHelpers.GetAggregateResult(cur, valueAggregateKey) as DoubleValueAggregateResult; - return value && value.hasResult ? [Math.min(prev[0], value.result!), Math.max(prev[1], value.result!)] : prev; - }, [Number.MIN_VALUE, Number.MAX_VALUE]); - } - }); - } - - activateHistogramOperation() { - this.props.doc.GetTAsync(this.props.fieldKey, HistogramField).then((histoOp: Opt) => { - if (histoOp) { - runInAction(() => this.HistoOp = histoOp.Data); - reaction(() => this.props.doc.GetList(KeyStore.LinkedFromDocs, []), docs => this.HistoOp!.Links.splice(0, this.HistoOp!.Links.length, ...docs), { fireImmediately: true }); - reaction(() => this.createOperationParamsCache, () => this.HistoOp!.Update(), { fireImmediately: true }); - } - }) - } - render() { - let label = this.HistoOp && this.HistoOp.X ? this.HistoOp.X.AttributeModel.DisplayName : "<...>"; - var h = this.props.isTopMost ? this.PanelHeight : this.props.doc.GetNumber(KeyStore.Height, 0); - var w = this.props.isTopMost ? this.PanelWidth : this.props.doc.GetNumber(KeyStore.Width, 0); - let loff = this.SizeConverter.LeftOffset; - let toff = this.SizeConverter.TopOffset; - let roff = this.SizeConverter.RightOffset; - let boff = this.SizeConverter.BottomOffset; - return ( - runInAction(() => { this.PanelWidth = r.entry.width; this.PanelHeight = r.entry.height })}> - {({ measureRef }) => -
    -
    - - -
    -
    {label}
    -
    - } -
    - ) - } -} - diff --git a/src/client/views/nodes/HistogramBoxPrimitives.scss b/src/client/views/nodes/HistogramBoxPrimitives.scss deleted file mode 100644 index 85f2c092d..000000000 --- a/src/client/views/nodes/HistogramBoxPrimitives.scss +++ /dev/null @@ -1,25 +0,0 @@ -.histogramboxprimitives-border { - border: 3px; - border-style: solid; - border-color: #282828; - pointer-events: none; - position: absolute; -} -.histogramboxprimitives-bar { - position: absolute; - border: 1px; - border-style: solid; - border-color: #282828; - pointer-events: all; -} - -.histogramboxprimitives-placer { - position: absolute; - pointer-events: none; - width: 100%; - height: 100%; -} -.histogramboxprimitives-line { - position: absolute; - background: lightgray; -} \ No newline at end of file diff --git a/src/client/views/nodes/HistogramBoxPrimitives.tsx b/src/client/views/nodes/HistogramBoxPrimitives.tsx deleted file mode 100644 index f15cb5689..000000000 --- a/src/client/views/nodes/HistogramBoxPrimitives.tsx +++ /dev/null @@ -1,336 +0,0 @@ -import React = require("react") -import { computed, observable, runInAction, trace } from "mobx"; -import { observer } from "mobx-react"; -import { Utils as DashUtils } from '../../../Utils'; -import { AttributeTransformationModel } from "../../northstar/core/attribute/AttributeTransformationModel"; -import { ChartType } from '../../northstar/model/binRanges/VisualBinRange'; -import { AggregateFunction, Bin, Brush, HistogramResult, MarginAggregateParameters, MarginAggregateResult, BinLabel } from "../../northstar/model/idea/idea"; -import { ModelHelpers } from "../../northstar/model/ModelHelpers"; -import { ArrayUtil } from "../../northstar/utils/ArrayUtil"; -import { LABColor } from '../../northstar/utils/LABcolor'; -import { PIXIRectangle } from "../../northstar/utils/MathUtil"; -import { StyleConstants } from "../../northstar/utils/StyleContants"; -import { HistogramBox, HistogramPrimitivesProps } from "./HistogramBox"; -import "./HistogramBoxPrimitives.scss"; -import { HistogramOperation } from "../../northstar/operations/HistogramOperation"; -import { FilterModel } from "../../northstar/core/filter/FilterModel"; - - -@observer -export class HistogramBoxPrimitives extends React.Component { - private get histoOp() { return this.props.HistoBox.HistoOp; } - private get renderDimension() { return this.props.HistoBox.SizeConverter.RenderDimension; } - @observable _selectedPrims: HistogramBinPrimitive[] = []; - @computed get xaxislines() { return this.renderGridLinesAndLabels(0); } - @computed get yaxislines() { return this.renderGridLinesAndLabels(1); } - @computed get selectedPrimitives() { return this._selectedPrims.map(bp => this.drawRect(bp.Rect, bp.BarAxis, undefined, "border")); } - @computed get binPrimitives() { - let histoResult = this.props.HistoBox.HistogramResult; - if (!histoResult || !histoResult.bins || !this.props.HistoBox.VisualBinRanges.length) - return (null); - let allBrushIndex = ModelHelpers.AllBrushIndex(histoResult); - return Object.keys(histoResult.bins).reduce((prims, key) => { - let drawPrims = new HistogramBinPrimitiveCollection(histoResult!.bins![key], this.props.HistoBox); - let filterModel = ModelHelpers.GetBinFilterModel(histoResult!.bins![key], allBrushIndex, histoResult!, this.histoOp!.X, this.histoOp!.Y); - - this.props.HistoBox.HitTargets.setValue(drawPrims.HitGeom, filterModel); - - let toggle = this.getSelectionToggle(drawPrims.BinPrimitives, allBrushIndex, filterModel); - drawPrims.BinPrimitives.filter(bp => bp.DataValue && bp.BrushIndex !== allBrushIndex).map(bp => - prims.push(...[{ r: bp.Rect, c: bp.Color }, { r: bp.MarginRect, c: StyleConstants.MARGIN_BARS_COLOR }].map(pair => this.drawRect(pair.r, bp.BarAxis, pair.c, "bar", toggle)))); - return prims; - }, [] as JSX.Element[]); - } - - private getSelectionToggle(binPrimitives: HistogramBinPrimitive[], allBrushIndex: number, filterModel: FilterModel) { - let allBrushPrim = ArrayUtil.FirstOrDefault(binPrimitives, bp => bp.BrushIndex == allBrushIndex); - return !allBrushPrim ? () => { } : () => runInAction(() => { - if (ArrayUtil.Contains(this.histoOp!.FilterModels, filterModel)) { - this._selectedPrims.splice(this._selectedPrims.indexOf(allBrushPrim!), 1); - this.histoOp!.RemoveFilterModels([filterModel]); - } - else { - this._selectedPrims.push(allBrushPrim!); - this.histoOp!.AddFilterModels([filterModel]); - } - }) - } - - private renderGridLinesAndLabels(axis: number) { - if (!this.props.HistoBox.SizeConverter.Initialized) - return (null); - let labels = this.props.HistoBox.VisualBinRanges[axis].GetLabels(); - return labels.reduce((prims, binLabel, i) => { - let r = this.props.HistoBox.SizeConverter.DataToScreenRange(binLabel.minValue!, binLabel.maxValue!, axis); - prims.push(this.drawLine(r.xFrom, r.yFrom, axis == 0 ? 0 : r.xTo - r.xFrom, axis == 0 ? r.yTo - r.yFrom : 0)); - if (i == labels.length - 1) - prims.push(this.drawLine(axis == 0 ? r.xTo : r.xFrom, axis == 0 ? r.yFrom : r.yTo, axis == 0 ? 0 : r.xTo - r.xFrom, axis == 0 ? r.yTo - r.yFrom : 0)); - return prims; - }, [] as JSX.Element[]); - } - - drawEntity(xFrom: number, yFrom: number, entity: JSX.Element) { - let transXpercent = xFrom / this.renderDimension * 100; - let transYpercent = yFrom / this.renderDimension * 100; - return (
    - {entity} -
    ); - } - drawLine(xFrom: number, yFrom: number, width: number, height: number) { - if (height < 0) { - yFrom += height; - height = -height; - } - if (width < 0) { - xFrom += width; - width = -width; - } - let trans2Xpercent = width == 0 ? `1px` : `${(xFrom + width) / this.renderDimension * 100}%`; - let trans2Ypercent = height == 0 ? `1px` : `${(yFrom + height) / this.renderDimension * 100}%`; - let line = (
    ); - return this.drawEntity(xFrom, yFrom, line); - } - - drawRect(r: PIXIRectangle, barAxis: number, color: number | undefined, classExt: string, tapHandler: () => void = () => { }) { - let widthPercent = r.width / this.renderDimension * 100; - let heightPercent = r.height / this.renderDimension * 100; - let rect = (
    { if (e.button == 0) tapHandler() }} - style={{ - borderBottomStyle: barAxis == 1 ? "none" : "solid", - borderLeftStyle: barAxis == 0 ? "none" : "solid", - width: `${widthPercent}%`, - height: `${heightPercent}%`, - background: color ? `${LABColor.RGBtoHexString(color)}` : "" - }} - />); - return this.drawEntity(r.x, r.y, rect); - } - render() { - return
    - {this.xaxislines} - {this.yaxislines} - {this.binPrimitives} - {this.selectedPrimitives} -
    - } -} - -class HistogramBinPrimitive { - constructor(init?: Partial) { - Object.assign(this, init); - } - public DataValue: number = 0; - public Rect: PIXIRectangle = PIXIRectangle.EMPTY; - public MarginRect: PIXIRectangle = PIXIRectangle.EMPTY; - public MarginPercentage: number = 0; - public Color: number = StyleConstants.WARNING_COLOR; - public Opacity: number = 1; - public BrushIndex: number = 0; - public BarAxis: number = -1; -} - -export class HistogramBinPrimitiveCollection { - private static TOLERANCE: number = 0.0001; - - private _histoBox: HistogramBox; - private get histoOp() { return this._histoBox.HistoOp!; } - private get histoResult() { return this.histoOp.Result as HistogramResult; } - private get sizeConverter() { return this._histoBox.SizeConverter!; } - public BinPrimitives: Array = new Array(); - public HitGeom: PIXIRectangle = PIXIRectangle.EMPTY; - - constructor(bin: Bin, histoBox: HistogramBox) { - this._histoBox = histoBox; - let brushing = this.setupBrushing(bin, this.histoOp.Normalization); // X= 0, Y = 1, V = 2 - - brushing.orderedBrushes.reduce((brushFactorSum, brush) => { - switch (histoBox.ChartType) { - case ChartType.VerticalBar: return this.createVerticalBarChartBinPrimitives(bin, brush, brushing.maxAxis, this.histoOp.Normalization); - case ChartType.HorizontalBar: return this.createHorizontalBarChartBinPrimitives(bin, brush, brushing.maxAxis, this.histoOp.Normalization); - case ChartType.SinglePoint: return this.createSinglePointChartBinPrimitives(bin, brush); - case ChartType.HeatMap: return this.createHeatmapBinPrimitives(bin, brush, brushFactorSum); - } - }, 0); - - // adjust brush rects (stacking or not) - var allBrushIndex = ModelHelpers.AllBrushIndex(this.histoResult); - var filteredBinPrims = this.BinPrimitives.filter(b => b.BrushIndex != allBrushIndex && b.DataValue != 0.0); - filteredBinPrims.reduce((sum, fbp) => { - if (histoBox.ChartType == ChartType.VerticalBar) { - if (this.histoOp.X.AggregateFunction == AggregateFunction.Count) { - fbp.Rect = new PIXIRectangle(fbp.Rect.x, fbp.Rect.y - sum, fbp.Rect.width, fbp.Rect.height); - fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x, fbp.MarginRect.y - sum, fbp.MarginRect.width, fbp.MarginRect.height); - return sum + fbp.Rect.height; - } - if (this.histoOp.Y.AggregateFunction == AggregateFunction.Avg) { - var w = fbp.Rect.width / 2.0; - fbp.Rect = new PIXIRectangle(fbp.Rect.x + sum, fbp.Rect.y, fbp.Rect.width / filteredBinPrims.length, fbp.Rect.height); - fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x - w + sum + (fbp.Rect.width / 2.0), fbp.MarginRect.y, fbp.MarginRect.width, fbp.MarginRect.height); - return sum + fbp.Rect.width; - } - } - else if (histoBox.ChartType == ChartType.HorizontalBar) { - if (this.histoOp.X.AggregateFunction == AggregateFunction.Count) { - fbp.Rect = new PIXIRectangle(fbp.Rect.x + sum, fbp.Rect.y, fbp.Rect.width, fbp.Rect.height); - fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x + sum, fbp.MarginRect.y, fbp.MarginRect.width, fbp.MarginRect.height); - return sum + fbp.Rect.width; - } - if (this.histoOp.X.AggregateFunction == AggregateFunction.Avg) { - var h = fbp.Rect.height / 2.0; - fbp.Rect = new PIXIRectangle(fbp.Rect.x, fbp.Rect.y + sum, fbp.Rect.width, fbp.Rect.height / filteredBinPrims.length); - fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x, fbp.MarginRect.y - h + sum + (fbp.Rect.height / 2.0), fbp.MarginRect.width, fbp.MarginRect.height); - return sum + fbp.Rect.height; - } - } - return 0; - }, 0); - this.BinPrimitives = this.BinPrimitives.reverse(); - var f = this.BinPrimitives.filter(b => b.BrushIndex == allBrushIndex); - this.HitGeom = f.length > 0 ? f[0].Rect : PIXIRectangle.EMPTY; - } - private setupBrushing(bin: Bin, normalization: number) { - var overlapBrushIndex = ModelHelpers.OverlapBrushIndex(this.histoResult); - var orderedBrushes = [this.histoResult.brushes![0], this.histoResult.brushes![overlapBrushIndex]]; - this.histoResult.brushes!.map(brush => brush.brushIndex != 0 && brush.brushIndex != overlapBrushIndex && orderedBrushes.push(brush)); - return { - orderedBrushes, - maxAxis: orderedBrushes.reduce((prev, Brush) => { - let aggResult = this.histoOp.getValue(normalization, bin, this.histoResult, Brush.brushIndex!); - return aggResult != undefined && aggResult > prev ? aggResult : prev; - }, Number.MIN_VALUE) - }; - } - private createHeatmapBinPrimitives(bin: Bin, brush: Brush, brushFactorSum: number): number { - - let unNormalizedValue = this.histoOp!.getValue(2, bin, this.histoResult, brush.brushIndex!); - if (unNormalizedValue == undefined) - return brushFactorSum; - - var normalizedValue = (unNormalizedValue - this._histoBox.ValueRange[0]) / (Math.abs((this._histoBox.ValueRange[1] - this._histoBox.ValueRange[0])) < HistogramBinPrimitiveCollection.TOLERANCE ? - unNormalizedValue : this._histoBox.ValueRange[1] - this._histoBox.ValueRange[0]); - - let allUnNormalizedValue = this.histoOp.getValue(2, bin, this.histoResult, ModelHelpers.AllBrushIndex(this.histoResult)) - - // bcz: are these calls needed? - let [xFrom, xTo] = this.sizeConverter.DataToScreenAxisRange(this._histoBox.VisualBinRanges, 0, bin); - let [yFrom, yTo] = this.sizeConverter.DataToScreenAxisRange(this._histoBox.VisualBinRanges, 1, bin); - - var returnBrushFactorSum = brushFactorSum; - if (allUnNormalizedValue != undefined) { - var brushFactor = (unNormalizedValue / allUnNormalizedValue); - returnBrushFactorSum += brushFactor; - returnBrushFactorSum = Math.min(returnBrushFactorSum, 1.0); - - var tempRect = new PIXIRectangle(xFrom, yTo, xTo - xFrom, yFrom - yTo); - var ratio = (tempRect.width / tempRect.height); - var newHeight = Math.sqrt((1.0 / ratio) * ((tempRect.width * tempRect.height) * returnBrushFactorSum)); - var newWidth = newHeight * ratio; - - xFrom = (tempRect.x + (tempRect.width - newWidth) / 2.0); - yTo = (tempRect.y + (tempRect.height - newHeight) / 2.0); - xTo = (xFrom + newWidth); - yFrom = (yTo + newHeight); - } - var alpha = 0.0; - var color = this.baseColorFromBrush(brush); - var lerpColor = LABColor.Lerp( - LABColor.FromColor(StyleConstants.MIN_VALUE_COLOR), - LABColor.FromColor(color), - (alpha + Math.pow(normalizedValue, 1.0 / 3.0) * (1.0 - alpha))); - var dataColor = LABColor.ToColor(lerpColor); - - this.createBinPrimitive(-1, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, dataColor, 1, unNormalizedValue); - return returnBrushFactorSum; - } - - private createSinglePointChartBinPrimitives(bin: Bin, brush: Brush): number { - let unNormalizedValue = this._histoBox.HistoOp!.getValue(2, bin, this.histoResult, brush.brushIndex!); - if (unNormalizedValue != undefined) { - let [xFrom, xTo] = this.sizeConverter.DataToScreenPointRange(0, bin, ModelHelpers.CreateAggregateKey(this.histoOp.X, this.histoResult, brush.brushIndex!)); - let [yFrom, yTo] = this.sizeConverter.DataToScreenPointRange(1, bin, ModelHelpers.CreateAggregateKey(this.histoOp.Y, this.histoResult, brush.brushIndex!)); - - if (xFrom != undefined && yFrom != undefined && xTo != undefined && yTo != undefined) - this.createBinPrimitive(-1, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, this.baseColorFromBrush(brush), 1, unNormalizedValue); - } - return 0; - } - - private createVerticalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number): number { - let dataValue = this.histoOp.getValue(1, bin, this.histoResult, brush.brushIndex!); - if (dataValue != undefined) { - let [yFrom, yValue, yTo] = this.sizeConverter.DataToScreenNormalizedRange(dataValue, normalization, 1, binBrushMaxAxis); - let [xFrom, xTo] = this.sizeConverter.DataToScreenAxisRange(this._histoBox.VisualBinRanges, 0, bin); - - var yMarginAbsolute = this.getMargin(bin, brush, this.histoOp.Y); - var marginRect = new PIXIRectangle(xFrom + (xTo - xFrom) / 2.0 - 1, - this.sizeConverter.DataToScreenY(yValue + yMarginAbsolute), 2, - this.sizeConverter.DataToScreenY(yValue - yMarginAbsolute) - this.sizeConverter.DataToScreenY(yValue + yMarginAbsolute)); - - this.createBinPrimitive(1, brush, marginRect, 0, xFrom, xTo, yFrom, yTo, - this.baseColorFromBrush(brush), normalization != 0 ? 1 : 0.6 * binBrushMaxAxis / this.sizeConverter.DataRanges[1] + 0.4, dataValue); - } - return 0; - } - - private createHorizontalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number): number { - let dataValue = this.histoOp.getValue(0, bin, this.histoResult, brush.brushIndex!); - if (dataValue != undefined) { - let [xFrom, xValue, xTo] = this.sizeConverter.DataToScreenNormalizedRange(dataValue, normalization, 0, binBrushMaxAxis); - let [yFrom, yTo] = this.sizeConverter.DataToScreenAxisRange(this._histoBox.VisualBinRanges, 1, bin); - - var xMarginAbsolute = this.sizeConverter.IsSmall ? 0 : this.getMargin(bin, brush, this.histoOp.X); - var marginRect = new PIXIRectangle(this.sizeConverter.DataToScreenX(xValue - xMarginAbsolute), - yTo + (yFrom - yTo) / 2.0 - 1, - this.sizeConverter.DataToScreenX(xValue + xMarginAbsolute) - this.sizeConverter.DataToScreenX(xValue - xMarginAbsolute), - 2.0); - - this.createBinPrimitive(0, brush, marginRect, 0, xFrom, xTo, yFrom, yTo, - this.baseColorFromBrush(brush), normalization != 1 ? 1 : 0.6 * binBrushMaxAxis / this.sizeConverter.DataRanges[0] + 0.4, dataValue); - } - return 0; - } - - - private getMargin(bin: Bin, brush: Brush, axis: AttributeTransformationModel) { - var marginParams = new MarginAggregateParameters(); - marginParams.aggregateFunction = axis.AggregateFunction; - var marginAggregateKey = ModelHelpers.CreateAggregateKey(axis, this.histoResult, brush.brushIndex!, marginParams); - var marginResult = ModelHelpers.GetAggregateResult(bin, marginAggregateKey) as MarginAggregateResult; - return !marginResult ? 0 : marginResult.absolutMargin!; - } - - private createBinPrimitive(barAxis: number, brush: Brush, marginRect: PIXIRectangle, - marginPercentage: number, xFrom: number, xTo: number, yFrom: number, yTo: number, color: number, opacity: number, dataValue: number) { - var binPrimitive = new HistogramBinPrimitive( - { - Rect: new PIXIRectangle(xFrom, yTo, xTo - xFrom, yFrom - yTo), - MarginRect: marginRect, - MarginPercentage: marginPercentage, - BrushIndex: brush.brushIndex, - Color: color, - Opacity: opacity, - DataValue: dataValue, - BarAxis: barAxis - }); - this.BinPrimitives.push(binPrimitive); - } - - private baseColorFromBrush(brush: Brush): number { - if (brush.brushIndex == ModelHelpers.RestBrushIndex(this.histoResult)) { - return StyleConstants.HIGHLIGHT_COLOR; - } - else if (brush.brushIndex == ModelHelpers.OverlapBrushIndex(this.histoResult)) { - return StyleConstants.OVERLAP_COLOR; - } - else if (brush.brushIndex == ModelHelpers.AllBrushIndex(this.histoResult)) { - return 0x00ff00; - } - else if (this.histoOp.BrushColors.length > 0) { - return this.histoOp.BrushColors[brush.brushIndex! % this.histoOp.BrushColors.length]; - } - return StyleConstants.HIGHLIGHT_COLOR; - } -} \ No newline at end of file diff --git a/src/client/views/nodes/HistogramLabelPrimitives.scss b/src/client/views/nodes/HistogramLabelPrimitives.scss deleted file mode 100644 index d8ee88d72..000000000 --- a/src/client/views/nodes/HistogramLabelPrimitives.scss +++ /dev/null @@ -1,12 +0,0 @@ - - .histogramLabelPrimitives-gridlabel { - position:absolute; - transform-origin: left top; - font-size: 11; - } - .histogramLabelPrimitives-placer { - position:absolute; - width:100%; - height:100%; - pointer-events: none; - } \ No newline at end of file diff --git a/src/client/views/nodes/HistogramLabelPrimitives.tsx b/src/client/views/nodes/HistogramLabelPrimitives.tsx deleted file mode 100644 index 7f365e45b..000000000 --- a/src/client/views/nodes/HistogramLabelPrimitives.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import React = require("react") -import { action, computed, reaction } from "mobx"; -import { observer } from "mobx-react"; -import { Utils as DashUtils } from '../../../Utils'; -import { NominalVisualBinRange } from "../../northstar/model/binRanges/NominalVisualBinRange"; -import { StyleConstants } from "../../northstar/utils/StyleContants"; -import "./../../northstar/utils/Extensions"; -import "./HistogramLabelPrimitives.scss"; -import { HistogramBox, HistogramPrimitivesProps } from "./HistogramBox"; - -@observer -export class HistogramLabelPrimitives extends React.Component { - componentDidMount() { - reaction(() => [this.props.HistoBox.PanelWidth, this.props.HistoBox.SizeConverter.LeftOffset, this.props.HistoBox.VisualBinRanges.length], - (fields) => HistogramLabelPrimitives.computeLabelAngle(fields[0] as number, fields[1] as number, this.props.HistoBox), { fireImmediately: true }); - } - - @action - static computeLabelAngle(panelWidth: number, leftOffset: number, histoBox: HistogramBox) { - const textWidth = 30; - if (panelWidth > 0 && histoBox.VisualBinRanges.length && histoBox.VisualBinRanges[0] instanceof NominalVisualBinRange) { - let space = (panelWidth - leftOffset * 2) / histoBox.VisualBinRanges[0].GetBins().length; - histoBox.SizeConverter.SetLabelAngle(Math.min(Math.PI / 2, Math.max(Math.PI / 6, textWidth / space * Math.PI / 2))); - } else if (histoBox.SizeConverter.LabelAngle) { - histoBox.SizeConverter.SetLabelAngle(0); - } - } - @computed get xaxislines() { return this.renderGridLinesAndLabels(0); } - @computed get yaxislines() { return this.renderGridLinesAndLabels(1); } - - private renderGridLinesAndLabels(axis: number) { - let sc = this.props.HistoBox.SizeConverter; - let vb = this.props.HistoBox.VisualBinRanges; - if (!vb.length || !sc.Initialized) - return (null); - let dim = (axis == 0 ? this.props.HistoBox.PanelWidth : this.props.HistoBox.PanelHeight) / ((axis == 0 && vb[axis] instanceof NominalVisualBinRange) ? - (12 + 5) : // (FontStyles.AxisLabel.fontSize + 5))); - sc.MaxLabelSizes[axis].coords[axis] + 5); - - let labels = vb[axis].GetLabels(); - return labels.reduce((prims, binLabel, i) => { - let r = sc.DataToScreenRange(binLabel.minValue!, binLabel.maxValue!, axis); - if (i % Math.ceil(labels.length / dim) === 0 && binLabel.label) { - const label = binLabel.label.Truncate(StyleConstants.MAX_CHAR_FOR_HISTOGRAM_LABELS, "..."); - const textHeight = 14; const textWidth = 30; - let xStart = (axis === 0 ? r.xFrom + (r.xTo - r.xFrom) / 2.0 : r.xFrom - 10 - textWidth); - let yStart = (axis === 1 ? r.yFrom - textHeight / 2 : r.yFrom); - - if (axis == 0 && vb[axis] instanceof NominalVisualBinRange) { - let space = (r.xTo - r.xFrom) / sc.RenderDimension * this.props.HistoBox.PanelWidth; - xStart += Math.max(textWidth / 2, (1 - textWidth / space) * textWidth / 2) - textHeight / 2; - } - - let xPercent = axis == 1 ? `${xStart}px` : `${xStart / sc.RenderDimension * 100}%` - let yPercent = axis == 0 ? `${this.props.HistoBox.PanelHeight - sc.BottomOffset - textHeight}px` : `${yStart / sc.RenderDimension * 100}%` - - prims.push( -
    -
    - {label} -
    -
    - ) - } - return prims; - }, [] as JSX.Element[]); - } - - render() { - let xaxislines = this.xaxislines; - let yaxislines = this.yaxislines; - return
    - {xaxislines} - {yaxislines} -
    - } - -} \ No newline at end of file diff --git a/src/fields/HistogramField.ts b/src/fields/HistogramField.ts deleted file mode 100644 index bb0014ab3..000000000 --- a/src/fields/HistogramField.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { BasicField } from "./BasicField"; -import { Field, FieldId } from "./Field"; -import { Types } from "../server/Message"; -import { HistogramOperation } from "../client/northstar/operations/HistogramOperation"; -import { action } from "mobx"; -import { AttributeTransformationModel } from "../client/northstar/core/attribute/AttributeTransformationModel"; -import { ColumnAttributeModel } from "../client/northstar/core/attribute/AttributeModel"; -import { CurrentUserUtils } from "../server/authentication/models/current_user_utils"; - - -export class HistogramField extends BasicField { - constructor(data?: HistogramOperation, id?: FieldId, save: boolean = true) { - super(data ? data : HistogramOperation.Empty, save, id); - } - - toString(): string { - return JSON.stringify(this.Data); - } - - Copy(): Field { - return new HistogramField(this.Data); - } - - ToScriptString(): string { - return `new HistogramField("${this.Data}")`; - } - - ToJson(): { type: Types, data: string, _id: string } { - return { - type: Types.HistogramOp, - data: JSON.stringify(this.Data), - _id: this.Id - } - } - - @action - static FromJson(id: string, data: any): HistogramField { - let jp = JSON.parse(data); - let X: AttributeTransformationModel | undefined; - let Y: AttributeTransformationModel | undefined; - let V: AttributeTransformationModel | undefined; - - CurrentUserUtils.GetAllNorthstarColumnAttributes().map(attr => { - if (attr.displayName == jp.X.AttributeModel.Attribute.DisplayName) { - X = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.X.AggregateFunction); - } - if (attr.displayName == jp.Y.AttributeModel.Attribute.DisplayName) { - Y = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.Y.AggregateFunction); - } - if (attr.displayName == jp.V.AttributeModel.Attribute.DisplayName) { - V = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.V.AggregateFunction); - } - }); - if (X && Y && V) { - return new HistogramField(new HistogramOperation(X, Y, V, jp.Normalization), id, false); - } - return new HistogramField(HistogramOperation.Empty, id, false); - } -} \ No newline at end of file diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts index 09dddf962..aa0b9ce92 100644 --- a/src/fields/KeyStore.ts +++ b/src/fields/KeyStore.ts @@ -44,5 +44,4 @@ export namespace KeyStore { export const Archives = new Key("Archives"); export const Updated = new Key("Updated"); export const Workspaces = new Key("Workspaces"); - export const NorthstarSchema = new Key("NorthstarSchema"); } diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts index f958df04b..98a7a1451 100644 --- a/src/server/ServerUtil.ts +++ b/src/server/ServerUtil.ts @@ -17,8 +17,7 @@ import { VideoField } from '../fields/VideoField'; import { InkField } from '../fields/InkField'; import { PDFField } from '../fields/PDFField'; import { TupleField } from '../fields/TupleField'; -import { HistogramField } from '../fields/HistogramField'; - +import { HistogramField } from '../client/northstar/dash-fields/HistogramField'; diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 4b42e40b6..0ac85b446 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -7,8 +7,9 @@ import { Document } from "../../../fields/Document"; import { KeyStore } from "../../../fields/KeyStore"; import { ListField } from "../../../fields/ListField"; import { Documents } from "../../../client/documents/Documents"; -import { Schema, Attribute, AttributeGroup } from "../../../client/northstar/model/idea/idea"; +import { Schema, Attribute, AttributeGroup, Catalog } from "../../../client/northstar/model/idea/idea"; import { observable, computed, action } from "mobx"; +import { ArrayUtil } from "../../../client/northstar/utils/ArrayUtil"; export class CurrentUserUtils { private static curr_email: string; @@ -16,8 +17,7 @@ export class CurrentUserUtils { private static user_document: Document; //TODO tfs: these should be temporary... private static mainDocId: string | undefined; - private static activeSchema: Schema | undefined; - @observable public static ActiveSchemaName: string = ""; + @observable private static catalog?: Catalog; public static get email(): string { return this.curr_email; @@ -39,12 +39,18 @@ export class CurrentUserUtils { this.mainDocId = id; } - - public static get ActiveSchema(): Schema | undefined { - return this.activeSchema; + @computed public static get NorthstarDBCatalog(): Catalog | undefined { + return this.catalog; + } + public static set NorthstarDBCatalog(ctlog: Catalog | undefined) { + this.catalog = ctlog; } - public static GetAllNorthstarColumnAttributes() { - if (!this.ActiveSchema || !this.ActiveSchema.rootAttributeGroup) { + public static GetNorthstarSchema(name: string): Schema | undefined { + return !this.catalog || !this.catalog.schemas ? undefined : + ArrayUtil.FirstOrDefault(this.catalog.schemas, (s: Schema) => s.displayName === name); + } + public static GetAllNorthstarColumnAttributes(schema: Schema) { + if (!schema || !schema.rootAttributeGroup) { return []; } const recurs = (attrs: Attribute[], g: AttributeGroup) => { @@ -56,13 +62,10 @@ export class CurrentUserUtils { } }; const allAttributes: Attribute[] = new Array(); - recurs(allAttributes, this.ActiveSchema.rootAttributeGroup); + recurs(allAttributes, schema.rootAttributeGroup); return allAttributes; } - public static set ActiveSchema(id: Schema | undefined) { - this.activeSchema = id; - } private static createUserDocument(id: string): Document { let doc = new Document(id); -- cgit v1.2.3-70-g09d2