From a2bec39bc152f7493e4171b6446040fa381e6463 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 17 Mar 2019 20:57:20 -0400 Subject: added tuple field, beginnig remote cursor display --- src/server/ServerUtil.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src/server/ServerUtil.ts') diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts index 5331c9e30..dce4bada5 100644 --- a/src/server/ServerUtil.ts +++ b/src/server/ServerUtil.ts @@ -14,8 +14,9 @@ import { HtmlField } from '../fields/HtmlField'; import { WebField } from '../fields/WebField'; import { AudioField } from '../fields/AudioField'; import { VideoField } from '../fields/VideoField'; -import {InkField} from '../fields/InkField'; -import {PDFField} from '../fields/PDFField'; +import { InkField } from '../fields/InkField'; +import { PDFField } from '../fields/PDFField'; +import { TupleField } from '../fields/TupleField'; @@ -55,6 +56,8 @@ export class ServerUtils { return new AudioField(new URL(data), id, false) case Types.Video: return new VideoField(new URL(data), id, false) + case Types.Tuple: + return new TupleField(data, id, false); case Types.Ink: return InkField.FromJson(id, data); case Types.Document: -- 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/ServerUtil.ts') 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 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/ServerUtil.ts') 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/ServerUtil.ts') 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 From 36019dc66ae66bac01118ed05bdb5c6466f9bdc8 Mon Sep 17 00:00:00 2001 From: Monika Hedman Date: Tue, 2 Apr 2019 18:05:56 -0400 Subject: done with minimize to box --- src/client/util/DragManager.ts | 1 - src/client/views/nodes/DocumentView.tsx | 35 +++++-- src/fields/Document.ts | 5 + src/fields/KeyStore.ts | 90 ++++++++--------- src/fields/MinimizedField.tsx | 29 ++++++ src/server/Message.ts | 168 ++++++++++++++++++-------------- src/server/ServerUtil.ts | 151 ++++++++++++++-------------- 7 files changed, 282 insertions(+), 197 deletions(-) create mode 100644 src/fields/MinimizedField.tsx (limited to 'src/server/ServerUtil.ts') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index ee0b5333c..c0f482e18 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -192,7 +192,6 @@ export namespace DragManager { scaleYs.push(scaleY); let dragElement = ele.cloneNode(true) as HTMLElement; dragElement.style.opacity = "0.7"; - console.log(dragElement); dragElement.style.position = "absolute"; dragElement.style.bottom = ""; dragElement.style.left = ""; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index c4f329357..05058e63d 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -31,6 +31,7 @@ import "./DocumentView.scss"; import React = require("react"); import { ServerUtils } from "../../../server/ServerUtil"; import { DocumentDecorations } from "../DocumentDecorations"; +import { MinimizedField } from "../../../fields/MinimizedField"; export interface DocumentViewProps { ContainingCollectionView: Opt; @@ -102,8 +103,15 @@ export class DocumentView extends React.Component { private _downX: number = 0; private _downY: number = 0; + // @observable + // private minimized: boolean = false; + @observable - private minimized: boolean = false; + private _minimized: boolean = this.props.Document.GetData( + KeyStore.Minimized, + MinimizedField, + false as boolean + ); private _reactionDisposer: Opt; @computed get active(): boolean { @@ -310,7 +318,13 @@ export class DocumentView extends React.Component { @action minimize = (e: React.MouseEvent): void => { - this.minimized = true; + //hopefully sets field? + this._minimized = true as boolean; + this.props.Document.SetData( + KeyStore.Minimized, + true as boolean, + MinimizedField + ); SelectionManager.DeselectAll(); }; @@ -383,7 +397,7 @@ export class DocumentView extends React.Component { e.preventDefault(); //for testing purposes - if (!this.minimized) { + if (!this.isMinimized()) { ContextMenu.Instance.addItem({ description: "Minimize", event: this.minimize @@ -434,12 +448,21 @@ export class DocumentView extends React.Component { }; isMinimized = () => { - return this.minimized; + let field = this.props.Document.GetT(KeyStore.Minimized, MinimizedField); + if (field && field !== FieldWaiting) { + return field.Data; + } + //return this.minimized; }; @action expand = () => { - this.minimized = false; + //this._minimized = false; + this.props.Document.SetData( + KeyStore.Minimized, + false as boolean, + MinimizedField + ); }; isSelected = () => { @@ -459,7 +482,7 @@ export class DocumentView extends React.Component { var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0); var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0); - if (this.minimized) { + if (this.isMinimized()) { return (
{ + constructor( + data: boolean = false as boolean, + id?: FieldId, + save: boolean = true as boolean + ) { + super(data, save, id); + } + + ToScriptString(): string { + return `new MinimizedField("${this.Data}")`; + } + + Copy() { + return new MinimizedField(this.Data); + } + + ToJson(): { type: Types; data: boolean; _id: string } { + return { + type: Types.Minimized, + data: this.Data, + _id: this.Id + }; + } +} diff --git a/src/server/Message.ts b/src/server/Message.ts index 05ae0f19a..29df57419 100644 --- a/src/server/Message.ts +++ b/src/server/Message.ts @@ -1,125 +1,145 @@ import { Utils } from "../Utils"; export class Message { - private name: string; - private guid: string; + private name: string; + private guid: string; - get Name(): string { - return this.name; - } + get Name(): string { + return this.name; + } - get Message(): string { - return this.guid - } + get Message(): string { + return this.guid; + } - constructor(name: string) { - this.name = name; - this.guid = Utils.GenerateDeterministicGuid(name) - } + constructor(name: string) { + this.name = name; + this.guid = Utils.GenerateDeterministicGuid(name); + } - GetValue() { - return this.Name; - } + GetValue() { + return this.Name; + } } class TestMessageArgs { - hello: string = ""; + hello: string = ""; } export class SetFieldArgs { - field: string; - value: any; + field: string; + value: any; - constructor(f: string, v: any) { - this.field = f - this.value = v - } + constructor(f: string, v: any) { + this.field = f; + this.value = v; + } } export class GetFieldArgs { - field: string; + field: string; - constructor(f: string) { - this.field = f - } + constructor(f: string) { + this.field = f; + } } export enum Types { - Number, List, Key, Image, Web, Document, Text, RichText, DocumentReference, Html, Video, Audio, Ink, PDF, Tuple, HistogramOp + Number, + List, + Key, + Image, + Web, + Document, + Text, + RichText, + DocumentReference, + Html, + Video, + Audio, + Ink, + PDF, + Tuple, + HistogramOp, + Minimized } export class DocumentTransfer implements Transferable { - readonly type = Types.Document - _id: string - - constructor(readonly obj: { type: Types, data: [string, string][], _id: string }) { - this._id = obj._id - } + readonly type = Types.Document; + _id: string; + + constructor( + readonly obj: { type: Types; data: [string, string][]; _id: string } + ) { + this._id = obj._id; + } } export class ImageTransfer implements Transferable { - readonly type = Types.Image + readonly type = Types.Image; - constructor(readonly _id: string) { } + constructor(readonly _id: string) {} } export class KeyTransfer implements Transferable { - name: string - readonly _id: string - readonly type = Types.Key - - constructor(i: string, n: string) { - this.name = n - this._id = i - } + name: string; + readonly _id: string; + readonly type = Types.Key; + + constructor(i: string, n: string) { + this.name = n; + this._id = i; + } } export class ListTransfer implements Transferable { - type = Types.List; + type = Types.List; - constructor(readonly _id: string) { } + constructor(readonly _id: string) {} } export class NumberTransfer implements Transferable { - readonly type = Types.Number + readonly type = Types.Number; - constructor(readonly value: number, readonly _id: string) { } + constructor(readonly value: number, readonly _id: string) {} } export class TextTransfer implements Transferable { - value: string - readonly _id: string - readonly type = Types.Text - - constructor(t: string, i: string) { - this.value = t - this._id = i - } + value: string; + readonly _id: string; + readonly type = Types.Text; + + constructor(t: string, i: string) { + this.value = t; + this._id = i; + } } export class RichTextTransfer implements Transferable { - value: string - readonly _id: string - readonly type = Types.Text - - constructor(t: string, i: string) { - this.value = t - this._id = i - } + value: string; + readonly _id: string; + readonly type = Types.Text; + + constructor(t: string, i: string) { + this.value = t; + this._id = i; + } } export interface Transferable { - readonly _id: string - readonly type: Types + readonly _id: string; + readonly type: Types; } export namespace MessageStore { - export const Foo = new Message("Foo"); - export const Bar = new Message("Bar"); - export const AddDocument = new Message("Add Document"); - export const SetField = new Message<{ _id: string, data: any, type: Types }>("Set Field") - export const GetField = new Message("Get Field") - export const GetFields = new Message("Get Fields") - export const GetDocument = new Message("Get Document"); - export const DeleteAll = new Message("Delete All"); -} \ No newline at end of file + export const Foo = new Message("Foo"); + export const Bar = new Message("Bar"); + export const AddDocument = new Message("Add Document"); + export const SetField = new Message<{ _id: string; data: any; type: Types }>( + "Set Field" + ); + export const GetField = new Message("Get Field"); + export const GetFields = new Message("Get Fields"); + export const GetDocument = new Message("Get Document"); + export const DeleteAll = new Message("Delete All"); +} diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts index 98a7a1451..3e24fed3a 100644 --- a/src/server/ServerUtil.ts +++ b/src/server/ServerUtil.ts @@ -1,78 +1,85 @@ - -import { Field } from './../fields/Field'; -import { TextField } from './../fields/TextField'; -import { NumberField } from './../fields/NumberField'; -import { RichTextField } from './../fields/RichTextField'; -import { Key } from './../fields/Key'; -import { ImageField } from './../fields/ImageField'; -import { ListField } from './../fields/ListField'; -import { Document } from './../fields/Document'; -import { Server } from './../client/Server'; -import { Types } from './Message'; -import { Utils } from '../Utils'; -import { HtmlField } from '../fields/HtmlField'; -import { WebField } from '../fields/WebField'; -import { AudioField } from '../fields/AudioField'; -import { VideoField } from '../fields/VideoField'; -import { InkField } from '../fields/InkField'; -import { PDFField } from '../fields/PDFField'; -import { TupleField } from '../fields/TupleField'; -import { HistogramField } from '../client/northstar/dash-fields/HistogramField'; - - +import { Field } from "./../fields/Field"; +import { TextField } from "./../fields/TextField"; +import { NumberField } from "./../fields/NumberField"; +import { RichTextField } from "./../fields/RichTextField"; +import { Key } from "./../fields/Key"; +import { ImageField } from "./../fields/ImageField"; +import { ListField } from "./../fields/ListField"; +import { Document } from "./../fields/Document"; +import { Server } from "./../client/Server"; +import { Types } from "./Message"; +import { Utils } from "../Utils"; +import { HtmlField } from "../fields/HtmlField"; +import { WebField } from "../fields/WebField"; +import { AudioField } from "../fields/AudioField"; +import { VideoField } from "../fields/VideoField"; +import { InkField } from "../fields/InkField"; +import { PDFField } from "../fields/PDFField"; +import { TupleField } from "../fields/TupleField"; +import { MinimizedField } from "../fields/MinimizedField"; +import { HistogramField } from "../client/northstar/dash-fields/HistogramField"; export class ServerUtils { - public static prepend(extension: string): string { return window.location.origin + extension; } + 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 - let id: string = obj._id - let type: Types = obj.type + public static FromJson(json: any): Field { + let obj = json; + let data: any = obj.data; + let id: string = obj._id; + let type: Types = obj.type; - if (!(data !== undefined && id && type !== undefined)) { - console.log("how did you manage to get an object that doesn't have a data or an id?") - return new TextField("Something to fill the space", Utils.GenerateGuid()); - } + if (!(data !== undefined && id && type !== undefined)) { + console.log( + "how did you manage to get an object that doesn't have a data or an id?" + ); + return new TextField("Something to fill the space", Utils.GenerateGuid()); + } - switch (type) { - case Types.Number: - return new NumberField(data, id, false) - case Types.Text: - return new TextField(data, id, false) - case Types.Html: - return new HtmlField(data, id, false) - case Types.Web: - return new WebField(new URL(data), id, false) - case Types.RichText: - return new RichTextField(data, id, false) - case Types.Key: - 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: - return ListField.FromJson(id, data) - case Types.Audio: - return new AudioField(new URL(data), id, false) - case Types.Video: - return new VideoField(new URL(data), id, false) - case Types.Tuple: - return new TupleField(data, id, false); - case Types.Ink: - return InkField.FromJson(id, data); - case Types.Document: - let doc: Document = new Document(id, false) - let fields: [string, string][] = data as [string, string][] - fields.forEach(element => { - doc._proxies.set(element[0], element[1]); - }); - return doc - default: - throw Error("Error, unrecognized field type received from server. If you just created a new field type, be sure to add it here"); - } + switch (type) { + case Types.Minimized: + return new MinimizedField(data, id, false); + case Types.Number: + return new NumberField(data, id, false); + case Types.Text: + return new TextField(data, id, false); + case Types.Html: + return new HtmlField(data, id, false); + case Types.Web: + console.log("LOOK HERE! " + data); + return new WebField(new URL(data), id, false); + case Types.RichText: + return new RichTextField(data, id, false); + case Types.Key: + 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: + return ListField.FromJson(id, data); + case Types.Audio: + return new AudioField(new URL(data), id, false); + case Types.Video: + return new VideoField(new URL(data), id, false); + case Types.Tuple: + return new TupleField(data, id, false); + case Types.Ink: + return InkField.FromJson(id, data); + case Types.Document: + let doc: Document = new Document(id, false); + let fields: [string, string][] = data as [string, string][]; + fields.forEach(element => { + doc._proxies.set(element[0], element[1]); + }); + return doc; + default: + throw Error( + "Error, unrecognized field type received from server. If you just created a new field type, be sure to add it here" + ); } -} \ No newline at end of file + } +} -- cgit v1.2.3-70-g09d2 From 2d5da2260e6b96d28fdf3d7c243049c67b29f535 Mon Sep 17 00:00:00 2001 From: Monika Hedman Date: Tue, 2 Apr 2019 18:06:40 -0400 Subject: cleanup --- src/server/ServerUtil.ts | 1 - 1 file changed, 1 deletion(-) (limited to 'src/server/ServerUtil.ts') diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts index 3e24fed3a..f3c5e1d1f 100644 --- a/src/server/ServerUtil.ts +++ b/src/server/ServerUtil.ts @@ -47,7 +47,6 @@ export class ServerUtils { case Types.Html: return new HtmlField(data, id, false); case Types.Web: - console.log("LOOK HERE! " + data); return new WebField(new URL(data), id, false); case Types.RichText: return new RichTextField(data, id, false); -- cgit v1.2.3-70-g09d2 From 52dade42e61a1d147bf43ece7f2b1d7b3d7b6b6a Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 3 Apr 2019 16:12:31 -0400 Subject: changed boolean. and editor indentation. --- .vscode/settings.json | 3 +- src/client/views/nodes/DocumentView.tsx | 854 ++++++++++----------- src/fields/BooleanField.ts | 25 + src/fields/MinimizedField.tsx | 29 - src/server/ServerUtil.ts | 116 +-- src/server/database.ts | 10 +- .../upload_a6a70d84ebb65febf7900e29f52cc86d.pdf | Bin 0 -> 1043556 bytes 7 files changed, 509 insertions(+), 528 deletions(-) create mode 100644 src/fields/BooleanField.ts delete mode 100644 src/fields/MinimizedField.tsx create mode 100644 src/server/public/files/upload_a6a70d84ebb65febf7900e29f52cc86d.pdf (limited to 'src/server/ServerUtil.ts') diff --git a/.vscode/settings.json b/.vscode/settings.json index 081b05b38..fc315ffaf 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,6 +7,7 @@ "**/.DS_Store": true, }, "editor.formatOnSave": true, + "editor.detectIndentation": false, "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets": false, "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": true -} +} \ No newline at end of file diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index bc627015c..714ab9447 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,19 +1,13 @@ -import { - action, - computed, - IReactionDisposer, - reaction, - runInAction, - observable -} from "mobx"; -import { library } from "@fortawesome/fontawesome-svg-core"; +import { action, computed, IReactionDisposer, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../fields/Document"; -import { Field, Opt, FieldWaiting } from "../../../fields/Field"; +import { Field, FieldWaiting, Opt } from "../../../fields/Field"; import { Key } from "../../../fields/Key"; import { KeyStore } from "../../../fields/KeyStore"; import { ListField } from "../../../fields/ListField"; +import { BooleanField } from "../../../fields/BooleanField"; import { TextField } from "../../../fields/TextField"; +import { ServerUtils } from "../../../server/ServerUtil"; import { Utils } from "../../../Utils"; import { Documents } from "../../documents/Documents"; import { DocumentManager } from "../../util/DocumentManager"; @@ -21,34 +15,28 @@ import { DragManager } from "../../util/DragManager"; import { SelectionManager } from "../../util/SelectionManager"; import { Transform } from "../../util/Transform"; import { CollectionDockingView } from "../collections/CollectionDockingView"; -import { - CollectionView, - CollectionViewType -} from "../collections/CollectionView"; +import { CollectionView, CollectionViewType } from "../collections/CollectionView"; import { ContextMenu } from "../ContextMenu"; import { DocumentContentsView } from "./DocumentContentsView"; import "./DocumentView.scss"; import React = require("react"); -import { ServerUtils } from "../../../server/ServerUtil"; -import { DocumentDecorations } from "../DocumentDecorations"; -import { MinimizedField } from "../../../fields/MinimizedField"; export interface DocumentViewProps { - ContainingCollectionView: Opt; - Document: Document; - AddDocument?: (doc: Document, allowDuplicates: boolean) => boolean; - RemoveDocument?: (doc: Document) => boolean; - ScreenToLocalTransform: () => Transform; - isTopMost: boolean; - ContentScaling: () => number; - PanelWidth: () => number; - PanelHeight: () => number; - focus: (doc: Document) => void; - SelectOnLoad: boolean; + ContainingCollectionView: Opt; + Document: Document; + AddDocument?: (doc: Document, allowDuplicates: boolean) => boolean; + RemoveDocument?: (doc: Document) => boolean; + ScreenToLocalTransform: () => Transform; + isTopMost: boolean; + ContentScaling: () => number; + PanelWidth: () => number; + PanelHeight: () => number; + focus: (doc: Document) => void; + SelectOnLoad: boolean; } export interface JsxArgs extends DocumentViewProps { - Keys: { [name: string]: Key }; - Fields: { [name: string]: Field }; + Keys: { [name: string]: Key }; + Fields: { [name: string]: Field }; } /* @@ -67,448 +55,448 @@ Example usage of this function: } */ export function FakeJsxArgs(keys: string[], fields: string[] = []): JsxArgs { - let Keys: { [name: string]: any } = {}; - let Fields: { [name: string]: any } = {}; - for (const key of keys) { - let fn = () => { }; - Object.defineProperty(fn, "name", { value: key + "Key" }); - Keys[key] = fn; - } - for (const field of fields) { - let fn = () => { }; - Object.defineProperty(fn, "name", { value: field }); - Fields[field] = fn; - } - let args: JsxArgs = { - Document: function Document() { }, - DocumentView: function DocumentView() { }, - Keys, - Fields - } as any; - return args; + let Keys: { [name: string]: any } = {}; + let Fields: { [name: string]: any } = {}; + for (const key of keys) { + let fn = () => { }; + Object.defineProperty(fn, "name", { value: key + "Key" }); + Keys[key] = fn; + } + for (const field of fields) { + let fn = () => { }; + Object.defineProperty(fn, "name", { value: field }); + Fields[field] = fn; + } + let args: JsxArgs = { + Document: function Document() { }, + DocumentView: function DocumentView() { }, + Keys, + Fields + } as any; + return args; } export interface JsxBindings { - Document: Document; - isSelected: () => boolean; - select: (isCtrlPressed: boolean) => void; - isTopMost: boolean; - SelectOnLoad: boolean; - [prop: string]: any; + Document: Document; + isSelected: () => boolean; + select: (isCtrlPressed: boolean) => void; + isTopMost: boolean; + SelectOnLoad: boolean; + [prop: string]: any; } @observer export class DocumentView extends React.Component { - private _mainCont = React.createRef(); - private _downX: number = 0; - private _downY: number = 0; + private _mainCont = React.createRef(); + private _downX: number = 0; + private _downY: number = 0; - private _reactionDisposer: Opt; - @computed get active(): boolean { - return ( - SelectionManager.IsSelected(this) || - !this.props.ContainingCollectionView || - this.props.ContainingCollectionView.active() - ); - } - @computed get topMost(): boolean { - return ( - !this.props.ContainingCollectionView || - this.props.ContainingCollectionView.collectionViewType == - CollectionViewType.Docking - ); - } - @computed get layout(): string { - return this.props.Document.GetText( - KeyStore.Layout, - "

Error loading layout data

" - ); - } - @computed get layoutKeys(): Key[] { - return this.props.Document.GetData( - KeyStore.LayoutKeys, - ListField, - new Array() - ); - } - @computed get layoutFields(): Key[] { - return this.props.Document.GetData( - KeyStore.LayoutFields, - ListField, - new Array() - ); - } - screenRect = (): ClientRect | DOMRect => - this._mainCont.current - ? this._mainCont.current.getBoundingClientRect() - : new DOMRect(); - onPointerDown = (e: React.PointerEvent): void => { - this._downX = e.clientX; - this._downY = e.clientY; - if (e.shiftKey && e.buttons === 2) { - if (this.props.isTopMost) { - this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey); - } else - CollectionDockingView.Instance.StartOtherDrag([this.props.Document], e); - e.stopPropagation(); - } else { - if (this.active && !e.isDefaultPrevented()) { - e.stopPropagation(); - document.removeEventListener("pointermove", this.onPointerMove); - document.addEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - document.addEventListener("pointerup", this.onPointerUp); - } + private _reactionDisposer: Opt; + @computed get active(): boolean { + return ( + SelectionManager.IsSelected(this) || + !this.props.ContainingCollectionView || + this.props.ContainingCollectionView.active() + ); + } + @computed get topMost(): boolean { + return ( + !this.props.ContainingCollectionView || + this.props.ContainingCollectionView.collectionViewType == + CollectionViewType.Docking + ); } - }; + @computed get layout(): string { + return this.props.Document.GetText( + KeyStore.Layout, + "

Error loading layout data

" + ); + } + @computed get layoutKeys(): Key[] { + return this.props.Document.GetData( + KeyStore.LayoutKeys, + ListField, + new Array() + ); + } + @computed get layoutFields(): Key[] { + return this.props.Document.GetData( + KeyStore.LayoutFields, + ListField, + new Array() + ); + } + screenRect = (): ClientRect | DOMRect => + this._mainCont.current + ? this._mainCont.current.getBoundingClientRect() + : new DOMRect(); + onPointerDown = (e: React.PointerEvent): void => { + this._downX = e.clientX; + this._downY = e.clientY; + if (e.shiftKey && e.buttons === 2) { + if (this.props.isTopMost) { + this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey); + } else + CollectionDockingView.Instance.StartOtherDrag([this.props.Document], e); + e.stopPropagation(); + } else { + if (this.active && !e.isDefaultPrevented()) { + e.stopPropagation(); + document.removeEventListener("pointermove", this.onPointerMove); + document.addEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + document.addEventListener("pointerup", this.onPointerUp); + } + } + }; - private dropDisposer?: DragManager.DragDropDisposer; + private dropDisposer?: DragManager.DragDropDisposer; - componentDidMount() { - if (this._mainCont.current) { - this.dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { - handlers: { drop: this.drop.bind(this) } - }); + componentDidMount() { + if (this._mainCont.current) { + this.dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { + handlers: { drop: this.drop.bind(this) } + }); + } + runInAction(() => DocumentManager.Instance.DocumentViews.push(this)); + this._reactionDisposer = reaction( + () => + this.props.ContainingCollectionView && + this.props.ContainingCollectionView.SelectedDocs.slice(), + () => { + if ( + this.props.ContainingCollectionView && + this.props.ContainingCollectionView.SelectedDocs.indexOf( + this.props.Document.Id + ) != -1 + ) + SelectionManager.SelectDoc(this, true); + } + ); } - runInAction(() => DocumentManager.Instance.DocumentViews.push(this)); - this._reactionDisposer = reaction( - () => - this.props.ContainingCollectionView && - this.props.ContainingCollectionView.SelectedDocs.slice(), - () => { - if ( - this.props.ContainingCollectionView && - this.props.ContainingCollectionView.SelectedDocs.indexOf( - this.props.Document.Id - ) != -1 - ) - SelectionManager.SelectDoc(this, true); - } - ); - } - componentDidUpdate() { - if (this.dropDisposer) { - this.dropDisposer(); - } - if (this._mainCont.current) { - this.dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { - handlers: { drop: this.drop.bind(this) } - }); + componentDidUpdate() { + if (this.dropDisposer) { + this.dropDisposer(); + } + if (this._mainCont.current) { + this.dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { + handlers: { drop: this.drop.bind(this) } + }); + } } - } - componentWillUnmount() { - if (this.dropDisposer) { - this.dropDisposer(); + componentWillUnmount() { + if (this.dropDisposer) { + this.dropDisposer(); + } + runInAction(() => + DocumentManager.Instance.DocumentViews.splice( + DocumentManager.Instance.DocumentViews.indexOf(this), + 1 + ) + ); + if (this._reactionDisposer) { + this._reactionDisposer(); + } } - runInAction(() => - DocumentManager.Instance.DocumentViews.splice( - DocumentManager.Instance.DocumentViews.indexOf(this), - 1 - ) - ); - if (this._reactionDisposer) { - this._reactionDisposer(); + + startDragging(x: number, y: number, dropAliasOfDraggedDoc: boolean) { + if (this._mainCont.current) { + const [left, top] = this.props + .ScreenToLocalTransform() + .inverse() + .transformPoint(0, 0); + let dragData = new DragManager.DocumentDragData([this.props.Document]); + dragData.aliasOnDrop = dropAliasOfDraggedDoc; + dragData.xOffset = x - left; + dragData.yOffset = y - top; + dragData.removeDocument = (dropCollectionView: CollectionView) => { + if ( + this.props.RemoveDocument && + this.props.ContainingCollectionView !== dropCollectionView + ) { + this.props.RemoveDocument(this.props.Document); + } + }; + DragManager.StartDocumentDrag([this._mainCont.current], dragData, { + handlers: { + dragComplete: action(() => { }) + }, + hideSource: !dropAliasOfDraggedDoc + }); + } } - } - startDragging(x: number, y: number, dropAliasOfDraggedDoc: boolean) { - if (this._mainCont.current) { - const [left, top] = this.props - .ScreenToLocalTransform() - .inverse() - .transformPoint(0, 0); - let dragData = new DragManager.DocumentDragData([this.props.Document]); - dragData.aliasOnDrop = dropAliasOfDraggedDoc; - dragData.xOffset = x - left; - dragData.yOffset = y - top; - dragData.removeDocument = (dropCollectionView: CollectionView) => { + onPointerMove = (e: PointerEvent): void => { + if (e.cancelBubble) { + return; + } if ( - this.props.RemoveDocument && - this.props.ContainingCollectionView !== dropCollectionView + Math.abs(this._downX - e.clientX) > 3 || + Math.abs(this._downY - e.clientY) > 3 ) { - this.props.RemoveDocument(this.props.Document); + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + if (!this.topMost || e.buttons == 2 || e.altKey) { + this.startDragging(e.x, e.y, e.ctrlKey || e.altKey); + } } - }; - DragManager.StartDocumentDrag([this._mainCont.current], dragData, { - handlers: { - dragComplete: action(() => { }) - }, - hideSource: !dropAliasOfDraggedDoc - }); - } - } - - onPointerMove = (e: PointerEvent): void => { - if (e.cancelBubble) { - return; - } - if ( - Math.abs(this._downX - e.clientX) > 3 || - Math.abs(this._downY - e.clientY) > 3 - ) { - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - if (!this.topMost || e.buttons == 2 || e.altKey) { - this.startDragging(e.x, e.y, e.ctrlKey || e.altKey); - } - } - e.stopPropagation(); - e.preventDefault(); - }; - onPointerUp = (e: PointerEvent): void => { - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - e.stopPropagation(); - if ( - Math.abs(e.clientX - this._downX) < 4 && - Math.abs(e.clientY - this._downY) < 4 - ) { - SelectionManager.SelectDoc(this, e.ctrlKey); - } - }; - stopPropogation = (e: React.SyntheticEvent) => { - e.stopPropagation(); - }; + e.stopPropagation(); + e.preventDefault(); + }; + onPointerUp = (e: PointerEvent): void => { + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + e.stopPropagation(); + if ( + Math.abs(e.clientX - this._downX) < 4 && + Math.abs(e.clientY - this._downY) < 4 + ) { + SelectionManager.SelectDoc(this, e.ctrlKey); + } + }; + stopPropogation = (e: React.SyntheticEvent) => { + e.stopPropagation(); + }; - deleteClicked = (): void => { - if (this.props.RemoveDocument) { - this.props.RemoveDocument(this.props.Document); - } - }; + deleteClicked = (): void => { + if (this.props.RemoveDocument) { + this.props.RemoveDocument(this.props.Document); + } + }; - fieldsClicked = (e: React.MouseEvent): void => { - if (this.props.AddDocument) { - this.props.AddDocument( - Documents.KVPDocument(this.props.Document, { width: 300, height: 300 }), - false - ); - } - }; - fullScreenClicked = (e: React.MouseEvent): void => { - CollectionDockingView.Instance.OpenFullScreen(this.props.Document); - ContextMenu.Instance.clearItems(); - ContextMenu.Instance.addItem({ - description: "Close Full Screen", - event: this.closeFullScreenClicked - }); - ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); - }; + fieldsClicked = (e: React.MouseEvent): void => { + if (this.props.AddDocument) { + this.props.AddDocument( + Documents.KVPDocument(this.props.Document, { width: 300, height: 300 }), + false + ); + } + }; + fullScreenClicked = (e: React.MouseEvent): void => { + CollectionDockingView.Instance.OpenFullScreen(this.props.Document); + ContextMenu.Instance.clearItems(); + ContextMenu.Instance.addItem({ + description: "Close Full Screen", + event: this.closeFullScreenClicked + }); + ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); + }; - closeFullScreenClicked = (e: React.MouseEvent): void => { - CollectionDockingView.Instance.CloseFullScreen(); - ContextMenu.Instance.clearItems(); - ContextMenu.Instance.addItem({ - description: "Full Screen", - event: this.fullScreenClicked - }); - ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); - }; + closeFullScreenClicked = (e: React.MouseEvent): void => { + CollectionDockingView.Instance.CloseFullScreen(); + ContextMenu.Instance.clearItems(); + ContextMenu.Instance.addItem({ + description: "Full Screen", + event: this.fullScreenClicked + }); + ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); + }; - @action - public minimize = (): void => { - this.props.Document.SetData( - KeyStore.Minimized, - true as boolean, - MinimizedField - ); - SelectionManager.DeselectAll(); - }; + @action + public minimize = (): void => { + this.props.Document.SetData( + KeyStore.Minimized, + true as boolean, + BooleanField + ); + SelectionManager.DeselectAll(); + }; - @action - drop = (e: Event, de: DragManager.DropEvent) => { - if (de.data instanceof DragManager.LinkDragData) { - let sourceDoc: Document = de.data.linkSourceDocumentView.props.Document; - let destDoc: Document = this.props.Document; - if (this.props.isTopMost) { - return; - } - let linkDoc: Document = new Document(); + @action + drop = (e: Event, de: DragManager.DropEvent) => { + if (de.data instanceof DragManager.LinkDragData) { + let sourceDoc: Document = de.data.linkSourceDocumentView.props.Document; + let destDoc: Document = this.props.Document; + if (this.props.isTopMost) { + return; + } + let linkDoc: Document = new Document(); - destDoc.GetTAsync(KeyStore.Prototype, Document).then(protoDest => - sourceDoc.GetTAsync(KeyStore.Prototype, Document).then(protoSrc => - runInAction(() => { - linkDoc.Set(KeyStore.Title, new TextField("New Link")); - linkDoc.Set(KeyStore.LinkDescription, new TextField("")); - linkDoc.Set(KeyStore.LinkTags, new TextField("Default")); + destDoc.GetTAsync(KeyStore.Prototype, Document).then(protoDest => + sourceDoc.GetTAsync(KeyStore.Prototype, Document).then(protoSrc => + runInAction(() => { + linkDoc.Set(KeyStore.Title, new TextField("New Link")); + linkDoc.Set(KeyStore.LinkDescription, new TextField("")); + linkDoc.Set(KeyStore.LinkTags, new TextField("Default")); - let dstTarg = protoDest ? protoDest : destDoc; - let srcTarg = protoSrc ? protoSrc : sourceDoc; - linkDoc.Set(KeyStore.LinkedToDocs, dstTarg); - linkDoc.Set(KeyStore.LinkedFromDocs, srcTarg); - dstTarg.GetOrCreateAsync( - KeyStore.LinkedFromDocs, - ListField, - field => { - (field as ListField).Data.push(linkDoc); - } - ); - srcTarg.GetOrCreateAsync( - KeyStore.LinkedToDocs, - ListField, - field => { - (field as ListField).Data.push(linkDoc); - } + let dstTarg = protoDest ? protoDest : destDoc; + let srcTarg = protoSrc ? protoSrc : sourceDoc; + linkDoc.Set(KeyStore.LinkedToDocs, dstTarg); + linkDoc.Set(KeyStore.LinkedFromDocs, srcTarg); + dstTarg.GetOrCreateAsync( + KeyStore.LinkedFromDocs, + ListField, + field => { + (field as ListField).Data.push(linkDoc); + } + ); + srcTarg.GetOrCreateAsync( + KeyStore.LinkedToDocs, + ListField, + field => { + (field as ListField).Data.push(linkDoc); + } + ); + }) + ) ); - }) - ) - ); - e.stopPropagation(); - } - }; + e.stopPropagation(); + } + }; - onDrop = (e: React.DragEvent) => { - if (e.isDefaultPrevented()) { - return; - } - let text = e.dataTransfer.getData("text/plain"); - if (text && text.startsWith(" { + if (e.isDefaultPrevented()) { + return; + } + let text = e.dataTransfer.getData("text/plain"); + if (text && text.startsWith(" { - e.stopPropagation(); - let moved = - Math.abs(this._downX - e.clientX) > 3 || - Math.abs(this._downY - e.clientY) > 3; - if (moved || e.isDefaultPrevented()) { - e.preventDefault(); - return; - } - e.preventDefault(); + @action + onContextMenu = (e: React.MouseEvent): void => { + e.stopPropagation(); + let moved = + Math.abs(this._downX - e.clientX) > 3 || + Math.abs(this._downY - e.clientY) > 3; + if (moved || e.isDefaultPrevented()) { + e.preventDefault(); + return; + } + e.preventDefault(); - if (!this.isMinimized()) { - ContextMenu.Instance.addItem({ - description: "Minimize", - event: this.minimize - }); - } - ContextMenu.Instance.addItem({ - description: "Full Screen", - event: this.fullScreenClicked - }); - ContextMenu.Instance.addItem({ - description: "Fields", - event: this.fieldsClicked - }); - ContextMenu.Instance.addItem({ - description: "Center", - event: () => this.props.focus(this.props.Document) - }); - ContextMenu.Instance.addItem({ - description: "Open Right", - event: () => - CollectionDockingView.Instance.AddRightSplit(this.props.Document) - }); - ContextMenu.Instance.addItem({ - description: "Copy URL", - event: () => { - Utils.CopyText(ServerUtils.prepend("/doc/" + this.props.Document.Id)); - } - }); - ContextMenu.Instance.addItem({ - description: "Copy ID", - event: () => { - Utils.CopyText(this.props.Document.Id); - } - }); - //ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) }) - ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); - if (!this.topMost) { - // DocumentViews should stop propagation of this event - e.stopPropagation(); - } + if (!this.isMinimized()) { + ContextMenu.Instance.addItem({ + description: "Minimize", + event: this.minimize + }); + } + ContextMenu.Instance.addItem({ + description: "Full Screen", + event: this.fullScreenClicked + }); + ContextMenu.Instance.addItem({ + description: "Fields", + event: this.fieldsClicked + }); + ContextMenu.Instance.addItem({ + description: "Center", + event: () => this.props.focus(this.props.Document) + }); + ContextMenu.Instance.addItem({ + description: "Open Right", + event: () => + CollectionDockingView.Instance.AddRightSplit(this.props.Document) + }); + ContextMenu.Instance.addItem({ + description: "Copy URL", + event: () => { + Utils.CopyText(ServerUtils.prepend("/doc/" + this.props.Document.Id)); + } + }); + ContextMenu.Instance.addItem({ + description: "Copy ID", + event: () => { + Utils.CopyText(this.props.Document.Id); + } + }); + //ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) }) + ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); + if (!this.topMost) { + // DocumentViews should stop propagation of this event + e.stopPropagation(); + } - ContextMenu.Instance.addItem({ - description: "Delete", - event: this.deleteClicked - }); - ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); - SelectionManager.SelectDoc(this, e.ctrlKey); - }; + ContextMenu.Instance.addItem({ + description: "Delete", + event: this.deleteClicked + }); + ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); + SelectionManager.SelectDoc(this, e.ctrlKey); + }; - isMinimized = () => { - let field = this.props.Document.GetT(KeyStore.Minimized, MinimizedField); - if (field && field !== FieldWaiting) { - return field.Data; - } - }; + isMinimized = () => { + let field = this.props.Document.GetT(KeyStore.Minimized, BooleanField); + if (field && field !== FieldWaiting) { + return field.Data; + } + }; - @action - expand = () => { - this.props.Document.SetData( - KeyStore.Minimized, - false as boolean, - MinimizedField - ); - }; + @action + expand = () => { + this.props.Document.SetData( + KeyStore.Minimized, + false as boolean, + BooleanField + ); + }; - isSelected = () => { - return SelectionManager.IsSelected(this); - }; + isSelected = () => { + return SelectionManager.IsSelected(this); + }; - select = (ctrlPressed: boolean) => { - SelectionManager.SelectDoc(this, ctrlPressed); - }; + select = (ctrlPressed: boolean) => { + SelectionManager.SelectDoc(this, ctrlPressed); + }; - render() { - if (!this.props.Document) { - return null; - } + render() { + if (!this.props.Document) { + return null; + } - var scaling = this.props.ContentScaling(); - var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0); - var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0); + var scaling = this.props.ContentScaling(); + var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0); + var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0); - if (this.isMinimized()) { - return ( -
- ); - } else { - var backgroundcolor = this.props.Document.GetText( - KeyStore.BackgroundColor, - "" - ); - return ( -
0 ? nativeWidth.toString() + "px" : "100%", - height: nativeHeight > 0 ? nativeHeight.toString() + "px" : "100%", - transformOrigin: "left top", - transform: `scale(${scaling} , ${scaling})` - }} - onDrop={this.onDrop} - onContextMenu={this.onContextMenu} - onPointerDown={this.onPointerDown} - > - -
- ); + if (this.isMinimized()) { + return ( +
+ ); + } else { + var backgroundcolor = this.props.Document.GetText( + KeyStore.BackgroundColor, + "" + ); + return ( +
0 ? nativeWidth.toString() + "px" : "100%", + height: nativeHeight > 0 ? nativeHeight.toString() + "px" : "100%", + transformOrigin: "left top", + transform: `scale(${scaling} , ${scaling})` + }} + onDrop={this.onDrop} + onContextMenu={this.onContextMenu} + onPointerDown={this.onPointerDown} + > + +
+ ); + } } - } } diff --git a/src/fields/BooleanField.ts b/src/fields/BooleanField.ts new file mode 100644 index 000000000..7378b30a1 --- /dev/null +++ b/src/fields/BooleanField.ts @@ -0,0 +1,25 @@ +import { BasicField } from "./BasicField"; +import { FieldId } from "./Field"; +import { Types } from "../server/Message"; + +export class BooleanField extends BasicField { + constructor(data: boolean = false as boolean, id?: FieldId, save: boolean = true as boolean) { + super(data, save, id); + } + + ToScriptString(): string { + return `new BooleanField("${this.Data}")`; + } + + Copy() { + return new BooleanField(this.Data); + } + + ToJson(): { type: Types; data: boolean; _id: string } { + return { + type: Types.Minimized, + data: this.Data, + _id: this.Id + }; + } +} diff --git a/src/fields/MinimizedField.tsx b/src/fields/MinimizedField.tsx deleted file mode 100644 index 5dfb22e6e..000000000 --- a/src/fields/MinimizedField.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { BasicField } from "./BasicField"; -import { FieldId } from "./Field"; -import { Types } from "../server/Message"; - -export class MinimizedField extends BasicField { - constructor( - data: boolean = false as boolean, - id?: FieldId, - save: boolean = true as boolean - ) { - super(data, save, id); - } - - ToScriptString(): string { - return `new MinimizedField("${this.Data}")`; - } - - Copy() { - return new MinimizedField(this.Data); - } - - ToJson(): { type: Types; data: boolean; _id: string } { - return { - type: Types.Minimized, - data: this.Data, - _id: this.Id - }; - } -} diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts index f3c5e1d1f..d3409abf4 100644 --- a/src/server/ServerUtil.ts +++ b/src/server/ServerUtil.ts @@ -16,69 +16,69 @@ import { VideoField } from "../fields/VideoField"; import { InkField } from "../fields/InkField"; import { PDFField } from "../fields/PDFField"; import { TupleField } from "../fields/TupleField"; -import { MinimizedField } from "../fields/MinimizedField"; +import { BooleanField } from "../fields/BooleanField"; import { HistogramField } from "../client/northstar/dash-fields/HistogramField"; export class ServerUtils { - public static prepend(extension: string): string { - return window.location.origin + extension; - } + 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; - let id: string = obj._id; - let type: Types = obj.type; + public static FromJson(json: any): Field { + let obj = json; + let data: any = obj.data; + let id: string = obj._id; + let type: Types = obj.type; - if (!(data !== undefined && id && type !== undefined)) { - console.log( - "how did you manage to get an object that doesn't have a data or an id?" - ); - return new TextField("Something to fill the space", Utils.GenerateGuid()); - } + if (!(data !== undefined && id && type !== undefined)) { + console.log( + "how did you manage to get an object that doesn't have a data or an id?" + ); + return new TextField("Something to fill the space", Utils.GenerateGuid()); + } - switch (type) { - case Types.Minimized: - return new MinimizedField(data, id, false); - case Types.Number: - return new NumberField(data, id, false); - case Types.Text: - return new TextField(data, id, false); - case Types.Html: - return new HtmlField(data, id, false); - case Types.Web: - return new WebField(new URL(data), id, false); - case Types.RichText: - return new RichTextField(data, id, false); - case Types.Key: - 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: - return ListField.FromJson(id, data); - case Types.Audio: - return new AudioField(new URL(data), id, false); - case Types.Video: - return new VideoField(new URL(data), id, false); - case Types.Tuple: - return new TupleField(data, id, false); - case Types.Ink: - return InkField.FromJson(id, data); - case Types.Document: - let doc: Document = new Document(id, false); - let fields: [string, string][] = data as [string, string][]; - fields.forEach(element => { - doc._proxies.set(element[0], element[1]); - }); - return doc; - default: - throw Error( - "Error, unrecognized field type received from server. If you just created a new field type, be sure to add it here" - ); + switch (type) { + case Types.Minimized: + return new BooleanField(data, id, false); + case Types.Number: + return new NumberField(data, id, false); + case Types.Text: + return new TextField(data, id, false); + case Types.Html: + return new HtmlField(data, id, false); + case Types.Web: + return new WebField(new URL(data), id, false); + case Types.RichText: + return new RichTextField(data, id, false); + case Types.Key: + 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: + return ListField.FromJson(id, data); + case Types.Audio: + return new AudioField(new URL(data), id, false); + case Types.Video: + return new VideoField(new URL(data), id, false); + case Types.Tuple: + return new TupleField(data, id, false); + case Types.Ink: + return InkField.FromJson(id, data); + case Types.Document: + let doc: Document = new Document(id, false); + let fields: [string, string][] = data as [string, string][]; + fields.forEach(element => { + doc._proxies.set(element[0], element[1]); + }); + return doc; + default: + throw Error( + "Error, unrecognized field type received from server. If you just created a new field type, be sure to add it here" + ); + } } - } } diff --git a/src/server/database.ts b/src/server/database.ts index a42d29aac..616251c72 100644 --- a/src/server/database.ts +++ b/src/server/database.ts @@ -1,8 +1,4 @@ -import { action, configure } from 'mobx'; import * as mongodb from 'mongodb'; -import { ObjectID } from 'mongodb'; -import { Transferable } from './Message'; -import { Utils } from '../Utils'; export class Database { public static Instance = new Database() @@ -26,9 +22,9 @@ export class Database { console.log(err.message); console.log(err.errmsg); } - if (res) { - console.log(JSON.stringify(res.result)); - } + // if (res) { + // console.log(JSON.stringify(res.result)); + // } callback() }); } diff --git a/src/server/public/files/upload_a6a70d84ebb65febf7900e29f52cc86d.pdf b/src/server/public/files/upload_a6a70d84ebb65febf7900e29f52cc86d.pdf new file mode 100644 index 000000000..dfd6ab339 Binary files /dev/null and b/src/server/public/files/upload_a6a70d84ebb65febf7900e29f52cc86d.pdf differ -- cgit v1.2.3-70-g09d2 From ef4509360d2056cb06ea512ef0059220bfdfcdcd Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Sat, 6 Apr 2019 03:51:47 -0400 Subject: Small changes --- src/fields/BooleanField.ts | 2 +- src/fields/Document.ts | 2 +- src/server/Message.ts | 186 ++++++++++++++++++++++----------------------- src/server/ServerUtil.ts | 2 +- 4 files changed, 96 insertions(+), 96 deletions(-) (limited to 'src/server/ServerUtil.ts') diff --git a/src/fields/BooleanField.ts b/src/fields/BooleanField.ts index 7378b30a1..d319b4021 100644 --- a/src/fields/BooleanField.ts +++ b/src/fields/BooleanField.ts @@ -17,7 +17,7 @@ export class BooleanField extends BasicField { ToJson(): { type: Types; data: boolean; _id: string } { return { - type: Types.Minimized, + type: Types.Boolean, data: this.Data, _id: this.Id }; diff --git a/src/fields/Document.ts b/src/fields/Document.ts index e4d35e860..538d4ada9 100644 --- a/src/fields/Document.ts +++ b/src/fields/Document.ts @@ -323,7 +323,7 @@ export class Document extends Field { @action SetDataOnPrototype(key: Key, value: T, ctor: { new(): U }, replaceWrongType = true) { this.GetTAsync(KeyStore.Prototype, Document, (f: Opt) => { - f && f.SetData(key, value, ctor); + f && f.SetData(key, value, ctor, replaceWrongType); }); } diff --git a/src/server/Message.ts b/src/server/Message.ts index 29df57419..0274609bb 100644 --- a/src/server/Message.ts +++ b/src/server/Message.ts @@ -1,145 +1,145 @@ import { Utils } from "../Utils"; export class Message { - private name: string; - private guid: string; + private name: string; + private guid: string; - get Name(): string { - return this.name; - } + get Name(): string { + return this.name; + } - get Message(): string { - return this.guid; - } + get Message(): string { + return this.guid; + } - constructor(name: string) { - this.name = name; - this.guid = Utils.GenerateDeterministicGuid(name); - } + constructor(name: string) { + this.name = name; + this.guid = Utils.GenerateDeterministicGuid(name); + } - GetValue() { - return this.Name; - } + GetValue() { + return this.Name; + } } class TestMessageArgs { - hello: string = ""; + hello: string = ""; } export class SetFieldArgs { - field: string; - value: any; + field: string; + value: any; - constructor(f: string, v: any) { - this.field = f; - this.value = v; - } + constructor(f: string, v: any) { + this.field = f; + this.value = v; + } } export class GetFieldArgs { - field: string; + field: string; - constructor(f: string) { - this.field = f; - } + constructor(f: string) { + this.field = f; + } } export enum Types { - Number, - List, - Key, - Image, - Web, - Document, - Text, - RichText, - DocumentReference, - Html, - Video, - Audio, - Ink, - PDF, - Tuple, - HistogramOp, - Minimized + Number, + List, + Key, + Image, + Web, + Document, + Text, + RichText, + DocumentReference, + Html, + Video, + Audio, + Ink, + PDF, + Tuple, + HistogramOp, + Boolean } export class DocumentTransfer implements Transferable { - readonly type = Types.Document; - _id: string; - - constructor( - readonly obj: { type: Types; data: [string, string][]; _id: string } - ) { - this._id = obj._id; - } + readonly type = Types.Document; + _id: string; + + constructor( + readonly obj: { type: Types; data: [string, string][]; _id: string } + ) { + this._id = obj._id; + } } export class ImageTransfer implements Transferable { - readonly type = Types.Image; + readonly type = Types.Image; - constructor(readonly _id: string) {} + constructor(readonly _id: string) { } } export class KeyTransfer implements Transferable { - name: string; - readonly _id: string; - readonly type = Types.Key; - - constructor(i: string, n: string) { - this.name = n; - this._id = i; - } + name: string; + readonly _id: string; + readonly type = Types.Key; + + constructor(i: string, n: string) { + this.name = n; + this._id = i; + } } export class ListTransfer implements Transferable { - type = Types.List; + type = Types.List; - constructor(readonly _id: string) {} + constructor(readonly _id: string) { } } export class NumberTransfer implements Transferable { - readonly type = Types.Number; + readonly type = Types.Number; - constructor(readonly value: number, readonly _id: string) {} + constructor(readonly value: number, readonly _id: string) { } } export class TextTransfer implements Transferable { - value: string; - readonly _id: string; - readonly type = Types.Text; - - constructor(t: string, i: string) { - this.value = t; - this._id = i; - } + value: string; + readonly _id: string; + readonly type = Types.Text; + + constructor(t: string, i: string) { + this.value = t; + this._id = i; + } } export class RichTextTransfer implements Transferable { - value: string; - readonly _id: string; - readonly type = Types.Text; - - constructor(t: string, i: string) { - this.value = t; - this._id = i; - } + value: string; + readonly _id: string; + readonly type = Types.Text; + + constructor(t: string, i: string) { + this.value = t; + this._id = i; + } } export interface Transferable { - readonly _id: string; - readonly type: Types; + readonly _id: string; + readonly type: Types; } export namespace MessageStore { - export const Foo = new Message("Foo"); - export const Bar = new Message("Bar"); - export const AddDocument = new Message("Add Document"); - export const SetField = new Message<{ _id: string; data: any; type: Types }>( - "Set Field" - ); - export const GetField = new Message("Get Field"); - export const GetFields = new Message("Get Fields"); - export const GetDocument = new Message("Get Document"); - export const DeleteAll = new Message("Delete All"); + export const Foo = new Message("Foo"); + export const Bar = new Message("Bar"); + export const AddDocument = new Message("Add Document"); + export const SetField = new Message<{ _id: string; data: any; type: Types }>( + "Set Field" + ); + export const GetField = new Message("Get Field"); + export const GetFields = new Message("Get Fields"); + export const GetDocument = new Message("Get Document"); + export const DeleteAll = new Message("Delete All"); } diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts index d3409abf4..2c2bfd0c9 100644 --- a/src/server/ServerUtil.ts +++ b/src/server/ServerUtil.ts @@ -38,7 +38,7 @@ export class ServerUtils { } switch (type) { - case Types.Minimized: + case Types.Boolean: return new BooleanField(data, id, false); case Types.Number: return new NumberField(data, id, false); -- cgit v1.2.3-70-g09d2 From cf86d1b7917f0317af550293344f784a341bd7b9 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Tue, 9 Apr 2019 02:45:06 -0400 Subject: Added script field and added list of scripts to list field --- src/client/util/Scripting.ts | 21 +++-- .../views/collections/CollectionBaseView.tsx | 21 ++++- .../views/collections/CollectionSchemaView.tsx | 4 +- src/fields/Document.ts | 7 +- src/fields/ListField.ts | 96 ++++++++++++++++++---- src/fields/ScriptField.ts | 64 +++++++++++++++ src/server/Message.ts | 3 +- src/server/ServerUtil.ts | 3 + 8 files changed, 187 insertions(+), 32 deletions(-) create mode 100644 src/fields/ScriptField.ts (limited to 'src/server/ServerUtil.ts') diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index 468484928..9015f21cf 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -30,8 +30,10 @@ export interface ScriptError { export type ScriptResult = ScriptSucccess | ScriptError; -export interface CompileSuccess { - compiled: true; +export interface CompiledScript { + readonly compiled: true; + readonly originalScript: string; + readonly options: Readonly; run(args?: { [name: string]: any }): ScriptResult; } @@ -40,9 +42,9 @@ export interface CompileError { errors: any[]; } -export type CompiledScript = CompileSuccess | CompileError; +export type CompileResult = CompiledScript | CompileError; -function Run(script: string | undefined, customParams: string[], diagnostics: any[]): CompiledScript { +function Run(script: string | undefined, customParams: string[], diagnostics: any[], originalScript: string, options: ScriptOptions): CompileResult { const errors = diagnostics.some(diag => diag.category === ts.DiagnosticCategory.Error); if (errors || !script) { return { compiled: false, errors: diagnostics }; @@ -68,7 +70,7 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an return { success: false, error }; } }; - return { compiled: true, run }; + return { compiled: true, run, originalScript, options }; } interface File { @@ -130,7 +132,7 @@ export interface ScriptOptions { params?: { [name: string]: string }; } -export function CompileScript(script: string, { requiredType = "", addReturn = false, params = {} }: ScriptOptions = {}): CompiledScript { +export function CompileScript(script: string, { requiredType = "", addReturn = false, params = {} }: ScriptOptions = {}): CompileResult { let host = new ScriptingCompilerHost; let paramArray: string[] = []; if ("this" in params) { @@ -140,7 +142,10 @@ export function CompileScript(script: string, { requiredType = "", addReturn = f if (key === "this") continue; paramArray.push(key); } - let paramString = paramArray.map(key => `${key}: ${params[key]}`).join(", "); + let paramString = paramArray.map(key => { + const val = params[key]; + return `${key}: ${val}`; + }).join(", "); let funcScript = `(function(${paramString})${requiredType ? `: ${requiredType}` : ''} { ${addReturn ? `return ${script};` : script} })`; @@ -152,7 +157,7 @@ export function CompileScript(script: string, { requiredType = "", addReturn = f let diagnostics = ts.getPreEmitDiagnostics(program).concat(testResult.diagnostics); - return Run(outputText, paramArray, diagnostics); + return Run(outputText, paramArray, diagnostics, script, { requiredType, addReturn, params }); } export function OrLiteralType(returnType: string): string { diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index 32462a4c1..4afa7cbf6 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -10,6 +10,8 @@ import { ListField } from '../../../fields/ListField'; import { action } from 'mobx'; import { Transform } from '../../util/Transform'; import { observer } from 'mobx-react'; +import { CompileScript } from '../../util/Scripting'; +import { ScriptField } from '../../../fields/ScriptField'; export enum CollectionViewType { Invalid, @@ -106,7 +108,24 @@ export class CollectionBaseView extends React.Component { } else { let proto = props.Document.GetPrototype(); if (!proto || proto === FieldWaiting || !this.createsCycle(proto, doc)) { - props.Document.SetOnPrototype(props.fieldKey, new ListField([doc])); + const field = new ListField([doc]); + // const script = CompileScript(` + // if(added) { + // console.log("added " + field.Title); + // } else { + // console.log("removed " + field.Title); + // } + // `, { + // addReturn: false, + // params: { + // field: Document.name, + // added: "boolean" + // } + // }); + // if (script.compiled) { + // field.addScript(new ScriptField(script)); + // } + props.Document.SetOnPrototype(props.fieldKey, field); } else { return false; diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 52933818f..587f60b3d 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -121,14 +121,14 @@ export class CollectionSchemaView extends CollectionSubView { return field || ""; }} SetValue={(value: string) => { - let script = CompileScript(value, { addReturn: true, params: { this: "Document" } }); + let script = CompileScript(value, { addReturn: true, params: { this: Document.name } }); if (!script.compiled) { return false; } return applyToDoc(props.Document, script.run); }} OnFillDown={(value: string) => { - let script = CompileScript(value, { addReturn: true, params: { this: "Document" } }); + let script = CompileScript(value, { addReturn: true, params: { this: Document.name } }); if (!script.compiled) { return; } diff --git a/src/fields/Document.ts b/src/fields/Document.ts index 4584660fb..60eaf5b51 100644 --- a/src/fields/Document.ts +++ b/src/fields/Document.ts @@ -16,10 +16,7 @@ import { HistogramField } from "../client/northstar/dash-fields/HistogramField"; export class Document extends Field { //TODO tfs: We should probably store FieldWaiting in fields when we request it from the server so that we don't set up multiple server gets for the same document and field - public fields: ObservableMap< - string, - { key: Key; field: Field } - > = new ObservableMap(); + public fields: ObservableMap = new ObservableMap(); public _proxies: ObservableMap = new ObservableMap(); constructor(id?: string, save: boolean = true) { @@ -406,7 +403,7 @@ export class Document extends Field { } else if (field) { copy.Set(key!, field.Copy()); - } + } }); } }); diff --git a/src/fields/ListField.ts b/src/fields/ListField.ts index b6eab5f86..8311e737b 100644 --- a/src/fields/ListField.ts +++ b/src/fields/ListField.ts @@ -5,12 +5,18 @@ import { Types } from "../server/Message"; import { BasicField } from "./BasicField"; import { Field, FieldId } from "./Field"; import { FieldMap } from "../client/SocketStub"; +import { ScriptField } from "./ScriptField"; export class ListField extends BasicField { private _proxies: string[] = []; - constructor(data: T[] = [], id?: FieldId, save: boolean = true) { + private _scriptIds: string[] = []; + private scripts: ScriptField[] = []; + + constructor(data: T[] = [], scripts: ScriptField[] = [], id?: FieldId, save: boolean = true) { super(data, save, id); + this.scripts = scripts; this.updateProxies(); + this._scriptIds = this.scripts.map(script => script.Id); if (save) { Server.UpdateField(this); } @@ -25,17 +31,22 @@ export class ListField extends BasicField { this.observeDisposer(); } this.observeDisposer = observe(this.Data as IObservableArray, (change: IArrayChange | IArraySplice) => { + const target = change.object; this.updateProxies(); if (change.type === "splice") { + this.runScripts(change.removed, false); UndoManager.AddEvent({ - undo: () => this.Data.splice(change.index, change.addedCount, ...change.removed), - redo: () => this.Data.splice(change.index, change.removedCount, ...change.added) + undo: () => target.splice(change.index, change.addedCount, ...change.removed), + redo: () => target.splice(change.index, change.removedCount, ...change.added) }); + this.runScripts(change.added, true); } else { + this.runScripts([change.oldValue], false); UndoManager.AddEvent({ - undo: () => this.Data[change.index] = change.oldValue, - redo: () => this.Data[change.index] = change.newValue + undo: () => target[change.index] = change.oldValue, + redo: () => target[change.index] = change.newValue }); + this.runScripts([change.newValue], true); } if (!this._processingServerUpdate) { Server.UpdateField(this); @@ -43,19 +54,60 @@ export class ListField extends BasicField { }); } + private runScripts(fields: T[], added: boolean) { + for (const script of this.scripts) { + this.runScript(fields, script, added); + } + } + + private runScript(fields: T[], script: ScriptField, added: boolean) { + if (!this._processingServerUpdate) { + for (const field of fields) { + script.script.run({ field, added }); + } + } + } + + addScript(script: ScriptField) { + this.scripts.push(script); + this._scriptIds.push(script.Id); + + this.runScript(this.Data, script, true); + UndoManager.AddEvent({ + undo: () => this.removeScript(script), + redo: () => this.addScript(script), + }); + Server.UpdateField(this); + } + + removeScript(script: ScriptField) { + const index = this.scripts.indexOf(script); + if (index === -1) { + return; + } + this.scripts.splice(index, 1); + this._scriptIds.splice(index, 1); + UndoManager.AddEvent({ + undo: () => this.addScript(script), + redo: () => this.removeScript(script), + }); + this.runScript(this.Data, script, false); + Server.UpdateField(this); + } + protected setData(value: T[]) { + this.runScripts(this.data, false); + this.data = observable(value); this.updateProxies(); this.observeList(); + this.runScripts(this.data, true); } private updateProxies() { this._proxies = this.Data.map(field => field.Id); } - UpdateFromServer(fields: string[]) { - this._proxies = fields; - } private arraysEqual(a: any[], b: any[]) { if (a === b) return true; if (a === null || b === null) return false; @@ -73,7 +125,7 @@ export class ListField extends BasicField { } init(callback: (field: Field) => any) { - Server.GetFields(this._proxies, action((fields: FieldMap) => { + const fieldsPromise = Server.GetFields(this._proxies).then(action((fields: FieldMap) => { if (!this.arraysEqual(this._proxies, this.data.map(field => field.Id))) { var dataids = this.data.map(d => d.Id); var proxies = this._proxies.map(p => p); @@ -102,8 +154,13 @@ export class ListField extends BasicField { } this._processingServerUpdate = false; } - callback(this); })); + + const scriptsPromise = Server.GetFields(this._scriptIds).then((fields: FieldMap) => { + this.scripts = this._scriptIds.map(id => fields[id] as ScriptField); + }); + + Promise.all([fieldsPromise, scriptsPromise]).then(() => callback(this)); } ToScriptString(): string { @@ -114,17 +171,26 @@ export class ListField extends BasicField { return new ListField(this.Data); } - ToJson(): { type: Types, data: string[], _id: string } { + + UpdateFromServer(data: { fields: string[], scripts: string[] }) { + this._proxies = data.fields; + this._scriptIds = data.scripts; + } + ToJson(): { type: Types, data: { fields: string[], scripts: string[] }, _id: string } { return { type: Types.List, - data: this._proxies || [], + data: { + fields: this._proxies, + scripts: this._scriptIds, + }, _id: this.Id }; } - static FromJson(id: string, ids: string[]): ListField { - let list = new ListField([], id, false); - list._proxies = ids; + static FromJson(id: string, data: { fields: string[], scripts: string[] }): ListField { + let list = new ListField([], [], id, false); + list._proxies = data.fields; + list._scriptIds = data.scripts; return list; } } \ No newline at end of file diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts new file mode 100644 index 000000000..24c1d9b3a --- /dev/null +++ b/src/fields/ScriptField.ts @@ -0,0 +1,64 @@ +import { Field, FieldId } from "./Field"; +import { Types } from "../server/Message"; +import { CompileScript, ScriptOptions, CompiledScript } from "../client/util/Scripting"; +import { Server } from "../client/Server"; + +export interface ScriptData { + script: string; + options: ScriptOptions; +} + +export class ScriptField extends Field { + readonly script: CompiledScript; + + constructor(script: CompiledScript, id?: FieldId, save: boolean = true) { + super(id); + + this.script = script; + + if (save) { + Server.UpdateField(this); + } + } + + static FromJson(id: string, data: ScriptData): ScriptField { + const script = CompileScript(data.script, data.options); + if (!script.compiled) { + throw new Error("Can't compile script"); + } + return new ScriptField(script, id, false); + } + + ToScriptString() { + return "new ScriptField(...)"; + } + + GetValue() { + return this.script; + } + + TrySetValue(): boolean { + throw new Error("Script fields currently can't be modified"); + } + + UpdateFromServer() { + throw new Error("Script fields currently can't be updated"); + } + + ToJson(): { _id: string, type: Types, data: ScriptData } { + const { options, originalScript } = this.script; + return { + _id: this.Id, + type: Types.Script, + data: { + script: originalScript, + options + }, + }; + } + + Copy(): Field { + //Script fields are currently immutable, so we can fake copy them + return this; + } +} \ No newline at end of file diff --git a/src/server/Message.ts b/src/server/Message.ts index 0274609bb..d22e5c17c 100644 --- a/src/server/Message.ts +++ b/src/server/Message.ts @@ -61,7 +61,8 @@ export enum Types { PDF, Tuple, HistogramOp, - Boolean + Boolean, + Script, } export class DocumentTransfer implements Transferable { diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts index 2c2bfd0c9..0973f82b1 100644 --- a/src/server/ServerUtil.ts +++ b/src/server/ServerUtil.ts @@ -18,6 +18,7 @@ import { PDFField } from "../fields/PDFField"; import { TupleField } from "../fields/TupleField"; import { BooleanField } from "../fields/BooleanField"; import { HistogramField } from "../client/northstar/dash-fields/HistogramField"; +import { ScriptField } from "../fields/ScriptField"; export class ServerUtils { public static prepend(extension: string): string { @@ -60,6 +61,8 @@ export class ServerUtils { return new PDFField(new URL(data), id, false); case Types.List: return ListField.FromJson(id, data); + case Types.Script: + return ScriptField.FromJson(id, data); case Types.Audio: return new AudioField(new URL(data), id, false); case Types.Video: -- cgit v1.2.3-70-g09d2 From d6fd8338a440085c1ba865c4771c0871208e961e Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 13 Apr 2019 19:39:37 -0400 Subject: cleaning up network data transfer. --- src/Utils.ts | 4 +- src/client/Server.ts | 20 +-- src/client/SocketStub.ts | 58 ++------- src/client/northstar/dash-fields/HistogramField.ts | 5 +- src/fields/AudioField.ts | 4 +- src/fields/BooleanField.ts | 4 +- src/fields/Document.ts | 17 +-- src/fields/DocumentReference.ts | 4 +- src/fields/Field.ts | 4 +- src/fields/HtmlField.ts | 4 +- src/fields/ImageField.ts | 4 +- src/fields/InkField.ts | 4 +- src/fields/Key.ts | 4 +- src/fields/ListField.ts | 4 +- src/fields/NumberField.ts | 4 +- src/fields/PDFField.ts | 4 +- src/fields/RichTextField.ts | 4 +- src/fields/ScriptField.ts | 4 +- src/fields/TextField.ts | 4 +- src/fields/TupleField.ts | 4 +- src/fields/VideoField.ts | 4 +- src/fields/WebField.ts | 4 +- src/server/Message.ts | 137 ++------------------- src/server/ServerUtil.ts | 99 ++++++--------- src/server/database.ts | 64 +++++----- src/server/index.ts | 8 +- 26 files changed, 150 insertions(+), 330 deletions(-) (limited to 'src/server/ServerUtil.ts') diff --git a/src/Utils.ts b/src/Utils.ts index c629bc263..2e672db9a 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -1,7 +1,7 @@ import v4 = require('uuid/v4'); import v5 = require("uuid/v5"); import { Socket } from 'socket.io'; -import { Message, Types } from './server/Message'; +import { Message, Types, Transferable } from './server/Message'; import { Document } from './fields/Document'; export class Utils { @@ -48,7 +48,7 @@ export class Utils { if (this.logFilter !== undefined && this.logFilter !== message.type) { return; } - let idString = (message._id || message.id || "").padStart(36, ' '); + let idString = (message.id || message.id || "").padStart(36, ' '); prefix = prefix.padEnd(16, ' '); console.log(`${prefix}: ${idString}, ${receiving ? 'receiving' : 'sending'} ${messageName} with data ${JSON.stringify(message)}`); } diff --git a/src/client/Server.ts b/src/client/Server.ts index 3bbbebe72..66e9878d9 100644 --- a/src/client/Server.ts +++ b/src/client/Server.ts @@ -12,7 +12,6 @@ export class Server { static Socket: SocketIOClient.Socket = OpenSocket(`${window.location.protocol}//${window.location.hostname}:4321`); static GUID: string = Utils.GenerateGuid(); - // Retrieves the cached value of the field and sends a request to the server for the real value (if it's not cached). // Call this is from within a reaction and test whether the return value is FieldWaiting. public static GetField(fieldid: FieldId): Promise>; @@ -127,13 +126,6 @@ export class Server { } } - public static AddDocument(document: Document) { - SocketStub.SEND_ADD_DOCUMENT(document); - } - public static AddDocumentField(doc: Document, key: Key, value: Field) { - console.log("Add doc field " + doc.Title + " " + key.Name + " fid " + value.Id + " " + value); - SocketStub.SEND_ADD_DOCUMENT_FIELD(doc, key, value); - } public static DeleteDocumentField(doc: Document, key: Key) { SocketStub.SEND_DELETE_DOCUMENT_FIELD(doc, key); } @@ -161,18 +153,18 @@ export class Server { } @action - static updateField(field: { _id: string, data: any, type: Types }) { - if (Server.ClientFieldsCached.has(field._id)) { - var f = Server.ClientFieldsCached.get(field._id); + static updateField(field: { id: string, data: any, type: Types }) { + if (Server.ClientFieldsCached.has(field.id)) { + var f = Server.ClientFieldsCached.get(field.id); if (f) { - // console.log("Applying : " + field._id); + // console.log("Applying : " + field.id); f.UpdateFromServer(field.data); f.init(emptyFunction); } else { - // console.log("Not applying wa : " + field._id); + // console.log("Not applying wa : " + field.id); } } else { - // console.log("Not applying mi : " + field._id); + // console.log("Not applying mi : " + field.id); } } } diff --git a/src/client/SocketStub.ts b/src/client/SocketStub.ts index 5e2ca6a98..382a81f66 100644 --- a/src/client/SocketStub.ts +++ b/src/client/SocketStub.ts @@ -2,7 +2,7 @@ import { Key } from "../fields/Key"; import { Field, FieldId, Opt } from "../fields/Field"; import { ObservableMap } from "mobx"; import { Document } from "../fields/Document"; -import { MessageStore, DocumentTransfer } from "../server/Message"; +import { MessageStore, Transferable } from "../server/Message"; import { Utils } from "../Utils"; import { Server } from "./Server"; import { ServerUtils } from "../server/ServerUtil"; @@ -16,35 +16,12 @@ export interface FieldMap { export class SocketStub { static FieldStore: ObservableMap = new ObservableMap(); - public static SEND_ADD_DOCUMENT(document: Document) { - - // Send a serialized version of the document to the server - // ...SOCKET(ADD_DOCUMENT, serialied document) - - // server stores each document field in its repository of stored fields - // document.fields.forEach((f, key) => this.FieldStore.set((f as Field).Id, f as Field)); - // let strippedDoc = new Document(document.Id); - // document.fields.forEach((f, key) => { - // if (f) { - // // let args: SetFieldArgs = new SetFieldArgs(f.Id, f.GetValue()) - // let args: Transferable = f.ToJson() - // Utils.Emit(Server.Socket, MessageStore.SetField, args) - // } - // }) - - // // server stores stripped down document (w/ only field id proxies) in the field store - // this.FieldStore.set(document.Id, new Document(document.Id)); - // document.fields.forEach((f, key) => (this.FieldStore.get(document.Id) as Document)._proxies.set(key.Id, (f as Field).Id)); - - console.log("sending " + document.Title); - Utils.Emit(Server.Socket, MessageStore.AddDocument, new DocumentTransfer(document.ToJson())); - } public static SEND_FIELD_REQUEST(fieldid: FieldId): Promise>; public static SEND_FIELD_REQUEST(fieldid: FieldId, callback: (field: Opt) => void): void; public static SEND_FIELD_REQUEST(fieldid: FieldId, callback?: (field: Opt) => void): Promise> | void { let fn = function (cb: (field: Opt) => void) { - Utils.EmitCallback(Server.Socket, MessageStore.GetField, fieldid, (field: any) => { + Utils.EmitCallback(Server.Socket, MessageStore.GetField, fieldid, (field: Transferable) => { if (field) { ServerUtils.FromJson(field).init(cb); } else { @@ -60,36 +37,15 @@ export class SocketStub { } public static SEND_FIELDS_REQUEST(fieldIds: FieldId[], callback: (fields: FieldMap) => any) { - Utils.EmitCallback(Server.Socket, MessageStore.GetFields, fieldIds, (fields: any[]) => { - let fieldMap: any = {}; - let proms: Promise[] = []; - for (let field of fields) { - let f = ServerUtils.FromJson(field); - fieldMap[field._id] = f; - proms.push(new Promise(res => f.init(res))); - } + Utils.EmitCallback(Server.Socket, MessageStore.GetFields, fieldIds, (fields: Transferable[]) => { + let fieldMap: FieldMap = {}; + fields.map(field => fieldMap[field.id] = ServerUtils.FromJson(field)); + let proms = Object.values(fieldMap).map(val => + new Promise(resolve => val!.init(resolve))); Promise.all(proms).then(() => callback(fieldMap)); }); } - public static SEND_ADD_DOCUMENT_FIELD(doc: Document, key: Key, value: Field) { - - // Send a serialized version of the field to the server along with the - // associated info of the document id and key where it is used. - - // ...SOCKET(ADD_DOCUMENT_FIELD, document id, key id, serialized field) - - // server updates its document to hold a proxy mapping from key => fieldId - var document = this.FieldStore.get(doc.Id) as Document; - if (document) { - document._proxies.set(key.Id, value.Id); - } - - // server adds the field to its repository of fields - this.FieldStore.set(value.Id, value); - // Utils.Emit(Server.Socket, MessageStore.AddDocument, new DocumentTransfer(doc.ToJson())) - } - public static SEND_DELETE_DOCUMENT_FIELD(doc: Document, key: Key) { // Send a request to delete the field stored under the specified key from the document diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts index 6abde4677..82de51d56 100644 --- a/src/client/northstar/dash-fields/HistogramField.ts +++ b/src/client/northstar/dash-fields/HistogramField.ts @@ -35,12 +35,11 @@ export class HistogramField extends BasicField { } - ToJson(): { type: Types, data: string, _id: string } { + ToJson() { return { type: Types.HistogramOp, - data: this.toString(), - _id: this.Id + id: this.Id }; } diff --git a/src/fields/AudioField.ts b/src/fields/AudioField.ts index 996d2556d..87e47a715 100644 --- a/src/fields/AudioField.ts +++ b/src/fields/AudioField.ts @@ -20,11 +20,11 @@ export class AudioField extends BasicField { return new AudioField(this.Data); } - ToJson(): { type: Types, data: string, _id: string } { + ToJson() { return { type: Types.Audio, data: this.Data.href, - _id: this.Id + id: this.Id }; } diff --git a/src/fields/BooleanField.ts b/src/fields/BooleanField.ts index d319b4021..d49bfe82b 100644 --- a/src/fields/BooleanField.ts +++ b/src/fields/BooleanField.ts @@ -15,11 +15,11 @@ export class BooleanField extends BasicField { return new BooleanField(this.Data); } - ToJson(): { type: Types; data: boolean; _id: string } { + ToJson() { return { type: Types.Boolean, data: this.Data, - _id: this.Id + id: this.Id }; } } diff --git a/src/fields/Document.ts b/src/fields/Document.ts index 628fe684c..7cf784f0e 100644 --- a/src/fields/Document.ts +++ b/src/fields/Document.ts @@ -26,6 +26,12 @@ export class Document extends Field { Server.UpdateField(this); } } + static FromJson(data: any, id: string, save: boolean): Document { + let doc = new Document(id, save); + let fields = data as [string, string][]; + fields.forEach(pair => doc._proxies.set(pair[0], pair[1])); + return doc; + } UpdateFromServer(data: [string, string][]) { for (const key in data) { @@ -410,18 +416,15 @@ export class Document extends Field { return copy; } - ToJson(): { type: Types; data: [string, string][]; _id: string } { + ToJson() { let fields: [string, string][] = []; - this._proxies.forEach((field, key) => { - if (field) { - fields.push([key, field]); - } - }); + this._proxies.forEach((field, key) => + field && fields.push([key, field])); return { type: Types.Document, data: fields, - _id: this.Id + id: this.Id }; } } diff --git a/src/fields/DocumentReference.ts b/src/fields/DocumentReference.ts index 6c0c1ef82..303754177 100644 --- a/src/fields/DocumentReference.ts +++ b/src/fields/DocumentReference.ts @@ -47,11 +47,11 @@ export class DocumentReference extends Field { return ""; } - ToJson(): { type: Types, data: FieldId, _id: string } { + ToJson() { return { type: Types.DocumentReference, data: this.document.Id, - _id: this.Id + id: this.Id }; } } \ No newline at end of file diff --git a/src/fields/Field.ts b/src/fields/Field.ts index d9db23b9e..3b3e95c2b 100644 --- a/src/fields/Field.ts +++ b/src/fields/Field.ts @@ -1,6 +1,6 @@ import { Utils } from "../Utils"; -import { Types } from "../server/Message"; +import { Types, Transferable } from "../server/Message"; import { computed } from "mobx"; export function Cast(field: FieldValue, ctor: { new(): T }): Opt { @@ -65,5 +65,5 @@ export abstract class Field { abstract Copy(): Field; - abstract ToJson(): { _id: string, type: Types, data: any }; + abstract ToJson(): Transferable; } \ No newline at end of file diff --git a/src/fields/HtmlField.ts b/src/fields/HtmlField.ts index 65665cf7a..a1d880070 100644 --- a/src/fields/HtmlField.ts +++ b/src/fields/HtmlField.ts @@ -15,11 +15,11 @@ export class HtmlField extends BasicField { return new HtmlField(this.Data); } - ToJson(): { _id: string; type: Types; data: string; } { + ToJson() { return { type: Types.Html, data: this.Data, - _id: this.Id, + id: this.Id, }; } } \ No newline at end of file diff --git a/src/fields/ImageField.ts b/src/fields/ImageField.ts index dd843026f..bce20f242 100644 --- a/src/fields/ImageField.ts +++ b/src/fields/ImageField.ts @@ -19,11 +19,11 @@ export class ImageField extends BasicField { return new ImageField(this.Data); } - ToJson(): { type: Types, data: string, _id: string } { + ToJson() { return { type: Types.Image, data: this.Data.href, - _id: this.Id + id: this.Id }; } } \ No newline at end of file diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts index ab706ee30..2eacd7d0c 100644 --- a/src/fields/InkField.ts +++ b/src/fields/InkField.ts @@ -31,11 +31,11 @@ export class InkField extends BasicField { return new InkField(this.Data); } - ToJson(): { _id: string; type: Types; data: any; } { + ToJson() { return { type: Types.Ink, data: this.Data, - _id: this.Id, + id: this.Id, }; } diff --git a/src/fields/Key.ts b/src/fields/Key.ts index c7f806b88..57e2dadf0 100644 --- a/src/fields/Key.ts +++ b/src/fields/Key.ts @@ -40,11 +40,11 @@ export class Key extends Field { return name; } - ToJson(): { type: Types, data: string, _id: string } { + ToJson() { return { type: Types.Key, data: this.name, - _id: this.Id + id: this.Id }; } } \ No newline at end of file diff --git a/src/fields/ListField.ts b/src/fields/ListField.ts index 8311e737b..e24099126 100644 --- a/src/fields/ListField.ts +++ b/src/fields/ListField.ts @@ -176,14 +176,14 @@ export class ListField extends BasicField { this._proxies = data.fields; this._scriptIds = data.scripts; } - ToJson(): { type: Types, data: { fields: string[], scripts: string[] }, _id: string } { + ToJson() { return { type: Types.List, data: { fields: this._proxies, scripts: this._scriptIds, }, - _id: this.Id + id: this.Id }; } diff --git a/src/fields/NumberField.ts b/src/fields/NumberField.ts index 45b920e31..7eea360c0 100644 --- a/src/fields/NumberField.ts +++ b/src/fields/NumberField.ts @@ -15,9 +15,9 @@ export class NumberField extends BasicField { return new NumberField(this.Data); } - ToJson(): { _id: string, type: Types, data: number } { + ToJson() { return { - _id: this.Id, + id: this.Id, type: Types.Number, data: this.Data }; diff --git a/src/fields/PDFField.ts b/src/fields/PDFField.ts index 65e179894..718a1a4c0 100644 --- a/src/fields/PDFField.ts +++ b/src/fields/PDFField.ts @@ -22,11 +22,11 @@ export class PDFField extends BasicField { return `new PDFField("${this.Data}")`; } - ToJson(): { type: Types, data: string, _id: string } { + ToJson() { return { type: Types.PDF, data: this.Data.href, - _id: this.Id + id: this.Id }; } diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts index 6f7b3074a..f53f48ca6 100644 --- a/src/fields/RichTextField.ts +++ b/src/fields/RichTextField.ts @@ -15,11 +15,11 @@ export class RichTextField extends BasicField { return new RichTextField(this.Data); } - ToJson(): { type: Types, data: string, _id: string } { + ToJson() { return { type: Types.RichText, data: this.Data, - _id: this.Id + id: this.Id }; } diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index 34c8a7544..7f87be45d 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -73,7 +73,7 @@ export class ScriptField extends Field { }); } - ToJson(): { _id: string, type: Types, data: ScriptData } { + ToJson() { const { options, originalScript } = this.script; let capturedIds: { [id: string]: string } = {}; for (const capt in options.capturedVariables) { @@ -85,7 +85,7 @@ export class ScriptField extends Field { }; delete (opts as any).capturedVariables; return { - _id: this.Id, + id: this.Id, type: Types.Script, data: { script: originalScript, diff --git a/src/fields/TextField.ts b/src/fields/TextField.ts index 69d26f42f..ddedec9b1 100644 --- a/src/fields/TextField.ts +++ b/src/fields/TextField.ts @@ -15,11 +15,11 @@ export class TextField extends BasicField { return new TextField(this.Data); } - ToJson(): { type: Types, data: string, _id: string } { + ToJson() { return { type: Types.Text, data: this.Data, - _id: this.Id + id: this.Id }; } } \ No newline at end of file diff --git a/src/fields/TupleField.ts b/src/fields/TupleField.ts index ad0f6f350..347f1fa05 100644 --- a/src/fields/TupleField.ts +++ b/src/fields/TupleField.ts @@ -49,11 +49,11 @@ export class TupleField extends BasicField<[T, U]> { return new TupleField(this.Data); } - ToJson(): { type: Types, data: [T, U], _id: string } { + ToJson() { return { type: Types.Tuple, data: this.Data, - _id: this.Id + id: this.Id }; } } \ No newline at end of file diff --git a/src/fields/VideoField.ts b/src/fields/VideoField.ts index d7cd7e968..838b811b1 100644 --- a/src/fields/VideoField.ts +++ b/src/fields/VideoField.ts @@ -19,11 +19,11 @@ export class VideoField extends BasicField { return new VideoField(this.Data); } - ToJson(): { type: Types, data: string, _id: string } { + ToJson() { return { type: Types.Video, data: this.Data.href, - _id: this.Id + id: this.Id }; } diff --git a/src/fields/WebField.ts b/src/fields/WebField.ts index 6023e9e6b..8b276a552 100644 --- a/src/fields/WebField.ts +++ b/src/fields/WebField.ts @@ -19,11 +19,11 @@ export class WebField extends BasicField { return new WebField(this.Data); } - ToJson(): { type: Types, data: string, _id: string } { + ToJson() { return { type: Types.Web, data: this.Data.href, - _id: this.Id + id: this.Id }; } diff --git a/src/server/Message.ts b/src/server/Message.ts index d22e5c17c..bbe4ffcad 100644 --- a/src/server/Message.ts +++ b/src/server/Message.ts @@ -1,146 +1,35 @@ import { Utils } from "../Utils"; export class Message { - private name: string; - private guid: string; - - get Name(): string { - return this.name; - } - - get Message(): string { - return this.guid; - } + private _name: string; + private _guid: string; constructor(name: string) { - this.name = name; - this.guid = Utils.GenerateDeterministicGuid(name); - } - - GetValue() { - return this.Name; - } -} - -class TestMessageArgs { - hello: string = ""; -} - -export class SetFieldArgs { - field: string; - value: any; - - constructor(f: string, v: any) { - this.field = f; - this.value = v; + this._name = name; + this._guid = Utils.GenerateDeterministicGuid(name); } -} -export class GetFieldArgs { - field: string; - - constructor(f: string) { - this.field = f; - } + get Name(): string { return this._name; } + get Message(): string { return this._guid; } } export enum Types { - Number, - List, - Key, - Image, - Web, - Document, - Text, - RichText, - DocumentReference, - Html, - Video, - Audio, - Ink, - PDF, - Tuple, - HistogramOp, - Boolean, - Script, -} - -export class DocumentTransfer implements Transferable { - readonly type = Types.Document; - _id: string; - - constructor( - readonly obj: { type: Types; data: [string, string][]; _id: string } - ) { - this._id = obj._id; - } -} - -export class ImageTransfer implements Transferable { - readonly type = Types.Image; - - constructor(readonly _id: string) { } -} - -export class KeyTransfer implements Transferable { - name: string; - readonly _id: string; - readonly type = Types.Key; - - constructor(i: string, n: string) { - this.name = n; - this._id = i; - } -} - -export class ListTransfer implements Transferable { - type = Types.List; - - constructor(readonly _id: string) { } -} - -export class NumberTransfer implements Transferable { - readonly type = Types.Number; - - constructor(readonly value: number, readonly _id: string) { } -} - -export class TextTransfer implements Transferable { - value: string; - readonly _id: string; - readonly type = Types.Text; - - constructor(t: string, i: string) { - this.value = t; - this._id = i; - } -} - -export class RichTextTransfer implements Transferable { - value: string; - readonly _id: string; - readonly type = Types.Text; - - constructor(t: string, i: string) { - this.value = t; - this._id = i; - } + Number, List, Key, Image, Web, Document, Text, RichText, DocumentReference, + Html, Video, Audio, Ink, PDF, Tuple, HistogramOp, Boolean, Script, } export interface Transferable { - readonly _id: string; + readonly id: string; readonly type: Types; + readonly data?: any; } export namespace MessageStore { export const Foo = new Message("Foo"); export const Bar = new Message("Bar"); - export const AddDocument = new Message("Add Document"); - export const SetField = new Message<{ _id: string; data: any; type: Types }>( - "Set Field" - ); - export const GetField = new Message("Get Field"); - export const GetFields = new Message("Get Fields"); + export const SetField = new Message("Set Field"); // send Transferable (no reply) + export const GetField = new Message("Get Field"); // send string 'id' get Transferable back + export const GetFields = new Message("Get Fields"); // send string[] of 'id' get Transferable[] back export const GetDocument = new Message("Get Document"); export const DeleteAll = new Message("Delete All"); } diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts index 0973f82b1..818230c1a 100644 --- a/src/server/ServerUtil.ts +++ b/src/server/ServerUtil.ts @@ -1,83 +1,56 @@ -import { Field } from "./../fields/Field"; -import { TextField } from "./../fields/TextField"; -import { NumberField } from "./../fields/NumberField"; -import { RichTextField } from "./../fields/RichTextField"; -import { Key } from "./../fields/Key"; -import { ImageField } from "./../fields/ImageField"; -import { ListField } from "./../fields/ListField"; -import { Document } from "./../fields/Document"; -import { Server } from "./../client/Server"; -import { Types } from "./Message"; -import { Utils } from "../Utils"; -import { HtmlField } from "../fields/HtmlField"; -import { WebField } from "../fields/WebField"; +import { HistogramField } from "../client/northstar/dash-fields/HistogramField"; import { AudioField } from "../fields/AudioField"; -import { VideoField } from "../fields/VideoField"; +import { BooleanField } from "../fields/BooleanField"; +import { HtmlField } from "../fields/HtmlField"; import { InkField } from "../fields/InkField"; import { PDFField } from "../fields/PDFField"; -import { TupleField } from "../fields/TupleField"; -import { BooleanField } from "../fields/BooleanField"; -import { HistogramField } from "../client/northstar/dash-fields/HistogramField"; import { ScriptField } from "../fields/ScriptField"; +import { TupleField } from "../fields/TupleField"; +import { VideoField } from "../fields/VideoField"; +import { WebField } from "../fields/WebField"; +import { Utils } from "../Utils"; +import { Document } from "./../fields/Document"; +import { Field } from "./../fields/Field"; +import { ImageField } from "./../fields/ImageField"; +import { Key } from "./../fields/Key"; +import { ListField } from "./../fields/ListField"; +import { NumberField } from "./../fields/NumberField"; +import { RichTextField } from "./../fields/RichTextField"; +import { TextField } from "./../fields/TextField"; +import { Transferable, Types } from "./Message"; 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; - let id: string = obj._id; - let type: Types = obj.type; + public static FromJson(json: Transferable): Field { - if (!(data !== undefined && id && type !== undefined)) { + if (!(json.data !== undefined && json.id && json.type !== undefined)) { console.log( "how did you manage to get an object that doesn't have a data or an id?" ); return new TextField("Something to fill the space", Utils.GenerateGuid()); } - switch (type) { - case Types.Boolean: - return new BooleanField(data, id, false); - case Types.Number: - return new NumberField(data, id, false); - case Types.Text: - return new TextField(data, id, false); - case Types.Html: - return new HtmlField(data, id, false); - case Types.Web: - return new WebField(new URL(data), id, false); - case Types.RichText: - return new RichTextField(data, id, false); - case Types.Key: - 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: - return ListField.FromJson(id, data); - case Types.Script: - return ScriptField.FromJson(id, data); - case Types.Audio: - return new AudioField(new URL(data), id, false); - case Types.Video: - return new VideoField(new URL(data), id, false); - case Types.Tuple: - return new TupleField(data, id, false); - case Types.Ink: - return InkField.FromJson(id, data); - case Types.Document: - let doc: Document = new Document(id, false); - let fields: [string, string][] = data as [string, string][]; - fields.forEach(element => { - doc._proxies.set(element[0], element[1]); - }); - return doc; + switch (json.type) { + case Types.Boolean: return new BooleanField(json.data, json.id, false); + case Types.Number: return new NumberField(json.data, json.id, false); + case Types.Text: return new TextField(json.data, json.id, false); + case Types.Html: return new HtmlField(json.data, json.id, false); + case Types.Web: return new WebField(new URL(json.data), json.id, false); + case Types.RichText: return new RichTextField(json.data, json.id, false); + case Types.Key: return new Key(json.data, json.id, false); + case Types.Image: return new ImageField(new URL(json.data), json.id, false); + case Types.HistogramOp: return HistogramField.FromJson(json.id, json.data); + case Types.PDF: return new PDFField(new URL(json.data), json.id, false); + case Types.List: return ListField.FromJson(json.id, json.data); + case Types.Script: return ScriptField.FromJson(json.id, json.data); + case Types.Audio: return new AudioField(new URL(json.data), json.id, false); + case Types.Video: return new VideoField(new URL(json.data), json.id, false); + case Types.Tuple: return new TupleField(json.data, json.id, false); + case Types.Ink: return InkField.FromJson(json.id, json.data); + case Types.Document: return Document.FromJson(json.data, json.id, false); default: throw Error( "Error, unrecognized field type received from server. If you just created a new field type, be sure to add it here" diff --git a/src/server/database.ts b/src/server/database.ts index e08385d98..c51d4a3f6 100644 --- a/src/server/database.ts +++ b/src/server/database.ts @@ -1,47 +1,55 @@ import * as mongodb from 'mongodb'; +import { Transferable } from './Message'; export class Database { public static DocumentsCollection = 'documents'; public static Instance = new Database(); private MongoClient = mongodb.MongoClient; private url = 'mongodb://localhost:27017/Dash'; - private currentWrites: { [_id: string]: Promise } = {}; + private currentWrites: { [id: string]: Promise } = {}; private db?: mongodb.Db; constructor() { this.MongoClient.connect(this.url, (err, client) => this.db = client.db()); } - public update(id: string, value: any, callback: () => void, collectionName = Database.DocumentsCollection) { + public update(id: string, value: any, callback: () => void) { if (this.db) { - let collection = this.db.collection(collectionName); + let collection = this.db.collection('documents'); const prom = this.currentWrites[id]; + let newProm: Promise; const run = (): Promise => { - let newProm = new Promise(resolve => { - collection.updateOne({ _id: id }, { $set: value }, { upsert: true } - , (err, res) => { - if (err) { - console.log(err.message); - console.log(err.errmsg); - } - // if (res) { - // console.log(JSON.stringify(res.result)); - // } - if (this.currentWrites[id] === newProm) { - delete this.currentWrites[id]; - } - resolve(); - callback(); - }); + return new Promise(resolve => { + collection.updateOne({ _id: id }, { $set: value }, { + upsert: true + }, (err, res) => { + if (err) { + console.log(err.message); + console.log(err.errmsg); + } + // if (res) { + // console.log(JSON.stringify(res.result)); + // } + if (this.currentWrites[id] === newProm) { + delete this.currentWrites[id]; + } + resolve(); + callback(); + }); }); - return newProm; }; - this.currentWrites[id] = prom ? prom.then(run) : run(); + if (prom) { + newProm = prom.then(run); + this.currentWrites[id] = newProm; + } else { + newProm = run(); + this.currentWrites[id] = newProm; + } } } public delete(id: string, collectionName = Database.DocumentsCollection) { - this.db && this.db.collection(collectionName).remove({ _id: id }); + this.db && this.db.collection(collectionName).remove({ id: id }); } public deleteAll(collectionName = Database.DocumentsCollection): Promise { @@ -55,18 +63,18 @@ export class Database { ); } - public getDocument(id: string, fn: (res: any) => void, collectionName = Database.DocumentsCollection) { - this.db && this.db.collection(collectionName).findOne({ _id: id }, (err, result) => - fn(result ? result : undefined)); + public getDocument(id: string, fn: (result?: Transferable) => void, collectionName = Database.DocumentsCollection) { + this.db && this.db.collection(collectionName).findOne({ id: id }, (err, result) => + fn(result ? ({ id: result._id, type: result.type, data: result.data }) : undefined)) } - public getDocuments(ids: string[], fn: (res: any) => void, collectionName = Database.DocumentsCollection) { - this.db && this.db.collection(collectionName).find({ _id: { "$in": ids } }).toArray((err, docs) => { + public getDocuments(ids: string[], fn: (result: Transferable[]) => void, collectionName = Database.DocumentsCollection) { + this.db && this.db.collection(collectionName).find({ id: { "$in": ids } }).toArray((err, docs) => { if (err) { console.log(err.message); console.log(err.errmsg); } - fn(docs); + fn(docs.map(doc => ({ id: doc._id, type: doc.type, data: doc.data }))); }); } diff --git a/src/server/index.ts b/src/server/index.ts index a6fe6fa2c..a86638186 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -246,17 +246,17 @@ function barReceived(guid: String) { clients[guid.toString()] = new Client(guid.toString()); } -function getField([id, callback]: [string, (result: any) => void]) { - Database.Instance.getDocument(id, (result: any) => +function getField([id, callback]: [string, (result?: Transferable) => void]) { + Database.Instance.getDocument(id, (result?: Transferable) => callback(result ? result : undefined)); } -function getFields([ids, callback]: [string[], (result: any) => void]) { +function getFields([ids, callback]: [string[], (result: Transferable[]) => void]) { Database.Instance.getDocuments(ids, callback); } function setField(socket: Socket, newValue: Transferable) { - Database.Instance.update(newValue._id, newValue, () => + Database.Instance.update(newValue.id, newValue, () => socket.broadcast.emit(MessageStore.SetField.Message, newValue)); } -- cgit v1.2.3-70-g09d2 From af6feb64510490da8d815f41ceb639d693b9eae3 Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 19 Apr 2019 16:34:32 -0400 Subject: played with iconifying things in a different way. fixed some things with schemas. --- src/Utils.ts | 2 +- src/client/documents/Documents.ts | 16 ++++ src/client/northstar/dash-nodes/HistogramBox.scss | 6 +- src/client/northstar/dash-nodes/HistogramBox.tsx | 2 +- src/client/views/DocumentDecorations.tsx | 42 ++++++---- src/client/views/Main.tsx | 8 +- .../views/collections/CollectionSchemaView.scss | 9 ++- .../views/collections/CollectionSchemaView.tsx | 5 +- .../CollectionFreeFormLinkView.tsx | 8 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 13 ++-- .../collections/collectionFreeForm/MarqueeView.tsx | 9 +-- src/client/views/globalCssVariables.scss | 2 + src/client/views/globalCssVariables.scss.d.ts | 1 + .../views/nodes/CollectionFreeFormDocumentView.tsx | 26 +++---- src/client/views/nodes/DocumentContentsView.tsx | 3 +- src/client/views/nodes/DocumentView.scss | 16 +--- src/client/views/nodes/DocumentView.tsx | 71 ++++++++--------- src/client/views/nodes/FieldView.tsx | 5 ++ src/client/views/nodes/IconBox.scss | 12 +++ src/client/views/nodes/IconBox.tsx | 90 ++++++++++++++++++++++ src/fields/IconFIeld.ts | 25 ++++++ src/fields/KeyStore.ts | 10 +-- src/server/Message.ts | 2 +- src/server/ServerUtil.ts | 2 + 24 files changed, 264 insertions(+), 121 deletions(-) create mode 100644 src/client/views/nodes/IconBox.scss create mode 100644 src/client/views/nodes/IconBox.tsx create mode 100644 src/fields/IconFIeld.ts (limited to 'src/server/ServerUtil.ts') diff --git a/src/Utils.ts b/src/Utils.ts index dec6245ef..98f75d3b9 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -1,7 +1,7 @@ import v4 = require('uuid/v4'); import v5 = require("uuid/v5"); import { Socket } from 'socket.io'; -import { Message, Types, Transferable } from './server/Message'; +import { Message } from './server/Message'; import { Document } from './fields/Document'; export class Utils { diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 4febfa7eb..b0bb74d89 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -32,6 +32,9 @@ import { action } from "mobx"; import { ColumnAttributeModel } from "../northstar/core/attribute/AttributeModel"; import { AttributeTransformationModel } from "../northstar/core/attribute/AttributeTransformationModel"; import { AggregateFunction } from "../northstar/model/idea/idea"; +import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss"; +import { IconBox } from "../views/nodes/IconBox"; +import { IconField } from "../../fields/IconFIeld"; export interface DocumentOptions { x?: number; @@ -63,6 +66,7 @@ export namespace Documents { let videoProto: Document; let audioProto: Document; let pdfProto: Document; + let iconProto: Document; const textProtoId = "textProto"; const histoProtoId = "histoProto"; const pdfProtoId = "pdfProto"; @@ -72,6 +76,7 @@ export namespace Documents { const kvpProtoId = "kvpProto"; const videoProtoId = "videoProto"; const audioProtoId = "audioProto"; + const iconProtoId = "iconProto"; export function initProtos(): Promise { return Server.GetFields([textProtoId, histoProtoId, collProtoId, pdfProtoId, imageProtoId, videoProtoId, audioProtoId, webProtoId, kvpProtoId]).then(fields => { @@ -84,6 +89,7 @@ export namespace Documents { videoProto = fields[videoProtoId] as Document || CreateVideoPrototype(); audioProto = fields[audioProtoId] as Document || CreateAudioPrototype(); pdfProto = fields[pdfProtoId] as Document || CreatePdfPrototype(); + iconProto = fields[iconProtoId] as Document || CreateIconPrototype(); }); } function assignOptions(doc: Document, options: DocumentOptions): Document { @@ -92,6 +98,8 @@ export namespace Documents { if (options.title !== undefined) { doc.SetText(KeyStore.Title, options.title); } if (options.page !== undefined) { doc.SetNumber(KeyStore.Page, options.page); } if (options.scale !== undefined) { doc.SetNumber(KeyStore.Scale, options.scale); } + if (options.width !== undefined) { doc.SetNumber(KeyStore.Width, options.width); } + if (options.height !== undefined) { doc.SetNumber(KeyStore.Height, options.height); } if (options.viewType !== undefined) { doc.SetNumber(KeyStore.ViewType, options.viewType); } if (options.backgroundColor !== undefined) { doc.SetText(KeyStore.BackgroundColor, options.backgroundColor); } if (options.ink !== undefined) { doc.Set(KeyStore.Ink, new InkField(options.ink)); } @@ -139,6 +147,11 @@ export namespace Documents { histoProto.SetText(KeyStore.BackgroundLayout, HistogramBox.LayoutString()); return histoProto; } + function CreateIconPrototype(): Document { + let iconProto = setupPrototypeOptions(iconProtoId, "ICON_PROTO", IconBox.LayoutString(), + { x: 0, y: 0, width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE), layoutKeys: [KeyStore.Data] }); + return iconProto; + } function CreateTextPrototype(): Document { let textProto = setupPrototypeOptions(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(), { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] }); @@ -203,6 +216,9 @@ export namespace Documents { export function TextDocument(options: DocumentOptions = {}) { return assignToDelegate(SetInstanceOptions(textProto, options, ["", TextField]).MakeDelegate(), options); } + export function IconDocument(icon: string, options: DocumentOptions = {}) { + return assignToDelegate(SetInstanceOptions(iconProto, { width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE), layoutKeys: [KeyStore.Data], layout: IconBox.LayoutString(), ...options }, [icon, IconField]), options); + } export function PdfDocument(url: string, options: DocumentOptions = {}) { return assignToDelegate(SetInstanceOptions(pdfProto, options, [new URL(url), PDFField]).MakeDelegate(), options); } diff --git a/src/client/northstar/dash-nodes/HistogramBox.scss b/src/client/northstar/dash-nodes/HistogramBox.scss index e899cf15e..06d781263 100644 --- a/src/client/northstar/dash-nodes/HistogramBox.scss +++ b/src/client/northstar/dash-nodes/HistogramBox.scss @@ -1,12 +1,12 @@ .histogrambox-container { padding: 0vw; position: absolute; - top: 0; - left:0; + top: -50%; + left:-50%; text-align: center; width: 100%; height: 100%; - background: black; + background: black; } .histogrambox-xaxislabel { position:absolute; diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx index 0e84ace50..e2ecc8c83 100644 --- a/src/client/northstar/dash-nodes/HistogramBox.tsx +++ b/src/client/northstar/dash-nodes/HistogramBox.tsx @@ -146,7 +146,7 @@ export class HistogramBox extends React.Component { return ( runInAction(() => { this.PanelWidth = r.entry.width; this.PanelHeight = r.entry.height; })}> {({ measureRef }) => -
+
{labelY} diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index cfb9befd5..da2c7a3be 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -5,17 +5,18 @@ import { KeyStore } from "../../fields/KeyStore"; import { ListField } from "../../fields/ListField"; import { NumberField } from "../../fields/NumberField"; import { TextField } from "../../fields/TextField"; +import { Document } from "../../fields/Document"; import { emptyFunction } from "../../Utils"; import { DragLinksAsDocuments, DragManager } from "../util/DragManager"; import { SelectionManager } from "../util/SelectionManager"; import { undoBatch } from "../util/UndoManager"; import './DocumentDecorations.scss'; import { MainOverlayTextBox } from "./MainOverlayTextBox"; -import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss"; import { DocumentView } from "./nodes/DocumentView"; import { LinkMenu } from "./nodes/LinkMenu"; import React = require("react"); import { CompileScript } from "../util/Scripting"; +import { IconBox } from "./nodes/IconBox"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -191,6 +192,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> document.addEventListener("pointerup", this.onMinimizeUp); } } + + @observable _minimizedX = 0; + @observable _minimizedY = 0; @action onMinimizeMove = (e: PointerEvent): void => { e.stopPropagation(); @@ -201,12 +205,20 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let xf = SelectionManager.SelectedDocuments()[0].props.ScreenToLocalTransform().inverse().transformPoint(0, 0); let dx = e.pageX - xf[0]; let dy = e.pageY - xf[1]; - if (Math.abs(dx) < 20 && Math.abs(dy) < 20) - dx = dy = 0; + this._minimizedX = e.clientX; + this._minimizedY = e.clientY; + if (Math.abs(dx) < 20 && Math.abs(dy) < 20) { + this._minimizedX = xf[0]; + this._minimizedY = xf[1]; + } SelectionManager.SelectedDocuments().map(dv => { - let where = (dv.props.ScreenToLocalTransform()).scale(dv.props.ContentScaling()).transformDirection(dx, dy); - dv.props.Document.SetNumber(KeyStore.MinimizedX, where[0]); - dv.props.Document.SetNumber(KeyStore.MinimizedY, where[1]); + let minDoc = dv.props.Document.Get(KeyStore.MinimizedDoc); + if (minDoc instanceof Document) { + let where = (dv.props.ScreenToLocalTransform()).scale(dv.props.ContentScaling()).transformPoint(this._minimizedX, this._minimizedY); + let minDocument = minDoc as Document; + minDocument.SetNumber(KeyStore.X, where[0] + dv.props.Document.GetNumber(KeyStore.X, 0)); + minDocument.SetNumber(KeyStore.Y, where[1] + dv.props.Document.GetNumber(KeyStore.Y, 0)); + } }); } } @@ -219,6 +231,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> if (Math.abs(dx) < 4 && Math.abs(dy) < 4 && !this._iconifying) { SelectionManager.SelectedDocuments().map(dv => dv.minimize()); SelectionManager.DeselectAll(); + } else { + this._minimizedX = this._minimizedY = 0; } document.removeEventListener("pointermove", this.onMinimizeMove); document.removeEventListener("pointerup", this.onMinimizeUp); @@ -404,24 +418,18 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> if (bounds.x === Number.MAX_VALUE || !seldoc) { return (null); } - let minvec = [seldoc.props.Document.GetNumber(KeyStore.MinimizedX, 0), seldoc.props.Document.GetNumber(KeyStore.MinimizedY, 0)]; - minvec = seldoc.props.ScreenToLocalTransform().scale(seldoc.props.ContentScaling()).inverse().transformDirection(minvec[0], minvec[1]); - let selpos = minvec[0] !== 0 || minvec[1] !== 0 ? - [minvec[0] - 12 + (!this._iconifying ? 8 : 0), minvec[1] - 12 + (!this._iconifying ? 28 : 0)] : + let selpos = this._minimizedX !== 0 || this._minimizedY !== 0 ? + [this._minimizedX - 12 + (!this._iconifying ? 8 : 0), this._minimizedY - 12 + (!this._iconifying ? 28 : 0)] : [0, this._iconifying ? -18 : 0]; let minimizeIcon = (
- {SelectionManager.SelectedDocuments().length == 1 ? SelectionManager.SelectedDocuments()[0].minimizedIcon : "..."} + {SelectionManager.SelectedDocuments().length == 1 ? IconBox.DocumentIcon(SelectionManager.SelectedDocuments()[0].props.Document.GetText(KeyStore.Layout, "...")) : "..."}
); if (this._iconifying) { - let xfpt = seldoc.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); - return (
- {minimizeIcon} -
); + return (
{minimizeIcon}
); } - // console.log(this._documents.length) - // let test = this._documents[0].props.Document.Title; + if (this.Hidden) { return (null); } diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 503a11b35..09ef30f6b 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -85,11 +85,11 @@ export class Main extends React.Component { this.initEventListeners(); this.initAuthenticationRouters(); - // try { - // this.initializeNorthstar(); - // } catch (e) { + try { + this.initializeNorthstar(); + } catch (e) { - // } + } } componentDidMount() { window.onpopstate = this.onHistory; } diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss index 6eabbe17c..cfdb3ab22 100644 --- a/src/client/views/collections/CollectionSchemaView.scss +++ b/src/client/views/collections/CollectionSchemaView.scss @@ -1,6 +1,7 @@ @import "../globalCssVariables"; + .collectionSchemaView-container { border-width: $COLLECTION_BORDER_WIDTH; border-color : $intermediate-color; @@ -10,6 +11,10 @@ position: absolute; width: 100%; height: 100%; + + .collectionSchemaView-cellContents { + height: $MAX_ROW_HEIGHT; + } .collectionSchemaView-previewRegion { position: relative; @@ -104,7 +109,7 @@ } .rt-tr-group { direction: ltr; - max-height: 44px; + max-height: $MAX_ROW_HEIGHT; } .rt-td { border-width: 1px; @@ -136,7 +141,7 @@ } .ReactTable .rt-th, .ReactTable .rt-td { - max-height: 44; + max-height: $MAX_ROW_HEIGHT; padding: 3px 7px; font-size: 13px; text-align: center; diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index b61eb342d..90077b053 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -5,6 +5,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable, untracked } from "mobx"; import { observer } from "mobx-react"; import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults } from "react-table"; +import { MAX_ROW_HEIGHT } from '../../views/globalCssVariables.scss' import "react-table/react-table.css"; import { Document } from "../../../fields/Document"; import { Field, Opt } from "../../../fields/Field"; @@ -99,11 +100,11 @@ export class CollectionSchemaView extends CollectionSubView { return false; }; return ( -
+
{ let field = props.Document.Get(props.fieldKey); if (field && field instanceof Field) { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 8868f7df0..20c5a84bf 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -23,10 +23,10 @@ export class CollectionFreeFormLinkView extends React.Component { if (super.drop(e, de) && de.data instanceof DragManager.DocumentDragData) { - console.log("DROP Aat " + de.x + " off " + de.data.xOffset); const [x, y] = this.getTransform().transformPoint(de.x - de.data.xOffset, de.y - de.data.yOffset); if (de.data.droppedDocuments.length) { let dragDoc = de.data.droppedDocuments[0]; let dropX = dragDoc.GetNumber(KeyStore.X, 0); let dropY = dragDoc.GetNumber(KeyStore.Y, 0); de.data.droppedDocuments.map(d => { - let minimized = d.GetBoolean(KeyStore.Minimized, false); - d.SetNumber(KeyStore.X, x + (d.GetNumber(KeyStore.X, 0) - (minimized ? d.GetNumber(KeyStore.MinimizedX, 0) : 0)) - dropX); - d.SetNumber(KeyStore.Y, y + (d.GetNumber(KeyStore.Y, 0) - (minimized ? d.GetNumber(KeyStore.MinimizedY, 0) : 0)) - dropY); - if (!minimized) { + d.SetNumber(KeyStore.X, x + (d.GetNumber(KeyStore.X, 0)) - dropX); + d.SetNumber(KeyStore.Y, y + (d.GetNumber(KeyStore.Y, 0)) - dropY); + if (!d.GetBoolean(KeyStore.IsMinimized, false)) { if (!d.GetNumber(KeyStore.Width, 0)) { d.SetNumber(KeyStore.Width, 300); } @@ -264,7 +263,9 @@ export class CollectionFreeFormView extends CollectionSubView { let docviews = this.props.Document.GetList(this.props.fieldKey, [] as Document[]).filter(doc => doc).reduce((prev, doc) => { var page = doc.GetNumber(KeyStore.Page, -1); if (page === curPage || page === -1) { - prev.push(); + let minim = doc.GetT(KeyStore.IsMinimized, BooleanField); + if (minim === undefined || (minim && !minim.Data)) + prev.push(); } return prev; }, [] as JSX.Element[]); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 8b94374fa..bf918beba 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -208,12 +208,11 @@ export class MarqueeView extends React.Component let selRect = this.Bounds; let selection: Document[] = []; this.props.activeDocuments().map(doc => { - let minimized = doc.GetBoolean(KeyStore.Minimized, false); var z = doc.GetNumber(KeyStore.Zoom, 1); - var x = doc.GetNumber(KeyStore.X, 0) + (minimized ? doc.GetNumber(KeyStore.MinimizedX, 0) : 0); - var y = doc.GetNumber(KeyStore.Y, 0) + (minimized ? doc.GetNumber(KeyStore.MinimizedY, 0) : 0); - var w = minimized ? MINIMIZED_ICON_SIZE : doc.Width() / z; - var h = minimized ? MINIMIZED_ICON_SIZE : doc.Height() / z; + var x = doc.GetNumber(KeyStore.X, 0); + var y = doc.GetNumber(KeyStore.Y, 0); + var w = doc.Width() / z; + var h = doc.Height() / z; if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) { selection.push(doc); } diff --git a/src/client/views/globalCssVariables.scss b/src/client/views/globalCssVariables.scss index f154f8158..4f68b71b0 100644 --- a/src/client/views/globalCssVariables.scss +++ b/src/client/views/globalCssVariables.scss @@ -24,8 +24,10 @@ $docDecorations-zindex: 998; // then doc decorations appear over everything else $remoteCursors-zindex: 997; // ... not sure what level the remote cursors should go -- is this right? $COLLECTION_BORDER_WIDTH: 1; $MINIMIZED_ICON_SIZE:25; +$MAX_ROW_HEIGHT: 44px; :export { contextMenuZindex: $contextMenu-zindex; COLLECTION_BORDER_WIDTH: $COLLECTION_BORDER_WIDTH; MINIMIZED_ICON_SIZE: $MINIMIZED_ICON_SIZE; + MAX_ROW_HEIGHT: $MAX_ROW_HEIGHT; } \ No newline at end of file diff --git a/src/client/views/globalCssVariables.scss.d.ts b/src/client/views/globalCssVariables.scss.d.ts index cc77d987a..9788d31f7 100644 --- a/src/client/views/globalCssVariables.scss.d.ts +++ b/src/client/views/globalCssVariables.scss.d.ts @@ -3,6 +3,7 @@ interface IGlobalScss { contextMenuZindex: string; // context menu shows up over everything COLLECTION_BORDER_WIDTH: string; MINIMIZED_ICON_SIZE: string; + MAX_ROW_HEIGHT: string; } declare const globalCssVariables: IGlobalScss; diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index a3689414d..1d42b3899 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -46,10 +46,10 @@ export class CollectionFreeFormDocumentView extends React.Component this.props.ScreenToLocalTransform() @@ -57,8 +57,8 @@ export class CollectionFreeFormDocumentView extends React.Component this.nativeWidth > 0 ? this.width / this.nativeWidth : 1; - panelWidth = () => this.isMinimized ? 10 : this.props.PanelWidth(); - panelHeight = () => this.isMinimized ? 10 : this.props.PanelHeight(); + panelWidth = () => this.props.PanelWidth(); + panelHeight = () => this.props.PanelHeight(); @computed get docView() { @@ -70,19 +70,17 @@ export class CollectionFreeFormDocumentView extends React.Component; } - get isMinimized() { return this.props.Document.GetBoolean(KeyStore.Minimized, false); } - render() { let zoomFade = 1; //var zoom = doc.GetNumber(KeyStore.Zoom, 1); - let transform = this.getTransform().scale(this.contentScaling()).inverse(); - var [sptX, sptY] = transform.transformPoint(0, 0); - let [bptX, bptY] = transform.transformPoint(this.props.PanelWidth(), this.props.PanelHeight()); - let w = bptX - sptX; - //zoomFade = area < 100 || area > 800 ? Math.max(0, Math.min(1, 2 - 5 * (zoom < this.scale ? this.scale / zoom : zoom / this.scale))) : 1; - let fadeUp = .75 * 1800; - let fadeDown = .075 * 1800; - zoomFade = w < fadeDown /* || w > fadeUp */ ? Math.max(0, Math.min(1, 2 - (w < fadeDown ? fadeDown / w : w / fadeUp))) : 1; + // let transform = this.getTransform().scale(this.contentScaling()).inverse(); + // var [sptX, sptY] = transform.transformPoint(0, 0); + // let [bptX, bptY] = transform.transformPoint(this.props.PanelWidth(), this.props.PanelHeight()); + // let w = bptX - sptX; + // //zoomFade = area < 100 || area > 800 ? Math.max(0, Math.min(1, 2 - 5 * (zoom < this.scale ? this.scale / zoom : zoom / this.scale))) : 1; + // let fadeUp = .75 * 1800; + // let fadeDown = .075 * 1800; + // zoomFade = w < fadeDown /* || w > fadeUp */ ? Math.max(0, Math.min(1, 2 - (w < fadeDown ? fadeDown / w : w / fadeUp))) : 1; return (
Error loading layout keys

; } return ; @@ -189,10 +179,10 @@ export class DocumentView extends React.Component { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); e.stopPropagation(); - if (!SelectionManager.IsSelected(this) && e.button !== 2 && - Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientY - this._downY) < 4) { - SelectionManager.SelectDoc(this, e.ctrlKey); - } + if (!SelectionManager.IsSelected(this) && e.button !== 2) + if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientY - this._downY) < 4) { + SelectionManager.SelectDoc(this, e.ctrlKey); + } } stopPropagation = (e: React.SyntheticEvent) => { e.stopPropagation(); @@ -221,13 +211,33 @@ export class DocumentView extends React.Component { ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); } + @action createIcon = (layoutString: string): void => { + let iconDoc = Documents.IconDocument(layoutString); + iconDoc.SetBoolean(KeyStore.IsMinimized, false); + iconDoc.SetNumber(KeyStore.NativeWidth, 0); + iconDoc.SetNumber(KeyStore.NativeHeight, 0); + iconDoc.Set(KeyStore.Prototype, this.props.Document); + iconDoc.Set(KeyStore.MaximizedDoc, this.props.Document); + this.props.Document.Set(KeyStore.MinimizedDoc, iconDoc); + this.props.addDocument && this.props.addDocument(iconDoc, false); + } + @action - public minimize = (where: number[]): void => { - this.props.Document.SetBoolean(KeyStore.Minimized, true); - if (where[0] !== 0 || where[1] !== 0) - this.props.Document.SetNumber(KeyStore.MinimizedX, where[0]); - if (where[1] !== 0 || where[0] !== 0) - this.props.Document.SetNumber(KeyStore.MinimizedY, where[1]); + public minimize = (): void => { + this.props.Document.SetBoolean(KeyStore.IsMinimized, true); + this.props.Document.GetAsync(KeyStore.MinimizedDoc, mindoc => { + if (mindoc === undefined) { + this.props.Document.GetAsync(KeyStore.BackgroundLayout, field => { + if (field instanceof TextField) this.createIcon(field.Data); + else this.props.Document.GetAsync(KeyStore.Layout, field => { + if (field instanceof TextField) this.createIcon(field.Data); + }); + }); + } + else if (mindoc instanceof Document) { + this.props.addDocument && this.props.addDocument(mindoc, false); + } + }); } @undoBatch @@ -295,7 +305,6 @@ export class DocumentView extends React.Component { } e.preventDefault(); - !this.isMinimized() && ContextMenu.Instance.addItem({ description: "Minimize", event: () => this.minimize([0, 0]) }); ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked }); ContextMenu.Instance.addItem({ description: "Fields", event: this.fieldsClicked }); ContextMenu.Instance.addItem({ description: "Center", event: () => this.props.focus(this.props.Document) }); @@ -309,9 +318,6 @@ export class DocumentView extends React.Component { SelectionManager.SelectDoc(this, false); } - @action - expand = (e: React.MouseEvent) => { this.props.Document.SetBoolean(KeyStore.Minimized, false); SelectionManager.SelectDoc(this, e.ctrlKey); } - isMinimized = () => this.props.Document.GetBoolean(KeyStore.Minimized, false); isSelected = () => SelectionManager.IsSelected(this); select = (ctrlPressed: boolean) => SelectionManager.SelectDoc(this, ctrlPressed); @@ -319,27 +325,12 @@ export class DocumentView extends React.Component { @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); } @computed get contents() { return (); } - @computed get minimizedIcon() { - let button = this.layout.indexOf("PDFBox") !== -1 ? faFilePdf : - this.layout.indexOf("ImageBox") !== -1 ? faImage : - this.layout.indexOf("Formatted") !== -1 ? faStickyNote : - this.layout.indexOf("Video") !== -1 ? faFilm : - this.layout.indexOf("Collection") !== -1 ? faObjectGroup : - faCaretUp; - return - } render() { var scaling = this.props.ContentScaling(); var nativeHeight = this.nativeHeight > 0 ? this.nativeHeight.toString() + "px" : "100%"; var nativeWidth = this.nativeWidth > 0 ? this.nativeWidth.toString() + "px" : "100%"; - if (this.isMinimized()) { - return ( -
- {this.minimizedIcon} -
); - } return (
{ else if (field instanceof ImageField) { return ; } + else if (field instanceof IconField) { + return ; + } else if (field instanceof VideoField) { return ; } diff --git a/src/client/views/nodes/IconBox.scss b/src/client/views/nodes/IconBox.scss new file mode 100644 index 000000000..ce0ee2e09 --- /dev/null +++ b/src/client/views/nodes/IconBox.scss @@ -0,0 +1,12 @@ + +@import "../globalCssVariables"; +.iconBox-container { + position: absolute; + left:0; + top:0; + svg { + width: 100% !important; + height: 100%; + background: white; + } +} \ No newline at end of file diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx new file mode 100644 index 000000000..5ada2186d --- /dev/null +++ b/src/client/views/nodes/IconBox.tsx @@ -0,0 +1,90 @@ +import React = require("react"); +import { library } from '@fortawesome/fontawesome-svg-core'; +import { faCaretUp, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { action, computed } from "mobx"; +import { observer } from "mobx-react"; +import { Document } from '../../../fields/Document'; +import { IconField } from "../../../fields/IconFIeld"; +import { KeyStore } from "../../../fields/KeyStore"; +import { SelectionManager } from "../../util/SelectionManager"; +import { FieldView, FieldViewProps } from './FieldView'; +import "./IconBox.scss"; + + +library.add(faCaretUp); +library.add(faObjectGroup); +library.add(faStickyNote); +library.add(faFilePdf); +library.add(faFilm); + +@observer +export class IconBox extends React.Component { + public static LayoutString() { return FieldView.LayoutString(IconBox); } + + @computed get maximized() { return this.props.Document.GetT(KeyStore.MaximizedDoc, Document); } + @computed get layout(): string { return this.props.Document.GetData(this.props.fieldKey, IconField, "

Error loading layout data

" as string); } + @computed get minimizedIcon() { return IconBox.DocumentIcon(this.layout); } + + public static DocumentIcon(layout: string) { + let button = layout.indexOf("PDFBox") !== -1 ? faFilePdf : + layout.indexOf("ImageBox") !== -1 ? faImage : + layout.indexOf("Formatted") !== -1 ? faStickyNote : + layout.indexOf("Video") !== -1 ? faFilm : + layout.indexOf("Collection") !== -1 ? faObjectGroup : + faCaretUp; + return + } + + animateTransition(icon: number[], targ: number[], width: number, height: number, stime: number, target: Document, maximizing: boolean) { + setTimeout(() => { + let now = Date.now(); + let progress = Math.min(1, (now - stime) / 200); + let pval = maximizing ? + [icon[0] + (targ[0] - icon[0]) * progress, icon[1] + (targ[1] - icon[1]) * progress] : + [targ[0] + (icon[0] - targ[0]) * progress, targ[1] + (icon[1] - targ[1]) * progress]; + target.SetNumber(KeyStore.Width, maximizing ? 25 + (width - 25) * progress : width + (25 - width) * progress); + target.SetNumber(KeyStore.Height, maximizing ? 25 + (height - 25) * progress : height + (25 - height) * progress); + target.SetNumber(KeyStore.X, pval[0]); + target.SetNumber(KeyStore.Y, pval[1]); + if (now < stime + 200) { + this.animateTransition(icon, targ, width, height, stime, target, maximizing); + } + else { + if (!maximizing) { + target.SetBoolean(KeyStore.IsMinimized, true); + target.SetNumber(KeyStore.X, targ[0]); + target.SetNumber(KeyStore.Y, targ[1]); + target.SetNumber(KeyStore.Width, width); + target.SetNumber(KeyStore.Height, height); + } + this._completed = true; + } + }, + 2); + } + + _completed = true; + + @action + public toggleMinimize = (): void => { + SelectionManager.DeselectAll(); + if (this.maximized instanceof Document && this._completed) { + this._completed = false; + let minimized = this.maximized.GetBoolean(KeyStore.IsMinimized, false); + this.maximized.SetBoolean(KeyStore.IsMinimized, false); + this.animateTransition( + [this.props.Document.GetNumber(KeyStore.X, 0), this.props.Document.GetNumber(KeyStore.Y, 0)], + [this.maximized.GetNumber(KeyStore.X, 0), this.maximized.GetNumber(KeyStore.Y, 0)], + this.maximized.GetNumber(KeyStore.Width, 0), this.maximized.GetNumber(KeyStore.Width, 0), + Date.now(), this.maximized, minimized); + } + } + + render() { + return ( +
+ {this.minimizedIcon} +
); + } +} \ No newline at end of file diff --git a/src/fields/IconFIeld.ts b/src/fields/IconFIeld.ts new file mode 100644 index 000000000..a6694cc49 --- /dev/null +++ b/src/fields/IconFIeld.ts @@ -0,0 +1,25 @@ +import { BasicField } from "./BasicField"; +import { FieldId } from "./Field"; +import { Types } from "../server/Message"; + +export class IconField extends BasicField { + constructor(data: string = "", id?: FieldId, save: boolean = true) { + super(data, save, id); + } + + ToScriptString(): string { + return `new IconField("${this.Data}")`; + } + + Copy() { + return new IconField(this.Data); + } + + ToJson() { + return { + type: Types.Icon, + data: this.Data, + id: this.Id + }; + } +} \ No newline at end of file diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts index ff2f31003..a347f8bcf 100644 --- a/src/fields/KeyStore.ts +++ b/src/fields/KeyStore.ts @@ -4,8 +4,6 @@ export namespace KeyStore { export const Prototype = new Key("Prototype"); export const X = new Key("X"); export const Y = new Key("Y"); - export const MinimizedX = new Key("MinimizedX"); - export const MinimizedY = new Key("MinimizedY"); export const Page = new Key("Page"); export const Title = new Key("Title"); export const Author = new Key("Author"); @@ -47,14 +45,16 @@ export namespace KeyStore { export const OptionalRightCollection = new Key("OptionalRightCollection"); export const Archives = new Key("Archives"); export const Workspaces = new Key("Workspaces"); - export const Minimized = new Key("Minimized"); + export const IsMinimized = new Key("IsMinimized"); + export const MinimizedDoc = new Key("MinimizedDoc"); + export const MaximizedDoc = new Key("MaximizedDoc"); export const CopyDraggedItems = new Key("CopyDraggedItems"); - export const KeyList: Key[] = [Prototype, X, Y, MinimizedX, MinimizedY, Page, Title, Author, PanX, PanY, Scale, NativeWidth, NativeHeight, + export const KeyList: Key[] = [Prototype, X, Y, Page, Title, Author, PanX, PanY, Scale, NativeWidth, NativeHeight, Width, Height, ZIndex, Zoom, Data, Annotations, ViewType, Layout, BackgroundColor, BackgroundLayout, OverlayLayout, LayoutKeys, LayoutFields, ColumnsKey, SchemaSplitPercentage, Caption, ActiveWorkspace, DocumentText, BrushingDocs, LinkedToDocs, LinkedFromDocs, LinkDescription, LinkTags, Thumbnail, ThumbnailPage, CurPage, AnnotationOn, NumPages, Ink, Cursors, OptionalRightCollection, - Archives, Workspaces, Minimized, CopyDraggedItems + Archives, Workspaces, IsMinimized, MinimizedDoc, MaximizedDoc, CopyDraggedItems ]; export function KeyLookup(keyid: string) { for (const key of KeyList) { diff --git a/src/server/Message.ts b/src/server/Message.ts index bbe4ffcad..15916ef12 100644 --- a/src/server/Message.ts +++ b/src/server/Message.ts @@ -14,7 +14,7 @@ export class Message { } export enum Types { - Number, List, Key, Image, Web, Document, Text, RichText, DocumentReference, + Number, List, Key, Image, Web, Document, Text, Icon, RichText, DocumentReference, Html, Video, Audio, Ink, PDF, Tuple, HistogramOp, Boolean, Script, } diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts index 818230c1a..79ca5e55d 100644 --- a/src/server/ServerUtil.ts +++ b/src/server/ServerUtil.ts @@ -18,6 +18,7 @@ import { NumberField } from "./../fields/NumberField"; import { RichTextField } from "./../fields/RichTextField"; import { TextField } from "./../fields/TextField"; import { Transferable, Types } from "./Message"; +import { IconField } from "../fields/IconFIeld"; export class ServerUtils { public static prepend(extension: string): string { @@ -37,6 +38,7 @@ export class ServerUtils { case Types.Boolean: return new BooleanField(json.data, json.id, false); case Types.Number: return new NumberField(json.data, json.id, false); case Types.Text: return new TextField(json.data, json.id, false); + case Types.Icon: return new IconField(json.data, json.id, false); case Types.Html: return new HtmlField(json.data, json.id, false); case Types.Web: return new WebField(new URL(json.data), json.id, false); case Types.RichText: return new RichTextField(json.data, json.id, false); -- cgit v1.2.3-70-g09d2 From 2e2377aaee0801ab20134b54e5105815c59f38e8 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Wed, 24 Apr 2019 20:50:53 -0400 Subject: Freeform view done --- .../collectionFreeForm/CollectionFreeFormView.tsx | 105 ++++++------- src/new_fields/Types.ts | 2 +- src/server/ServerUtil.ts | 62 -------- test/test.ts | 166 ++------------------- 4 files changed, 69 insertions(+), 266 deletions(-) delete mode 100644 src/server/ServerUtil.ts (limited to 'src/server/ServerUtil.ts') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index ee7d65d23..3a9c9780b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -19,7 +19,7 @@ import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); import v5 = require("uuid/v5"); -import { createSchema, makeInterface } from "../../../../new_fields/Schema"; +import { createSchema, makeInterface, listSpec } from "../../../../new_fields/Schema"; import { Doc, Id } from "../../../../new_fields/Doc"; import { FieldValue, Cast } from "../../../../new_fields/Types"; import { pageSchema } from "../../nodes/ImageBox"; @@ -83,19 +83,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const [x, y] = this.getTransform().transformPoint(de.x - de.data.xOffset, de.y - de.data.yOffset); if (de.data.droppedDocuments.length) { let dragDoc = de.data.droppedDocuments[0]; - let dropX = dragDoc.GetNumber(KeyStore.X, 0); - let dropY = dragDoc.GetNumber(KeyStore.Y, 0); + let dropX = Cast(dragDoc.x, "number", 0); + let dropY = Cast(dragDoc.y, "number", 0); de.data.droppedDocuments.map(d => { - d.SetNumber(KeyStore.X, x + (d.GetNumber(KeyStore.X, 0)) - dropX); - d.SetNumber(KeyStore.Y, y + (d.GetNumber(KeyStore.Y, 0)) - dropY); - if (!d.GetBoolean(KeyStore.IsMinimized, false)) { - if (!d.GetNumber(KeyStore.Width, 0)) { - d.SetNumber(KeyStore.Width, 300); + d.x = x + Cast(d.x, "number", 0) - dropX; + d.y = y + Cast(d.y, "number", 0) - dropY; + if (!Cast(d.isMinimized, "boolean", false)) { + if (!Cast(d.width, "number", 0)) { + d.width = 300; } - if (!d.GetNumber(KeyStore.Height, 0)) { - let nw = d.GetNumber(KeyStore.NativeWidth, 0); - let nh = d.GetNumber(KeyStore.NativeHeight, 0); - d.SetNumber(KeyStore.Height, nw && nh ? nh / nw * d.Width() : 300); + if (!Cast(d.height, "number", 0)) { + let nw = Cast(d.nativeWidth, "number", 0); + let nh = Cast(d.nativeHeight, "number", 0); + d.height = nw && nh ? nh / nw * Cast(d.width, "number", 0) : 300; } } this.bringToFront(d); @@ -115,7 +115,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action onPointerDown = (e: React.PointerEvent): void => { - let childSelected = this.props.Document.GetList(this.props.fieldKey, [] as Document[]).filter(doc => doc).reduce((childSelected, doc) => { + let childSelected = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), [] as Doc[]).filter(doc => doc).reduce((childSelected, doc) => { var dv = DocumentManager.Instance.getDocumentView(doc); return childSelected || (dv && SelectionManager.IsSelected(dv) ? true : false); }, false); @@ -139,20 +139,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action onPointerMove = (e: PointerEvent): void => { if (!e.cancelBubble) { - let x = this.props.Document.GetNumber(KeyStore.PanX, 0); - let y = this.props.Document.GetNumber(KeyStore.PanY, 0); - let docs = this.props.Document.GetList(this.props.fieldKey, [] as Document[]); + let x = Cast(this.props.Document.panX, "number", 0); + let y = Cast(this.props.Document.panY, "number", 0); + let docs = this.children || []; let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); if (!this.isAnnotationOverlay) { - let minx = docs.length ? docs[0].GetNumber(KeyStore.X, 0) : 0; - let maxx = docs.length ? docs[0].Width() + minx : minx; - let miny = docs.length ? docs[0].GetNumber(KeyStore.Y, 0) : 0; - let maxy = docs.length ? docs[0].Height() + miny : miny; + let minx = docs.length ? Cast(docs[0].x, "number", 0) : 0; + let maxx = docs.length ? Cast(docs[0].width, "number", 0) + minx : minx; + let miny = docs.length ? Cast(docs[0].y, "number", 0) : 0; + let maxy = docs.length ? Cast(docs[0].height, "number", 0) + miny : miny; let ranges = docs.filter(doc => doc).reduce((range, doc) => { - let x = doc.GetNumber(KeyStore.X, 0); - let xe = x + doc.GetNumber(KeyStore.Width, 0); - let y = doc.GetNumber(KeyStore.Y, 0); - let ye = y + doc.GetNumber(KeyStore.Height, 0); + let x = Cast(doc.x, "number", 0); + let xe = x + Cast(doc.width, "number", 0); + let y = Cast(doc.y, "number", 0); + let ye = y + Cast(doc.height, "number", 0); return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]], [range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]]; }, [[minx, maxx], [miny, maxy]]); @@ -176,10 +176,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { // if (!this.props.active()) { // return; // } - let childSelected = this.props.Document.GetList(this.props.fieldKey, [] as Document[]).filter(doc => doc).reduce((childSelected, doc) => { + let childSelected = (this.children || []).filter(doc => doc).some(doc => { var dv = DocumentManager.Instance.getDocumentView(doc); - return childSelected || (dv && SelectionManager.IsSelected(dv) ? true : false); - }, false); + return dv && SelectionManager.IsSelected(dv) ? true : false; + }); if (!this.props.isSelected() && !childSelected && !this.props.isTopMost) { return; } @@ -188,8 +188,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (e.ctrlKey) { let deltaScale = (1 - (e.deltaY / coefficient)); - this.props.Document.SetNumber(KeyStore.NativeWidth, this.nativeWidth * deltaScale); - this.props.Document.SetNumber(KeyStore.NativeHeight, this.nativeHeight * deltaScale); + this.props.Document.nativeWidth = this.nativeWidth * deltaScale; + this.props.Document.nativeHeight = this.nativeHeight * deltaScale; e.stopPropagation(); e.preventDefault(); } else { @@ -204,7 +204,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y); let safeScale = Math.abs(localTransform.Scale); - this.props.Document.SetNumber(KeyStore.Scale, Math.abs(safeScale)); + this.props.Document.scale = Math.abs(safeScale); this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale); e.stopPropagation(); } @@ -216,8 +216,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { var scale = this.getLocalTransform().inverse().Scale; const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX)); const newPanY = Math.min((1 - 1 / scale) * this.nativeHeight, Math.max(0, panY)); - this.props.Document.SetNumber(KeyStore.PanX, this.isAnnotationOverlay ? newPanX : panX); - this.props.Document.SetNumber(KeyStore.PanY, this.isAnnotationOverlay ? newPanY : panY); + this.props.Document.panX = this.isAnnotationOverlay ? newPanX : panX; + this.props.Document.panY = this.isAnnotationOverlay ? newPanY : panY; } @action @@ -230,23 +230,23 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } @action - bringToFront(doc: Document) { - this.props.Document.GetList(this.props.fieldKey, [] as Document[]).slice().sort((doc1, doc2) => { + bringToFront(doc: Doc) { + (this.children || []).slice().sort((doc1, doc2) => { if (doc1 === doc) return 1; if (doc2 === doc) return -1; - return doc1.GetNumber(KeyStore.ZIndex, 0) - doc2.GetNumber(KeyStore.ZIndex, 0); - }).map((doc, index) => doc.SetNumber(KeyStore.ZIndex, index + 1)); + return Cast(doc1.zIndex, "number", 0) - Cast(doc2.zIndex, "number", 0); + }).forEach((doc, index) => doc.zIndex = index + 1); return doc; } - focusDocument = (doc: Document) => { + focusDocument = (doc: Doc) => { this.setPan( - doc.GetNumber(KeyStore.X, 0) + doc.Width() / 2, - doc.GetNumber(KeyStore.Y, 0) + doc.Height() / 2); + Cast(doc.x, "number", 0) + Cast(doc.width, "number", 0) / 2, + Cast(doc.y, "number", 0) + Cast(doc.height, "number", 0) / 2); this.props.focus(this.props.Document); } - getDocumentViewProps(document: Document): DocumentViewProps { + getDocumentViewProps(document: Doc): DocumentViewProps { return { Document: document, addDocument: this.props.addDocument, @@ -255,8 +255,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { ScreenToLocalTransform: this.getTransform, isTopMost: false, selectOnLoad: document.Id === this._selectOnLoaded, - PanelWidth: document.Width, - PanelHeight: document.Height, + PanelWidth: () => Cast(document.width, "number", 0),//TODO Types These are inline functions + PanelHeight: () => Cast(document.height, "number", 0), ContentScaling: returnOne, ContainingCollectionView: this.props.CollectionView, focus: this.focusDocument, @@ -267,13 +267,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @computed get views() { - var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1); - let docviews = this.props.Document.GetList(this.props.fieldKey, [] as Document[]).filter(doc => doc).reduce((prev, doc) => { - var page = doc.GetNumber(KeyStore.Page, -1); + let curPage = FieldValue(this.Document.curPage, -1); + let docviews = (this.children || []).filter(doc => doc).reduce((prev, doc) => { + var page = Cast(doc.page, "number", -1); if (page === curPage || page === -1) { - let minim = doc.GetT(KeyStore.IsMinimized, BooleanField); - if (minim === undefined || (minim && !minim.Data)) - prev.push(); + let minim = Cast(doc.isMinimized, "boolean"); + if (minim === undefined || !minim) { + prev.push(); + } } return prev; }, [] as JSX.Element[]); @@ -316,9 +317,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @observer class CollectionFreeFormOverlayView extends React.Component { @computed get overlayView() { - let overlayLayout = this.props.Document.GetText(KeyStore.OverlayLayout, ""); + let overlayLayout = Cast(this.props.Document.overlayLayout, "string", ""); return !overlayLayout ? (null) : - (); } render() { @@ -329,9 +330,9 @@ class CollectionFreeFormOverlayView extends React.Component { @observer class CollectionFreeFormBackgroundView extends React.Component { @computed get backgroundView() { - let backgroundLayout = this.props.Document.GetText(KeyStore.BackgroundLayout, ""); + let backgroundLayout = Cast(this.props.Document.backgroundLayout, "string", ""); return !backgroundLayout ? (null) : - (); } render() { diff --git a/src/new_fields/Types.ts b/src/new_fields/Types.ts index 55083765a..4ad8dcade 100644 --- a/src/new_fields/Types.ts +++ b/src/new_fields/Types.ts @@ -38,7 +38,7 @@ export interface Interface { } export function Cast | ListSpec>(field: FieldResult, ctor: T): FieldResult>; -export function Cast | ListSpec>(field: FieldResult, ctor: T, defaultVal: ToType): ToType; +export function Cast | ListSpec>(field: FieldResult, ctor: T, defaultVal: WithoutList>): WithoutList>; export function Cast | ListSpec>(field: FieldResult, ctor: T, defaultVal?: ToType): FieldResult> | undefined { if (field instanceof Promise) { return defaultVal === undefined ? field.then(f => Cast(f, ctor) as any) as any : defaultVal; diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts deleted file mode 100644 index 79ca5e55d..000000000 --- a/src/server/ServerUtil.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { HistogramField } from "../client/northstar/dash-fields/HistogramField"; -import { AudioField } from "../fields/AudioField"; -import { BooleanField } from "../fields/BooleanField"; -import { HtmlField } from "../fields/HtmlField"; -import { InkField } from "../fields/InkField"; -import { PDFField } from "../fields/PDFField"; -import { ScriptField } from "../fields/ScriptField"; -import { TupleField } from "../fields/TupleField"; -import { VideoField } from "../fields/VideoField"; -import { WebField } from "../fields/WebField"; -import { Utils } from "../Utils"; -import { Document } from "./../fields/Document"; -import { Field } from "./../fields/Field"; -import { ImageField } from "./../fields/ImageField"; -import { Key } from "./../fields/Key"; -import { ListField } from "./../fields/ListField"; -import { NumberField } from "./../fields/NumberField"; -import { RichTextField } from "./../fields/RichTextField"; -import { TextField } from "./../fields/TextField"; -import { Transferable, Types } from "./Message"; -import { IconField } from "../fields/IconFIeld"; - -export class ServerUtils { - public static prepend(extension: string): string { - return window.location.origin + extension; - } - - public static FromJson(json: Transferable): Field { - - if (!(json.data !== undefined && json.id && json.type !== undefined)) { - console.log( - "how did you manage to get an object that doesn't have a data or an id?" - ); - return new TextField("Something to fill the space", Utils.GenerateGuid()); - } - - switch (json.type) { - case Types.Boolean: return new BooleanField(json.data, json.id, false); - case Types.Number: return new NumberField(json.data, json.id, false); - case Types.Text: return new TextField(json.data, json.id, false); - case Types.Icon: return new IconField(json.data, json.id, false); - case Types.Html: return new HtmlField(json.data, json.id, false); - case Types.Web: return new WebField(new URL(json.data), json.id, false); - case Types.RichText: return new RichTextField(json.data, json.id, false); - case Types.Key: return new Key(json.data, json.id, false); - case Types.Image: return new ImageField(new URL(json.data), json.id, false); - case Types.HistogramOp: return HistogramField.FromJson(json.id, json.data); - case Types.PDF: return new PDFField(new URL(json.data), json.id, false); - case Types.List: return ListField.FromJson(json.id, json.data); - case Types.Script: return ScriptField.FromJson(json.id, json.data); - case Types.Audio: return new AudioField(new URL(json.data), json.id, false); - case Types.Video: return new VideoField(new URL(json.data), json.id, false); - case Types.Tuple: return new TupleField(json.data, json.id, false); - case Types.Ink: return InkField.FromJson(json.id, json.data); - case Types.Document: return Document.FromJson(json.data, json.id, false); - default: - throw Error( - "Error, unrecognized field type received from server. If you just created a new field type, be sure to add it here" - ); - } - } -} diff --git a/test/test.ts b/test/test.ts index 16cace026..91dc43379 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1,171 +1,35 @@ -import { NumberField } from "../src/fields/NumberField"; import { expect } from 'chai'; import 'mocha'; -import { Key } from "../src/fields/Key"; -import { Document } from "../src/fields/Document"; import { autorun, reaction } from "mobx"; -import { DocumentReference } from "../src/fields/DocumentReference"; -import { TextField } from "../src/fields/TextField"; -import { Field, FieldWaiting } from "../src/fields/Field"; - -describe('Number Controller', () => { - it('Should be constructable', () => { - const numController = new NumberField(15); - expect(numController.Data).to.equal(15); - }); - - it('Should update', () => { - const numController = new NumberField(15); - let ran = false; - reaction(() => numController.Data, (data) => { ran = true; }); - expect(ran).to.equal(false); - numController.Data = 5; - expect(ran).to.equal(true); - }); -}); +import { Doc } from '../src/new_fields/Doc'; +import { Cast } from '../src/new_fields/Types'; describe("Document", () => { it('should hold fields', () => { - let key = new Key("Test"); - let key2 = new Key("Test2"); - let field = new NumberField(15); - let doc = new Document(); - doc.Set(key, field); - let getField = doc.GetT(key, NumberField); - let getField2 = doc.GetT(key2, NumberField); + let key = "Test"; + let key2 = "Test2"; + let field = 15; + let doc = new Doc(); + doc[key] = field; + let getField = Cast(doc[key], "number"); + let getField2 = Cast(doc[key2], "number"); expect(getField).to.equal(field); expect(getField2).to.equal(undefined); }); it('should update', () => { - let doc = new Document(); - let key = new Key("Test"); - let key2 = new Key("Test2"); + let doc = new Doc(); + let key = "Test"; + let key2 = "Test2"; let ran = false; - reaction(() => doc.Get(key), (field) => { ran = true; }); + reaction(() => doc[key], (field) => { ran = true; }); expect(ran).to.equal(false); - doc.Set(key2, new NumberField(4)); + doc[key2] = 4; expect(ran).to.equal(false); - doc.Set(key, new NumberField(5)); + doc[key] = 5; expect(ran).to.equal(true); }); }); - -describe("Reference", () => { - it('should dereference', () => { - let doc = new Document(); - let doc2 = new Document(); - const key = new Key("test"); - const key2 = new Key("test2"); - - const numCont = new NumberField(55); - doc.Set(key, numCont); - let ref = new DocumentReference(doc, key); - let ref2 = new DocumentReference(doc, key2); - doc2.Set(key2, ref); - - let ref3 = new DocumentReference(doc2, key2); - let ref4 = new DocumentReference(doc2, key); - - expect(ref.Dereference()).to.equal(numCont); - expect(ref.DereferenceToRoot()).to.equal(numCont); - expect(ref2.Dereference()).to.equal(undefined); - expect(ref2.DereferenceToRoot()).to.equal(undefined); - expect(ref3.Dereference()).to.equal(ref); - expect(ref3.DereferenceToRoot()).to.equal(numCont); - expect(ref4.Dereference()).to.equal(undefined); - expect(ref4.DereferenceToRoot()).to.equal(undefined); - }); - - it('should work with prototypes', () => { - let doc = new Document; - let doc2 = doc.MakeDelegate(); - let key = new Key("test"); - expect(doc.Get(key)).to.equal(undefined); - expect(doc2.Get(key)).to.equal(undefined); - let num = new NumberField(55); - let num2 = new NumberField(56); - - doc.Set(key, num); - expect(doc.Get(key)).to.equal(num); - expect(doc2.Get(key)).to.equal(num); - - doc2.Set(key, num2); - expect(doc.Get(key)).to.equal(num); - expect(doc2.Get(key)).to.equal(num2); - }); - - it('should update through layers', () => { - let doc = new Document(); - let doc2 = new Document(); - let doc3 = new Document(); - const key = new Key("test"); - const key2 = new Key("test2"); - const key3 = new Key("test3"); - - const numCont = new NumberField(55); - doc.Set(key, numCont); - const ref = new DocumentReference(doc, key); - doc2.Set(key2, ref); - const ref3 = new DocumentReference(doc2, key2); - doc3.Set(key3, ref3); - - let ran = false; - reaction(() => { - let field = ((doc3.Get(key3)).DereferenceToRoot()).GetValue(); - return field; - }, (field) => { - ran = true; - }); - expect(ran).to.equal(false); - - numCont.Data = 44; - expect(ran).to.equal(true); - ran = false; - - doc.Set(key, new NumberField(33)); - expect(ran).to.equal(true); - ran = false; - - doc.Set(key2, new NumberField(4)); - expect(ran).to.equal(false); - - doc2.Set(key2, new TextField("hello")); - expect(ran).to.equal(true); - ran = false; - - doc3.Set(key3, new TextField("world")); - expect(ran).to.equal(true); - ran = false; - }); - - it('should update with prototypes', () => { - let doc = new Document(); - let doc2 = doc.MakeDelegate(); - const key = new Key("test"); - - const numCont = new NumberField(55); - - let ran = false; - reaction(() => { - let field = doc2.GetT(key, NumberField); - if (field && field !== FieldWaiting) { - return field.Data; - } - return undefined; - }, (field) => { - ran = true; - }); - expect(ran).to.equal(false); - - doc.Set(key, numCont); - expect(ran).to.equal(true); - - ran = false; - numCont.Data = 1; - expect(ran).to.equal(true); - }); -}); \ No newline at end of file -- cgit v1.2.3-70-g09d2