aboutsummaryrefslogtreecommitdiff
path: root/src/client/views
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views')
-rw-r--r--src/client/views/Main.tsx77
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx17
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx25
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx2
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx4
-rw-r--r--src/client/views/nodes/FieldView.tsx7
-rw-r--r--src/client/views/nodes/HistogramBox.scss8
-rw-r--r--src/client/views/nodes/HistogramBox.tsx67
8 files changed, 172 insertions, 35 deletions
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 06a9a92d3..d2ba6998c 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -1,4 +1,4 @@
-import { action, configure, observable, runInAction, trace, computed } from 'mobx';
+import { action, configure, observable, runInAction, trace, computed, reaction } from 'mobx';
import "normalize.css";
import * as React from 'react';
import * as ReactDOM from 'react-dom';
@@ -23,7 +23,6 @@ import "./Main.scss";
import { observer } from 'mobx-react';
import { InkingControl } from './InkingControl';
import { RouteStore } from '../../server/RouteStore';
-import { json } from 'body-parser';
import { library } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faFont } from '@fortawesome/free-solid-svg-icons';
@@ -37,15 +36,18 @@ import { faRedoAlt } from '@fortawesome/free-solid-svg-icons';
import { faPenNib } from '@fortawesome/free-solid-svg-icons';
import { faFilm } from '@fortawesome/free-solid-svg-icons';
import { faMusic } from '@fortawesome/free-solid-svg-icons';
+import { faTree } 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 { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
import { Field, Opt, FieldWaiting } from '../../fields/Field';
import { ListField } from '../../fields/ListField';
-import { map } from 'bluebird';
import { Gateway, Settings } from '../northstar/manager/Gateway';
-import { Catalog } from '../northstar/model/idea/idea';
+import { Catalog, Schema, Attribute, AttributeGroup } from '../northstar/model/idea/idea';
+import { ArrayUtil } from '../northstar/utils/ArrayUtil';
+import '../northstar/model/ModelExtensions'
+import '../northstar/utils/Extensions'
@observer
export class Main extends React.Component {
@@ -53,7 +55,8 @@ export class Main extends React.Component {
@observable private mainfreeform?: Document;
@observable public pwidth: number = 0;
@observable public pheight: number = 0;
- @observable private _northstarCatalog: Catalog | undefined = undefined;
+ @observable ActiveSchema: Schema | undefined;
+ private _northstarColumns: Document[] = [];
@computed private get mainContainer(): Document | undefined {
let doc = this.userDocument.GetT(KeyStore.ActiveWorkspace, Document);
@@ -87,6 +90,10 @@ export class Main extends React.Component {
};
// this.initializeNorthstar();
+ let y = "";
+ y.ReplaceAll("a", "B");
+
+ CurrentUserUtils.loadCurrentUser();
library.add(faFont);
library.add(faImage);
@@ -99,29 +106,12 @@ export class Main extends React.Component {
library.add(faPenNib);
library.add(faFilm);
library.add(faMusic);
-
+ library.add(faTree);
this.initEventListeners();
Documents.initProtos(() => this.initAuthenticationRouters());
- }
- @action SetNorthstarCatalog(ctlog: Catalog) {
- this._northstarCatalog = ctlog;
- if (this._northstarCatalog) {
- console.log("CATALOG " + this._northstarCatalog.schemas);
- }
- }
- async initializeNorthstar(): Promise<void> {
- let envPath = "assets/env.json";
- const response = await fetch(envPath, {
- redirect: "follow",
- method: "GET",
- credentials: "include"
- });
- const env = await response.json();
- Settings.Instance.Update(env);
- let cat = Gateway.Instance.ClearCatalog();
- cat.then(async () => this.SetNorthstarCatalog(await Gateway.Instance.GetCatalog()));
+ this.initializeNorthstar();
}
onHistory = () => {
@@ -259,6 +249,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 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" }));
@@ -273,6 +264,7 @@ export class Main extends React.Component {
[React.createRef<HTMLDivElement>(), "music", "Add Audio", addAudioNode],
[React.createRef<HTMLDivElement>(), "globe-asia", "Add Web Clipping", addWebNode],
[React.createRef<HTMLDivElement>(), "object-group", "Add Collection", addColNode],
+ [React.createRef<HTMLDivElement>(), "tree", "Add Tree", addTreeNode],
[React.createRef<HTMLDivElement>(), "table", "Add Schema", addSchemaNode],
]
@@ -344,6 +336,43 @@ export class Main extends React.Component {
</div>
);
}
+
+ // --------------- Northstar hooks ------------- /
+
+ @action SetNorthstarCatalog(ctlog: Catalog) {
+ if (ctlog && ctlog.schemas) {
+ this.ActiveSchema = ArrayUtil.FirstOrDefault<Schema>(ctlog.schemas!, (s: Schema) => s.displayName === "mimic");
+ this._northstarColumns = this.GetAllNorthstarColumnAttributes().map(a => Documents.HistogramDocument({ width: 200, height: 200, title: a.displayName! }));
+ }
+ }
+ async initializeNorthstar(): Promise<void> {
+ let envPath = "/assets/env.json";
+ const response = await fetch(envPath, {
+ redirect: "follow",
+ method: "GET",
+ credentials: "include"
+ });
+ const env = await response.json();
+ Settings.Instance.Update(env);
+ let cat = Gateway.Instance.ClearCatalog();
+ cat.then(async () => this.SetNorthstarCatalog(await Gateway.Instance.GetCatalog()));
+ }
+ public GetAllNorthstarColumnAttributes() {
+ if (!this.ActiveSchema || !this.ActiveSchema.rootAttributeGroup) {
+ return [];
+ }
+ const recurs = (attrs: Attribute[], g: AttributeGroup) => {
+ if (g.attributes) {
+ attrs.push.apply(attrs, g.attributes);
+ if (g.attributeGroups) {
+ g.attributeGroups.forEach(ng => recurs(attrs, ng));
+ }
+ }
+ };
+ const allAttributes: Attribute[] = new Array<Attribute>();
+ recurs(allAttributes, this.ActiveSchema.rootAttributeGroup);
+ return allAttributes;
+ }
}
CurrentUserUtils.loadCurrentUser().then(() => {
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index fd0810242..950df7261 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -17,6 +17,7 @@ import { COLLECTION_BORDER_WIDTH } from "./CollectionView";
import React = require("react");
import { SubCollectionViewProps } from "./CollectionViewBase";
import { ServerUtils } from "../../../server/ServerUtil";
+import { DragManager } from "../../util/DragManager";
@observer
export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
@@ -190,6 +191,21 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
@action
onPointerDown = (e: React.PointerEvent): void => {
var className = (e.target as any).className;
+ if ((className == "lm_title" || className == "lm_tab lm_active") && e.ctrlKey) {
+ e.stopPropagation();
+ e.preventDefault();
+ let docid = (e.target as any).DashDocId;
+ let tab = (e.target as any).parentElement as HTMLElement;
+ Server.GetField(docid, action((f: Opt<Field>) =>
+ DragManager.StartDocumentDrag(tab, new DragManager.DocumentDragData(f as Document),
+ {
+ handlers: {
+ dragComplete: action(() => { }),
+ },
+ hideSource: true
+ }))
+ );
+ }
if (className == "lm_drag_handle" || className == "lm_close" || className == "lm_maximise" || className == "lm_minimise" || className == "lm_close_tab") {
this._flush = true;
}
@@ -208,6 +224,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
this.stateChanged();
}
tabCreated = (tab: any) => {
+ tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId;
tab.closeElement.off('click') //unbind the current click handler
.click(function () {
tab.contentItem.remove();
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index b986f394c..34b019244 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -2,7 +2,7 @@ import React = require("react")
import { library } from '@fortawesome/fontawesome-svg-core';
import { faCog } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable } from "mobx";
+import { action, computed, observable, trace, untracked } from "mobx";
import { observer } from "mobx-react";
import Measure from "react-measure";
import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults } from "react-table";
@@ -141,6 +141,7 @@ export class CollectionSchemaView extends CollectionViewBase {
};
}
+ @computed
get columns() {
return this.props.Document.GetList<Key>(KeyStore.ColumnsKey, []);
}
@@ -167,10 +168,19 @@ export class CollectionSchemaView extends CollectionViewBase {
this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage == 0 ? 33 : 0);
}
}
- findAllDocumentKeys = (): { [id: string]: boolean } => {
+
+ @computed
+ get findAllDocumentKeys(): { [id: string]: boolean } {
const docs = this.props.Document.GetList<Document>(this.props.fieldKey, []);
let keys: { [id: string]: boolean } = {}
- docs.map(doc => doc.GetAllPrototypes().map(proto => proto._proxies.forEach((val: any, key: string) => keys[key] = false)));
+ if (this._optionsActivated > -1) {
+ // bcz: ugh. this is untracked since otherwise a large collection of documents will blast the server for all their fields.
+ // then as each document's fields come back, we update the documents _proxies. Each time we do this, the whole schema will be
+ // invalidated and re-rendered. This workaround will inquire all of the document fields before the options button is clicked.
+ // then by the time the options button is clicked, all of the fields should be in place. If a new field is added while this menu
+ // is displayed (unlikely) it won't show up until something else changes.
+ untracked(() => docs.map(doc => doc.GetAllPrototypes().map(proto => proto._proxies.forEach((val: any, key: string) => keys[key] = false))));
+ }
this.columns.forEach(key => keys[key.Id] = true)
return keys;
}
@@ -228,13 +238,18 @@ export class CollectionSchemaView extends CollectionViewBase {
}
}
+ @observable _optionsActivated: number = 0;
+ @action
+ OptionsMenuDown = (e: React.PointerEvent) => {
+ this._optionsActivated++;
+ }
render() {
library.add(faCog);
const columns = this.columns;
const children = this.props.Document.GetList<Document>(this.props.fieldKey, []);
const selected = children.length > this._selectedIndex ? children[this._selectedIndex] : undefined;
//all the keys/columns that will be displayed in the schema
- const allKeys = this.findAllDocumentKeys();
+ const allKeys = this.findAllDocumentKeys;
let content = this._selectedIndex == -1 || !selected ? (null) : (
<Measure onResize={this.setScaling}>
{({ measureRef }) =>
@@ -274,7 +289,7 @@ export class CollectionSchemaView extends CollectionViewBase {
</div>
</div>
}>
- <button id="schemaOptionsMenuBtn"><FontAwesomeIcon style={{ color: "white" }} icon="cog" size="sm" /></button>
+ <button id="schemaOptionsMenuBtn" onPointerDown={this.OptionsMenuDown}><FontAwesomeIcon style={{ color: "white" }} icon="cog" size="sm" /></button>
</Flyout>);
return (
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index ec1bf5d0e..6cc14ebcb 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -76,7 +76,7 @@ class TreeView extends React.Component<TreeViewProps> {
}}
/>);
return (
- <div key={this.props.document.Id} className="docContainer" ref={reference} onPointerDown={onItemDown}>
+ <div className="docContainer" ref={reference} onPointerDown={onItemDown}>
{editableView(this.props.document.Title)}
<div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div>
</div >)
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index ce72ab64b..2f0459f88 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -19,6 +19,7 @@ import { KeyValueBox } from "./KeyValueBox";
import { PDFBox } from "./PDFBox";
import { VideoBox } from "./VideoBox";
import { WebBox } from "./WebBox";
+import { HistogramBox } from "./HistogramBox";
import React = require("react");
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
@@ -47,8 +48,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
render() {
return <JsxParser
- components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox }}
- bindings={this.CreateBindings()}
+ components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }} bindings={this.CreateBindings()}
jsx={this.layout}
showWarnings={true}
onError={(test: any) => { console.log(test) }}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index b6d50bffb..f6343c631 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -7,7 +7,6 @@ import { TextField } from "../../../fields/TextField";
import { NumberField } from "../../../fields/NumberField";
import { RichTextField } from "../../../fields/RichTextField";
import { ImageField } from "../../../fields/ImageField";
-import { WebField } from "../../../fields/WebField";
import { VideoField } from "../../../fields/VideoField"
import { Key } from "../../../fields/Key";
import { FormattedTextBox } from "./FormattedTextBox";
@@ -64,9 +63,11 @@ export class FieldView extends React.Component<FieldViewProps> {
}
else if (field instanceof AudioField) {
return <AudioBox {...this.props} />
- } else if (field instanceof Document) {
+ }
+ else if (field instanceof Document) {
return <div>{field.Title}</div>
- } else if (field instanceof ListField) {
+ }
+ else if (field instanceof ListField) {
return (<div>
{(field as ListField<Field>).Data.map(f => {
return f instanceof Document ? f.Title : f.GetValue().toString();
diff --git a/src/client/views/nodes/HistogramBox.scss b/src/client/views/nodes/HistogramBox.scss
new file mode 100644
index 000000000..04bf1d732
--- /dev/null
+++ b/src/client/views/nodes/HistogramBox.scss
@@ -0,0 +1,8 @@
+.histogrambox-container {
+ padding: 0vw;
+ position: relative;
+ text-align: center;
+ 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
new file mode 100644
index 000000000..223fdf0d8
--- /dev/null
+++ b/src/client/views/nodes/HistogramBox.tsx
@@ -0,0 +1,67 @@
+import React = require("react")
+import { observer } from "mobx-react";
+import { FieldView, FieldViewProps } from './FieldView';
+import "./VideoBox.scss";
+import { observable, reaction } from "mobx";
+import { HistogramOperation } from "../../northstar/operations/HistogramOperation";
+import { Main } from "../Main";
+import { ColumnAttributeModel } from "../../northstar/core/attribute/AttributeModel";
+import { AttributeTransformationModel } from "../../northstar/core/attribute/AttributeTransformationModel";
+import { AggregateFunction, HistogramResult, DoubleValueAggregateResult } from "../../northstar/model/idea/idea";
+import { ModelHelpers } from "../../northstar/model/ModelHelpers";
+
+@observer
+export class HistogramBox extends React.Component<FieldViewProps> {
+
+ public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(HistogramBox, fieldStr) }
+
+ constructor(props: FieldViewProps) {
+ super(props);
+ }
+
+ @observable _histoResult?: HistogramResult;
+ _histoOp?: HistogramOperation;
+
+ componentDidMount() {
+ Main.Instance.GetAllNorthstarColumnAttributes().map(a => {
+ if (a.displayName == this.props.doc.Title) {
+ var atmod = new ColumnAttributeModel(a);
+ this._histoOp = new HistogramOperation(new AttributeTransformationModel(atmod, AggregateFunction.None),
+ new AttributeTransformationModel(atmod, AggregateFunction.Count),
+ new AttributeTransformationModel(atmod, AggregateFunction.Count));
+ reaction(() => [this._histoOp && this._histoOp.Result],
+ () => this._histoResult = this._histoOp ? this._histoOp.Result as HistogramResult : undefined
+ );
+ this._histoOp.Update();
+ }
+ })
+ }
+
+ twoString() {
+ let str = "";
+ if (this._histoResult && !this._histoResult.isEmpty) {
+ for (let key in this._histoResult.bins) {
+ if (this._histoResult.bins.hasOwnProperty(key)) {
+ let bin = this._histoResult.bins[key];
+ str += JSON.stringify(bin.binIndex!.toJSON()) + " = ";
+ let valueAggregateKey = ModelHelpers.CreateAggregateKey(this._histoOp!.V, this._histoResult, ModelHelpers.AllBrushIndex(this._histoResult));
+ let value = ModelHelpers.GetAggregateResult(bin, valueAggregateKey) as DoubleValueAggregateResult;
+ if (value && value.hasResult && value.result) {
+ str += value.result;
+ }
+ }
+ }
+ }
+ return str;
+ }
+
+ render() {
+ if (!this._histoResult)
+ return (null);
+ return (
+ <div className="histogrambox-container">
+ `HISTOGRAM RESULT : ${this.twoString()}`
+ </div>
+ )
+ }
+} \ No newline at end of file