diff --git a/.babelrc b/.babelrc
index f5277971..f104f6f6 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,3 +1,4 @@
{
- "presets": ["es2015", "stage-2", "react", "flow"]
+ "presets": ["es2015", "stage-2", "react", "flow"],
+ "plugins": ["transform-decorators-legacy"]
}
diff --git a/modules/bus/store.js b/modules/bus/store.js
deleted file mode 100644
index 758ba412..00000000
--- a/modules/bus/store.js
+++ /dev/null
@@ -1,61 +0,0 @@
-
-export class Store {
-
- constructor() {
- this.state = {};
- this.listeners = {};
- this.locked = false;
- }
-
- subscribe(key, callback) {
- let listenerList = this.listeners[key];
- if (listenerList === undefined) {
- listenerList = [];
- this.listeners[key] = listenerList;
- }
- listenerList.push(callback);
- return callback;
- };
-
- unsubscribe(key, callback) {
- const listenerList = this.listeners[key];
- for (let i = 0; i < listenerList.length; i++) {
- if (listenerList[i] === callback) {
- listenerList.splice(i, 1);
- return;
- }
- }
- };
-
- dispatch(key, newValue, oldValue) {
- if (this.locked === true) {
- throw 'concurrent state modification';
- }
- this.locked = true;
- try {
- let listenerList = this.listeners[key];
- if (listenerList !== undefined) {
- for (let i = 0; i < listenerList.length; i++) {
- const callback = listenerList[i];
- try {
- callback(newValue, oldValue, this);
- } catch(e) {
- console.error(e);
- }
- }
- }
- } finally {
- this.locked = false;
- }
- };
-
- set(key, value) {
- let oldValue = this.state[key];
- this.state[key] = value;
- this.dispatch(key, value, oldValue);
- }
-
- get(key) {
- return this.state[key];
- }
-}
\ No newline at end of file
diff --git a/modules/context/index.js b/modules/context/index.js
new file mode 100644
index 00000000..26011fa8
--- /dev/null
+++ b/modules/context/index.js
@@ -0,0 +1,9 @@
+import Bus from '../bus';
+import {observable} from 'mobx';
+
+export default {
+ services: {},
+ streams: {},
+ //@deprecated
+ bus: new Bus()
+};
\ No newline at end of file
diff --git a/modules/lstream/base.js b/modules/lstream/base.js
new file mode 100644
index 00000000..c221dd23
--- /dev/null
+++ b/modules/lstream/base.js
@@ -0,0 +1,50 @@
+
+export class StreamBase {
+
+ attach() {}
+
+ next(value) {}
+
+ map(fn) {
+ return new MapStream(this, fn);
+ }
+
+ filter(predicate) {
+ return new FilterStream(this, predicate);
+ }
+}
+
+
+export class MapStream extends StreamBase {
+
+ constructor(stream, fn) {
+ super();
+ this.stream = stream;
+ this.fn = fn;
+ }
+
+ attach(observer) {
+ return this.stream.attach(val => observer(this.fn(val)));
+ }
+
+ static create = (stream, fn) => new MapStream(stream, fn);
+}
+
+export class FilterStream extends StreamBase {
+
+ constructor(stream, predicate) {
+ super();
+ this.stream = stream;
+ this.predicate = predicate;
+ }
+
+ attach(observer) {
+ return this.stream.attach(val => {
+ if (this.predicate(val)) {
+ observer(val);
+ }
+ });
+ }
+
+ static create = (stream, predicate) => new FilterStream(stream, predicate);
+}
diff --git a/modules/lstream/combine.js b/modules/lstream/combine.js
new file mode 100644
index 00000000..bb6d1834
--- /dev/null
+++ b/modules/lstream/combine.js
@@ -0,0 +1,38 @@
+import {StreamBase} from './base';
+
+export class CombineStream extends StreamBase {
+
+ constructor(streams) {
+ super();
+ this.streams = streams;
+ this.values = this.streams.map(() => NOT_INITIALIZED);
+ this.ready = false;
+ }
+
+ attach(observer) {
+ let detachers = new Array(this.streams.length);
+ this.streams.forEach((s, i) => {
+ detachers[i] = s.attach(value => {
+ this.values[i] = value;
+ if (!this.ready) {
+ this.ready = this.isReady();
+ }
+ if (this.ready) {
+ observer(this.values);
+ }
+ });
+ });
+ return () => detachers.forEach(d => d());
+ }
+
+ isReady() {
+ for (let val of this.values) {
+ if (val === NOT_INITIALIZED) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
+
+const NOT_INITIALIZED = {};
\ No newline at end of file
diff --git a/modules/lstream/emitter.js b/modules/lstream/emitter.js
new file mode 100644
index 00000000..3a0d9444
--- /dev/null
+++ b/modules/lstream/emitter.js
@@ -0,0 +1,41 @@
+import {StreamBase} from './base';
+
+const READY = 0;
+const EMITTING = 1;
+
+export class Emitter extends StreamBase {
+
+ constructor() {
+ super();
+ this.observers = [];
+ this.state = READY;
+ }
+
+ attach(observer) {
+ this.observers.push(observer);
+ return () => this.detach(observer);
+ }
+
+ detach(callback) {
+ for (let i = this.observers.length - 1; i >= 0 ; i--) {
+ if (this.observers[i] === callback) {
+ this.observers.splice(i, 1);
+ }
+ }
+ };
+
+ next(value) {
+ if (this.state === EMITTING) {
+ console.warn('recursive dispatch');
+ return;
+ }
+ try {
+ this.state = EMITTING;
+ for (let i = 0; i < this.observers.length; i++) {
+ this.observers[i](value);
+ }
+ } finally {
+ this.state = READY;
+ }
+ }
+}
diff --git a/modules/lstream/index.js b/modules/lstream/index.js
new file mode 100644
index 00000000..baea2b91
--- /dev/null
+++ b/modules/lstream/index.js
@@ -0,0 +1,22 @@
+import {CombineStream} from './combine';
+import {StateStream} from './state';
+import {Emitter} from './emitter';
+import {FilterStream, MapStream} from './base';
+
+export function combine(...streams) {
+ return new CombineStream(streams);
+}
+
+export function stream() {
+ return new Emitter();
+}
+
+export function state(initialValue) {
+ return new StateStream(initialValue);
+}
+
+export const map = MapStream.create;
+
+export const filter = FilterStream.create;
+
+export const merger = states => states.reduce((acc, v) => Object.assign(acc, v), {});
diff --git a/modules/lstream/state.js b/modules/lstream/state.js
new file mode 100644
index 00000000..f74fcb48
--- /dev/null
+++ b/modules/lstream/state.js
@@ -0,0 +1,38 @@
+import {Emitter} from './emitter';
+
+export class StateStream extends Emitter {
+
+ constructor(initialValue) {
+ super();
+ this._value = initialValue;
+ }
+
+ get value() {
+ return this._value;
+ }
+
+ set value(v) {
+ this.next(v);
+ }
+
+ next(v) {
+ this._value = v;
+ super.next(v);
+ }
+
+ update(updater) {
+ this.value = updater(this._value);
+ }
+
+ mutate(mutator) {
+ mutator(this._value);
+ this.next(this._value);
+ }
+
+ attach(observer) {
+ observer(this._value);
+ return super.attach(observer);
+ }
+}
+
+
diff --git a/modules/ui/connect.js b/modules/ui/connect.js
new file mode 100644
index 00000000..f1f01c8c
--- /dev/null
+++ b/modules/ui/connect.js
@@ -0,0 +1,29 @@
+import React from 'react';
+import context from 'context';
+
+export default function connect(streamProvider) {
+ return function (Component) {
+ return class Connected extends React.Component {
+
+ streamProps = {};
+
+ componentWillMount() {
+ let stream = streamProvider(context.streams, this.props);
+ this.detacher = stream.attach(data => {
+ this.streamProps = data;
+ this.forceUpdate();
+ });
+ }
+
+ componentWillUnmount() {
+ this.detacher();
+ }
+
+ render() {
+ return ;
+
+ }
+ };
+ }
+}
diff --git a/modules/ui/connect.jsx b/modules/ui/connectLegacy.jsx
similarity index 100%
rename from modules/ui/connect.jsx
rename to modules/ui/connectLegacy.jsx
diff --git a/modules/ui/decoratorChain.js b/modules/ui/decoratorChain.js
new file mode 100644
index 00000000..4ee20e46
--- /dev/null
+++ b/modules/ui/decoratorChain.js
@@ -0,0 +1,9 @@
+export default function decoratorChain() {
+ let decorators = Array.from(arguments);
+ return function(Component) {
+ for (let i = decorators.length - 1; i >= 0; i --) {
+ Component = decorators[i](Component);
+ }
+ return Component;
+ }
+}
\ No newline at end of file
diff --git a/modules/ui/mapContext.js b/modules/ui/mapContext.js
new file mode 100644
index 00000000..e3fd9c98
--- /dev/null
+++ b/modules/ui/mapContext.js
@@ -0,0 +1,11 @@
+import React from 'react';
+import context from 'context';
+
+export default function mapContext(mapper) {
+ return function (Component) {
+ return function ContextMapper(props) {
+ let actions = mapper(context, props);
+ return
+ }
+ }
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index 442c2fad..6886a159 100644
--- a/package.json
+++ b/package.json
@@ -28,22 +28,23 @@
"babel-core": "6.26.0",
"babel-eslint": "8.0.2",
"babel-loader": "7.1.2",
- "babel-preset-es2015": "6.24.1",
- "babel-preset-stage-2": "6.24.1",
+ "babel-plugin-transform-decorators-legacy": "1.3.5",
"babel-polyfill": "6.26.0",
- "babel-preset-react": "6.24.1",
+ "babel-preset-es2015": "6.24.1",
"babel-preset-flow": "6.23.0",
+ "babel-preset-react": "6.24.1",
+ "babel-preset-stage-2": "6.24.1",
"css-loader": "0.28.7",
- "less-loader": "4.0.5",
"eslint": "4.13.1",
"eslint-plugin-babel": "4.1.2",
"eslint-plugin-import": "2.8.0",
"eslint-plugin-react": "7.5.1",
+ "grunt": "1.0.1",
+ "grunt-contrib-copy": "1.0.0",
+ "less-loader": "4.0.5",
"style-loader": "0.13.1",
"webpack": "3.10.0",
- "webpack-dev-server": "2.9.7",
- "grunt": "1.0.1",
- "grunt-contrib-copy": "1.0.0"
+ "webpack-dev-server": "2.9.7"
},
"dependencies": {
"classnames": "2.2.5",
@@ -56,7 +57,6 @@
"json-loader": "0.5.4 ",
"less": "2.7.3",
"libtess": "1.2.2",
- "mobx": "^4.3.0",
"mousetrap": "1.6.1",
"numeric": "1.2.6",
"prop-types": "15.6.0",
diff --git a/web/app/brep/debug/debugger/BrepDebuggerWindow.jsx b/web/app/brep/debug/debugger/BrepDebuggerWindow.jsx
index f2a993c4..a8b1fd72 100644
--- a/web/app/brep/debug/debugger/BrepDebuggerWindow.jsx
+++ b/web/app/brep/debug/debugger/BrepDebuggerWindow.jsx
@@ -1,7 +1,7 @@
import React from 'react';
import Window from 'ui/components/Window';
import BrepDebugger from './brepDebugger';
-import connect, {PROPAGATE_SELF_PROPS} from 'ui/connect';
+import connect, {PROPAGATE_SELF_PROPS} from 'ui/connectLegacy';
import {addToGroup, clearGroup, createGroup, removeFromGroup} from 'scene/sceneGraph';
import {createToken} from 'bus';
import Fa from 'ui/components/Fa';
diff --git a/web/app/cad/actions/actionButtonBehavior.js b/web/app/cad/actions/actionButtonBehavior.js
index 04fcad6d..57c22b14 100644
--- a/web/app/cad/actions/actionButtonBehavior.js
+++ b/web/app/cad/actions/actionButtonBehavior.js
@@ -1,13 +1,11 @@
-import {TOKENS as ACTION_TOKENS} from "./actionSystemPlugin";
export function mapActionBehavior(actionIdGetter) {
- return ({dispatch}, props) => {
+ return ({services}, props) => {
const actionId = actionIdGetter(props);
- const actionRunToken = ACTION_TOKENS.actionRun(actionId);
let request = {actionId, x:0, y:0};
let canceled = true;
- let showed = false;
+ let shown = false;
function updateCoords({pageX, pageY}) {
request.x = pageX + 10;
@@ -15,23 +13,23 @@ export function mapActionBehavior(actionIdGetter) {
}
return {
- onClick: data => dispatch(actionRunToken, data),
+ onClick: e => services.action.run(actionId, e),
onMouseEnter: e => {
updateCoords(e);
canceled = false;
- showed = false;
+ shown = false;
setTimeout(() => {
if (!canceled) {
- showed = true;
- dispatch(ACTION_TOKENS.SHOW_HINT_FOR, request)
+ shown = true;
+ services.action.showHintFor(request)
}
}, 500);
},
onMouseMove: updateCoords,
onMouseLeave: () => {
canceled = true;
- if (showed) {
- dispatch(ACTION_TOKENS.SHOW_HINT_FOR, null)
+ if (shown) {
+ services.action.showHintFor(null)
}
}
}};
diff --git a/web/app/cad/actions/actionHelpers.js b/web/app/cad/actions/actionHelpers.js
index 9133f806..64aa2971 100644
--- a/web/app/cad/actions/actionHelpers.js
+++ b/web/app/cad/actions/actionHelpers.js
@@ -1,6 +1,6 @@
export function checkForSelectedFaces(amount) {
- return (state, context) => {
- state.enabled = context.services.selection.face.objects.length >= amount;
+ return (state, selection) => {
+ state.enabled = selection.length >= amount;
if (!state.enabled) {
state.hint = amount === 1 ? 'requires a face to be selected' : 'requires ' + amount + ' faces to be selected';
}
@@ -8,7 +8,7 @@ export function checkForSelectedFaces(amount) {
}
export function checkForSelectedSolids(amount) {
- return (state, context) => {
+ return (state, selection, context) => {
state.enabled = context.services.selection.face.objects.length >= amount;
if (!state.enabled) {
state.hint = amount === 1 ? 'requires a solid to be selected' : 'requires ' + amount + ' solids to be selected';
@@ -18,14 +18,14 @@ export function checkForSelectedSolids(amount) {
export function requiresFaceSelection(amount) {
return {
- listens: ['selection_face'],
+ listens: streams => streams.selection.face,
update: checkForSelectedFaces(amount)
}
}
export function requiresSolidSelection(amount) {
return {
- listens: ['selection_face'],
+ listens: streams => streams.selection.face,
update: checkForSelectedSolids(amount)
}
}
diff --git a/web/app/cad/actions/actionSystemPlugin.js b/web/app/cad/actions/actionSystemPlugin.js
index f56920bd..142880e3 100644
--- a/web/app/cad/actions/actionSystemPlugin.js
+++ b/web/app/cad/actions/actionSystemPlugin.js
@@ -1,56 +1,62 @@
-import {createToken} from 'bus';
-import {enableAnonymousActionHint} from "./anonHint";
+import {enableAnonymousActionHint} from './anonHint';
+import * as stream from 'lstream';
export function activate(context) {
- let {bus} = context;
+ let {bus, streams} = context;
+
+ streams.action = {
+ appearance: {},
+ state: {},
+ hint: stream.state(null)
+ };
+
+ let runners = {};
let showAnonymousActionHint = enableAnonymousActionHint(context);
function run(id, data) {
- bus.dispatch(TOKENS.actionRun(id), data);
+ let state = streams.action.state[id].value;
+ let runner = runners[id];
+ if (!state||!runner) {
+ console.warn('request to run nonexistent action')
+ return;
+ }
+ if (state.enabled) {
+ runner(context, data);
+ } else {
+ showAnonymousActionHint(id);
+ }
}
function register(action) {
- bus.enableState(TOKENS.actionAppearance(action.id), action.appearance);
+ streams.action.appearance[action.id] = stream.state(action.appearance);
+
+ runners[action.id] = action.invoke;
- let stateToken = TOKENS.actionState(action.id);
let initialState = {
hint: '',
enabled: true,
visible: true
};
- if (action.update) {
- action.update(initialState, context);
- }
- bus.enableState(stateToken, initialState);
-
+
+ let actionStateStream = stream.state(initialState);
+ streams.action.state[action.id] = actionStateStream;
+
if (action.update && action.listens) {
- const stateUpdater = () => {
- bus.updateState(stateToken, (actionState) => {
- actionState.hint = '';
- actionState.enabled = true;
- actionState.visible = true;
- action.update(actionState, context);
- return actionState;
- });
- };
-
- for (let event of action.listens) {
- bus.subscribe(event, stateUpdater);
- }
+ action.listens(streams).attach(data => {
+ actionStateStream.mutate(v => {
+ v.hint = '';
+ v.enabled = true;
+ v.visible = true;
+ action.update(v, data, context)
+ return v;
+ })
+ });
}
- bus.subscribe(TOKENS.actionRun(action.id), data => {
- if (bus.state[stateToken].enabled) {
- action.invoke(context, data)
- } else {
- showAnonymousActionHint(action.id);
- }
- });
}
- bus.enableState(TOKENS.HINT, null);
function registerAction(action) {
register(action);
}
@@ -58,40 +64,27 @@ export function activate(context) {
function registerActions(actions) {
actions.forEach(action => register(action));
}
-
- synchActionHint(bus);
- context.services.action = {run, registerAction, registerActions}
-}
-
-
-
-function synchActionHint(bus) {
-
- bus.subscribe(TOKENS.SHOW_HINT_FOR, request => {
+ function showHintFor(request) {
if (request) {
- let {actionId, x, y} = request;
- let actionState = bus.getState(TOKENS.actionState(actionId));
- let actionAppearance = bus.getState(TOKENS.actionAppearance(actionId));
+ let {actionId, x, y, requester} = request;
+ let actionState = streams.action.state[actionId].value;
+ let actionAppearance = streams.action.appearance[actionId].value;
if (actionState && actionAppearance) {
- bus.dispatch(TOKENS.HINT, {
- actionId, x, y,
+ streams.action.hint.value = {
+ actionId, x, y, requester,
info: actionAppearance.info,
hint: actionState.hint
- });
+ };
}
} else {
- bus.dispatch(TOKENS.HINT, null);
+ if (streams.action.hint.value !== null) {
+ streams.action.hint.value = null;
+ }
}
- });
+ }
+
+ context.services.action = {run, registerAction, registerActions, showHintFor}
}
-export const ACTION_NS = 'action';
-export const TOKENS = {
- actionState: (actionId) => createToken(ACTION_NS, 'state', actionId),
- actionAppearance: (actionId) => createToken(ACTION_NS, 'appearance', actionId),
- actionRun: (actionId) => createToken(ACTION_NS, 'run', actionId),
- SHOW_HINT_FOR: createToken(ACTION_NS, 'showHintFor'),
- HINT: createToken(ACTION_NS, 'hint'),
-};
\ No newline at end of file
diff --git a/web/app/cad/actions/anonHint.js b/web/app/cad/actions/anonHint.js
index c2a05140..86111de6 100644
--- a/web/app/cad/actions/anonHint.js
+++ b/web/app/cad/actions/anonHint.js
@@ -1,21 +1,17 @@
-import {TOKENS} from "./actionSystemPlugin";
-export function enableAnonymousActionHint({bus, services}) {
+export function enableAnonymousActionHint({streams, services}) {
- let autoHideCanceled = true;
- bus.subscribe(TOKENS.SHOW_HINT_FOR, () => autoHideCanceled = true);
-
return function(actionId) {
let {left, top} = services.dom.viewerContainer.getBoundingClientRect();
- bus.dispatch(TOKENS.SHOW_HINT_FOR, {
+ services.action.showHintFor({
actionId,
x: left + 100,
- y: top + 10
+ y: top + 10,
+ requester: 'anonymous'
});
- autoHideCanceled = false;
setTimeout(() => {
- if (!autoHideCanceled) {
- bus.dispatch(TOKENS.SHOW_HINT_FOR, null);
+ if (!streams.action.hint.value.requester === 'anonymous') {
+ services.action.showHintFor(null);
}
}, 1000);
}
diff --git a/web/app/cad/actions/coreActions.js b/web/app/cad/actions/coreActions.js
index d4ba54b6..d58bfd5c 100644
--- a/web/app/cad/actions/coreActions.js
+++ b/web/app/cad/actions/coreActions.js
@@ -9,7 +9,7 @@ export default [
icon96: 'img/cad/face-edit96.png',
info: 'open sketcher for a face/plane',
},
- listens: ['selection_face'],
+ listens: streams => streams.selection.face,
update: ActionHelpers.checkForSelectedFaces(1),
invoke: ({services}) => services.sketcher.sketchFace(services.selection.face.single)
},
diff --git a/web/app/cad/actions/operationActions.js b/web/app/cad/actions/operationActions.js
index 13f250b5..e4cc68b2 100644
--- a/web/app/cad/actions/operationActions.js
+++ b/web/app/cad/actions/operationActions.js
@@ -58,14 +58,14 @@ OPERATION_ACTIONS.forEach(action => mergeInfo(action));
function requiresFaceSelection(amount) {
return {
- listens: ['selection_face'],
+ listens: streams => streams.selection.face,
update: ActionHelpers.checkForSelectedFaces(amount)
}
}
function requiresSolidSelection(amount) {
return {
- listens: ['selection_face'],
+ listens: streams => streams.selection.face,
update: ActionHelpers.checkForSelectedSolids(amount)
}
}
diff --git a/web/app/cad/craft/wizard/components/EdgesSelectionControl.jsx b/web/app/cad/craft/wizard/components/EdgesSelectionControl.jsx
deleted file mode 100644
index 0d949bb7..00000000
--- a/web/app/cad/craft/wizard/components/EdgesSelectionControl.jsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import React from 'react';
-
-import TextControl from "ui/components/controls/TextControl";
-import Folder from '../../../../../../modules/ui/components/Folder';
-import MDForm from './MDForm';
-
-export default function EdgesSelectionControl({label, edges, onUpdate, itemMetadata}, {services}) {
- return
- {edges.map((subParams, i) =>
-
- )}
- ;
-}
-
diff --git a/web/app/cad/craft/wizard/components/FaceSelectionControl.jsx b/web/app/cad/craft/wizard/components/FaceSelectionControl.jsx
deleted file mode 100644
index b55ca535..00000000
--- a/web/app/cad/craft/wizard/components/FaceSelectionControl.jsx
+++ /dev/null
@@ -1,8 +0,0 @@
-import React from 'react';
-
-import TextControl from "ui/components/controls/TextControl";
-
-export default function FaceSelectionControl(props) {
- return
-}
-
diff --git a/web/app/cad/craft/wizard/components/HistoryWizard.jsx b/web/app/cad/craft/wizard/components/HistoryWizard.jsx
index 1d505ed3..48a1c97e 100644
--- a/web/app/cad/craft/wizard/components/HistoryWizard.jsx
+++ b/web/app/cad/craft/wizard/components/HistoryWizard.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import connect from '../../../../../../modules/ui/connect';
+import connect from '../../../../../../modules/ui/connectLegacy';
import {TOKENS as CRAFT_TOKENS} from '../../craftPlugin';
import Wizard from './Wizard';
import {finishHistoryEditing, stepOverridingParams} from '../../craftHistoryUtils';
diff --git a/web/app/cad/craft/wizard/components/MDForm.jsx b/web/app/cad/craft/wizard/components/MDForm.jsx
deleted file mode 100644
index 33618a5f..00000000
--- a/web/app/cad/craft/wizard/components/MDForm.jsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import React from 'react';
-import camelCaseSplit from 'gems/camelCaseSplit';
-import Label from 'ui/components/controls/Label';
-import NumberControl from 'ui/components/controls/NumberControl';
-import RadioButtons, {RadioButton} from 'ui/components/controls/RadioButtons';
-import TextControl from 'ui/components/controls/TextControl';
-import Field from 'ui/components/controls/Field';
-import FaceSelectionControl from './FaceSelectionControl';
-import Folder from 'ui/components/Folder';
-
-export default class MDForm extends React.Component {
-
- render() {
- let {metadata, data} = this.props;
- return metadata.map(({name, label, type, ...options}, index) => {
- label = label || uiLabel(name);
- let value = data[name];
-
- if (type === 'array') {
- return
- {value && value.map((itemData, i) =>
-
- )}
-
- } else {
- return
-
- {
- (() => {
- let commonProps = {initValue: value};
- if (type === 'number') {
- return ;
- } else if (type === 'face') {
- return ;
- } else if (type === 'choice') {
- return
- {options.options.map(op => )}
- ;
- } else {
- return ;
- }
- })()
- }
-
- }
- });
-
- }
-
-}
-
-function uiLabel(name) {
- return camelCaseSplit(name).map(w => w.toLowerCase()).join(' ');
-}
diff --git a/web/app/cad/craft/wizard/components/Wizard.jsx b/web/app/cad/craft/wizard/components/Wizard.jsx
index c729fa43..b02b1a2f 100644
--- a/web/app/cad/craft/wizard/components/Wizard.jsx
+++ b/web/app/cad/craft/wizard/components/Wizard.jsx
@@ -9,29 +9,28 @@ import {CURRENT_SELECTION} from '../wizardPlugin';
import ls from './Wizard.less';
import CadError from '../../../../utils/errors';
import {createPreviewer} from '../../../preview/scenePreviewer';
-import {observable} from 'mobx';
-import {entitySelectionToken} from '../../../scene/controls/pickControlPlugin';
-import {EDGE} from '../../../scene/entites';
import {FormContext} from './form/Form';
export default class Wizard extends React.Component {
state = {hasError: false};
-
- constructor({type}) {
+
+ constructor({initialState}) {
super();
this.formContext = {
- data: {},
+ data: initialState || {},
onChange: noop
};
}
-
+
componentDidMount() {
let {services} = this.context;
+
let {previewGeomProvider} = services.operation.get(this.props.type);
let previewer = createPreviewer(previewGeomProvider, services);
let preview = previewer(this.formContext.data);
+
this.formContext.onChange = () => preview.update(this.formContext.data);
this.dispose = () => {
preview.dispose();
diff --git a/web/app/cad/craft/wizard/components/WizardManager.jsx b/web/app/cad/craft/wizard/components/WizardManager.jsx
index 575f9fb2..cb7435cd 100644
--- a/web/app/cad/craft/wizard/components/WizardManager.jsx
+++ b/web/app/cad/craft/wizard/components/WizardManager.jsx
@@ -1,6 +1,6 @@
import React, {Fragment} from 'react';
import {TOKENS as WIZARD_TOKENS} from '../wizardPlugin';
-import connect from 'ui/connect';
+import connect from 'ui/connectLegacy';
import Wizard from './Wizard';
import HistoryWizard from './HistoryWizard';
diff --git a/web/app/cad/craft/wizard/components/form/MultiEntity.jsx b/web/app/cad/craft/wizard/components/form/MultiEntity.jsx
index f5391ded..f24ccf2a 100644
--- a/web/app/cad/craft/wizard/components/form/MultiEntity.jsx
+++ b/web/app/cad/craft/wizard/components/form/MultiEntity.jsx
@@ -4,10 +4,13 @@ import {entitySelectionToken} from '../../../../scene/controls/pickControlPlugin
import {attachToForm} from './Form';
import Stack from 'ui/components/Stack';
import {FormContext} from '../form/Form';
+import mapContext from 'ui/mapContext';
-const MultiEntityImpl = attachToForm(class MultiEntityImpl extends React.Component {
+@attachToForm
+@mapContext(({streams}) => ({streams}))
+class MultiEntityImpl extends React.Component {
- constructor({entity, itemName, initValue}, {bus}) {
+ constructor({entity, itemName, initValue}, ) {
super();
this.state = {
value: initValue
@@ -24,11 +27,12 @@ const MultiEntityImpl = attachToForm(class MultiEntityImpl extends React.Compone
};
componentDidMount() {
- this.context.bus.subscribe(entitySelectionToken(this.props.entity), this.selectionChanged);
+ let {streams, entity} = this.props;
+ this.detacher = streams.selection[entity].attach(this.selectionChanged);
}
componentWillUnmount() {
- this.context.bus.unsubscribe(entitySelectionToken(this.props.entity), this.selectionChanged);
+ this.detacher();
}
render() {
@@ -51,19 +55,15 @@ const MultiEntityImpl = attachToForm(class MultiEntityImpl extends React.Compone
}
;
}
+}
- static contextTypes = {
- bus: PropTypes.object
- };
-});
-
-export default function MultiEntity(props, {bus}) {
- let defaultValue = bus.state[entitySelectionToken(props.entity)].map(id => ({
+export default function MultiEntity(props, {streams}) {
+ let defaultValue = streams.selection[props.entity].value.map(id => ({
[props.itemName]: id
}));
return
}
MultiEntity.contextTypes = {
- bus: PropTypes.object
+ streams: PropTypes.object
};
\ No newline at end of file
diff --git a/web/app/cad/craft/wizard/components/form/SingleEntity.jsx b/web/app/cad/craft/wizard/components/form/SingleEntity.jsx
index c258165d..a8887b48 100644
--- a/web/app/cad/craft/wizard/components/form/SingleEntity.jsx
+++ b/web/app/cad/craft/wizard/components/form/SingleEntity.jsx
@@ -2,8 +2,11 @@ import React from 'react';
import PropTypes from 'prop-types';
import {entitySelectionToken} from '../../../../scene/controls/pickControlPlugin';
import {attachToForm} from './Form';
+import mapContext from 'ui/mapContext';
-const SingleEntityImpl = attachToForm(class SingleEntityImpl extends React.Component {
+@attachToForm
+@mapContext(({streams}) => ({streams}))
+class SingleEntityImpl extends React.Component {
constructor({initValue}) {
super();
@@ -19,11 +22,12 @@ const SingleEntityImpl = attachToForm(class SingleEntityImpl extends React.Compo
};
componentDidMount() {
- this.context.bus.subscribe(entitySelectionToken(this.props.entity), this.selectionChanged);
+ let {streams, entity} = this.props;
+ this.detacher = streams.selection[entity].attach(this.selectionChanged);
}
componentWillUnmount() {
- this.context.bus.unsubscribe(entitySelectionToken(this.props.entity), this.selectionChanged);
+ this.detacher();
}
render() {
@@ -31,16 +35,12 @@ const SingleEntityImpl = attachToForm(class SingleEntityImpl extends React.Compo
{this.props.name}: {this.state.selectedItem}
;
}
+}
- static contextTypes = {
- bus: PropTypes.object
- };
-});
-
-export default function SingleEntity(props, {bus}) {
- return
+export default function SingleEntity(props, {streams}) {
+ return
}
SingleEntity.contextTypes = {
- bus: PropTypes.object
+ streams: PropTypes.object
};
\ No newline at end of file
diff --git a/web/app/cad/debugPlugin.js b/web/app/cad/debugPlugin.js
index 38848c6e..f5039a02 100644
--- a/web/app/cad/debugPlugin.js
+++ b/web/app/cad/debugPlugin.js
@@ -15,11 +15,13 @@ import curveTess from '../brep/geom/impl/curve/curve-tess';
import tessellateSurface from '../brep/geom/surfaces/surfaceTess';
-export function activate({bus, services}) {
+export function activate({bus, services, streams}) {
addGlobalDebugActions(services);
services.action.registerActions(DebugActions);
services.menu.registerMenus([DebugMenuConfig]);
- bus.updateState(UI_TOKENS.CONTROL_BAR_LEFT, actions => [...actions, 'menu.debug']);
+
+ streams.ui.controlBars.left.update(actions => [...actions, 'menu.debug']);
+
bus.enableState(BREP_DEBUG_WINDOW_VISIBLE, false);
contributeComponent();
}
@@ -287,7 +289,7 @@ const DebugActions = [
label: 'print face',
info: 'print a face out as JSON',
},
- listens: ['selection_face'],
+ listens: streams => streams.selection.face,
update: checkForSelectedFaces(1),
invoke: ({services: {selection}}) => {
let s = selection.face.single;
@@ -305,7 +307,7 @@ const DebugActions = [
label: 'print face id',
info: 'print a face id',
},
- listens: ['selection_face'],
+ listens: streams => streams.selection.face,
update: checkForSelectedFaces(1),
invoke: ({services: {selection}}) => {
console.log(selection.face.single.id);
@@ -319,7 +321,7 @@ const DebugActions = [
label: 'print face sketch',
info: 'print face sketch stripping constraints and boundary',
},
- listens: ['selection_face'],
+ listens: streams => streams.selection.face,
update: checkForSelectedFaces(1),
invoke: ({services: {selection, project}}) => {
const faceId = selection.face.single.id;
diff --git a/web/app/cad/dom/actionInfo/ActionInfo.jsx b/web/app/cad/dom/actionInfo/ActionInfo.jsx
index 09a16281..18184314 100644
--- a/web/app/cad/dom/actionInfo/ActionInfo.jsx
+++ b/web/app/cad/dom/actionInfo/ActionInfo.jsx
@@ -3,14 +3,13 @@ import ls from './ActionInfo.less';
import AuxWidget from 'ui/components/AuxWidget';
import connect from 'ui/connect';
-import {TOKENS as ACTION_TOKENS} from '../../actions/actionSystemPlugin';
-import {TOKENS as KeyboardTokens} from '../../keyboard/keyboardPlugin';
+import {combine} from 'lstream';
function ActionInfo({actionId, x, y, info, hint, hotKey}) {
let visible = !!(actionId && (info || hint || hotKey));
-
- return
+
+ return
{visible &&
{hint && {hint}
}
{info && {info}
}
@@ -19,7 +18,11 @@ function ActionInfo({actionId, x, y, info, hint, hotKey}) {
;
}
-export default connect(ActionInfo, [ACTION_TOKENS.HINT, KeyboardTokens.KEYMAP], {
- mapProps: ([ hintInfo, keymap ]) => (Object.assign({hotKey: hintInfo && keymap[hintInfo.actionId]}, hintInfo))
-});
+export default connect(streams =>
+ combine(
+ streams.action.hint,
+ streams.ui.keymap)
+ .map(([hintInfo, keymap]) => Object.assign({hotKey: hintInfo && keymap[hintInfo.actionId]}, hintInfo)
+))
+(ActionInfo);
diff --git a/web/app/cad/dom/components/AppTabs.jsx b/web/app/cad/dom/components/AppTabs.jsx
index c463c7a1..10dd0145 100644
--- a/web/app/cad/dom/components/AppTabs.jsx
+++ b/web/app/cad/dom/components/AppTabs.jsx
@@ -5,7 +5,7 @@ import View3d from './View3d';
import ls from './AppTabs.less';
import TabSwitcher, {Tab} from 'ui/components/TabSwticher';
-import connect from 'ui/connect';
+import connect from 'ui/connectLegacy';
import {TOKENS as APP_TABS_TOKENS} from "../appTabsPlugin";
import Card from "ui/components/Card";
diff --git a/web/app/cad/dom/components/OperationHistory.jsx b/web/app/cad/dom/components/OperationHistory.jsx
index b8ff2c98..3d084850 100644
--- a/web/app/cad/dom/components/OperationHistory.jsx
+++ b/web/app/cad/dom/components/OperationHistory.jsx
@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import Stack from 'ui/components/Stack';
-import connect from 'ui/connect';
+import connect from 'ui/connectLegacy';
import Fa from 'ui/components/Fa';
import ImgIcon from 'ui/components/ImgIcon';
import ls from './OperationHistory.less';
diff --git a/web/app/cad/dom/components/PlugableControlBar.jsx b/web/app/cad/dom/components/PlugableControlBar.jsx
index 6424a6e2..c61b0e94 100644
--- a/web/app/cad/dom/components/PlugableControlBar.jsx
+++ b/web/app/cad/dom/components/PlugableControlBar.jsx
@@ -1,14 +1,12 @@
-import React, {Fragment} from 'react';
+import React from 'react';
import ControlBar, {ControlBarButton} from './ControlBar';
import connect from 'ui/connect';
import Fa from 'ui/components/Fa';
-import {TOKENS as UI_TOKENS} from '../uiEntryPointsPlugin';
-import {TOKENS as ACTION_TOKENS} from '../../actions/actionSystemPlugin';
-import {toIdAndOverrides} from "../../actions/actionRef";
-import {mapActionBehavior} from "../../actions/actionButtonBehavior";
-import {DEFAULT_MAPPER} from "ui/connect";
-import {isMenuAction} from "../menu/menuPlugin";
-
+import {toIdAndOverrides} from '../../actions/actionRef';
+import {isMenuAction} from '../menu/menuPlugin';
+import {combine, merger} from 'lstream';
+import mapContext from 'ui/mapContext';
+import decoratorChain from '../../../../../modules/ui/decoratorChain';
export default function PlugableControlBar() {
return } right={}/>;
@@ -17,7 +15,7 @@ export default function PlugableControlBar() {
function ButtonGroup({actions}) {
return actions.map(actionRef => {
let [id, overrides] = toIdAndOverrides(actionRef);
- return ;
+ return ;
});
}
@@ -39,22 +37,21 @@ class ActionButton extends React.Component {
}
}
-const BUTTON_CONNECTOR = {
- mapProps: ([actions]) => ({actions})
-};
+const LeftGroup = connect(streams => streams.ui.controlBars.left.map(actions => ({actions})))(ButtonGroup);
+const RightGroup = connect(streams => streams.ui.controlBars.right.map(actions => ({actions})))(ButtonGroup);
-const LeftGroup = connect(ButtonGroup, UI_TOKENS.CONTROL_BAR_LEFT, BUTTON_CONNECTOR);
-const RightGroup = connect(ButtonGroup, UI_TOKENS.CONTROL_BAR_RIGHT, BUTTON_CONNECTOR);
+const ConnectedActionButton = decoratorChain(
-
-const ConnectedActionButton = connect(ActionButton,
- props => [ACTION_TOKENS.actionAppearance(props.actionId),
- ACTION_TOKENS.actionState(props.actionId)],
- {
- mapProps: (state, props) => Object.assign(DEFAULT_MAPPER(state), props),
- mapActions: mapActionBehavior(props => props.actionId),
- }
-);
+ connect(
+ (streams, props) => combine(
+ streams.action.appearance[props.actionId],
+ streams.action.state[props.actionId]).map(merger)),
+
+ mapContext(({services}, props) => ({
+ onClick: data => services.action.run(props.actionId, data)
+ }))
+)
+(ActionButton);
function getMenuData(el) {
//TODO: make more generic
diff --git a/web/app/cad/dom/components/PlugableToolbar.jsx b/web/app/cad/dom/components/PlugableToolbar.jsx
index ce9e4bf8..77bf3d41 100644
--- a/web/app/cad/dom/components/PlugableToolbar.jsx
+++ b/web/app/cad/dom/components/PlugableToolbar.jsx
@@ -1,15 +1,14 @@
-import React, {Fragment} from 'react';
+import React from 'react';
import connect from 'ui/connect';
import Fa from 'ui/components/Fa';
-import {TOKENS as UI_TOKENS} from '../uiEntryPointsPlugin';
-import {TOKENS as ACTION_TOKENS} from '../../actions/actionSystemPlugin';
import Toolbar, {ToolbarButton} from 'ui/components/Toolbar';
import ImgIcon from 'ui/components/ImgIcon';
import {toIdAndOverrides} from '../../actions/actionRef';
import {mapActionBehavior} from '../../actions/actionButtonBehavior';
-import {DEFAULT_MAPPER} from 'ui/connect';
import capitalize from 'gems/capitalize';
-
+import decoratorChain from 'ui/decoratorChain';
+import {combine, merger} from 'lstream';
+import mapContext from '../../../../../modules/ui/mapContext';
function ConfigurableToolbar({actions, small, ...props}) {
@@ -34,19 +33,20 @@ function ActionButton({label, icon96, cssIcons, small, enabled, visible, actionI
}
-const ConnectedActionButton = connect(ActionButton,
- ({actionId}) => [ACTION_TOKENS.actionAppearance(actionId), ACTION_TOKENS.actionState(actionId)], {
- mapProps: (state, props) => Object.assign(DEFAULT_MAPPER(state), props),
- mapActions: mapActionBehavior(props => props.actionId),
- });
+const ConnectedActionButton = decoratorChain(
+ connect((streams, {actionId}) => combine(streams.action.appearance[actionId], streams.action.state[actionId]).map(merger)),
+ mapContext(mapActionBehavior(props => props.actionId))
+)
+(ActionButton);
-export function createPlugableToolbar(configToken, small) {
- return connect(ConfigurableToolbar, configToken, {
- staticProps: {small},
- mapProps: ([actions]) => ({actions})
- });
+export function createPlugableToolbar(streamSelector, small) {
+ return decoratorChain(
+ connect(streams => streamSelector(streams).map(actions => ({actions}))),
+ mapContext(mapActionBehavior(props => props.actionId))
+ )
+ (props => );
}
-export const PlugableToolbarLeft = createPlugableToolbar(UI_TOKENS.TOOLBAR_BAR_LEFT);
-export const PlugableToolbarLeftSecondary = createPlugableToolbar(UI_TOKENS.TOOLBAR_BAR_LEFT_SECONDARY);
-export const PlugableToolbarRight = createPlugableToolbar(UI_TOKENS.TOOLBAR_BAR_RIGHT, true);
\ No newline at end of file
+export const PlugableToolbarLeft = createPlugableToolbar(streams => streams.ui.toolbars.left);
+export const PlugableToolbarLeftSecondary = createPlugableToolbar(streams => streams.ui.toolbars.leftSecondary);
+export const PlugableToolbarRight = createPlugableToolbar(streams => streams.ui.toolbars.right, true);
\ No newline at end of file
diff --git a/web/app/cad/dom/components/UISystem.jsx b/web/app/cad/dom/components/UISystem.jsx
index bcf900b8..1476f1e7 100644
--- a/web/app/cad/dom/components/UISystem.jsx
+++ b/web/app/cad/dom/components/UISystem.jsx
@@ -1,7 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import MenuHolder from '../menu/MenuHolder';
-import {TOKENS as MENU_TOKENS} from '../menu/menuPlugin';
import WindowSystem from 'ui/WindowSystem';
import ActionInfo from '../actionInfo/ActionInfo';
@@ -25,10 +24,8 @@ export default class UISystem extends React.Component {
}
closeAllUpPopups = () => {
- let openedMenus = this.context.bus.state[MENU_TOKENS.OPENED];
- if (openedMenus && openedMenus.length !== 0) {
- this.context.bus.dispatch(MENU_TOKENS.CLOSE_ALL);
- }
+ this.context.services.menu.closeAll();
+ this.context.services.action.showHintFor(null);
};
getChildContext() {
@@ -38,7 +35,7 @@ export default class UISystem extends React.Component {
}
static contextTypes = {
- bus: PropTypes.object
+ services: PropTypes.object
};
static childContextTypes = {
diff --git a/web/app/cad/dom/components/WebApplication.jsx b/web/app/cad/dom/components/WebApplication.jsx
index 488f2913..852e90ad 100644
--- a/web/app/cad/dom/components/WebApplication.jsx
+++ b/web/app/cad/dom/components/WebApplication.jsx
@@ -24,6 +24,7 @@ export default class WebApplication extends React.Component {
static childContextTypes = {
bus: PropTypes.object,
- services: PropTypes.object
+ services: PropTypes.object,
+ streams: PropTypes.object
};
}
diff --git a/web/app/cad/dom/menu/MenuHolder.jsx b/web/app/cad/dom/menu/MenuHolder.jsx
index 8455b792..8eaab2fa 100644
--- a/web/app/cad/dom/menu/MenuHolder.jsx
+++ b/web/app/cad/dom/menu/MenuHolder.jsx
@@ -1,12 +1,12 @@
import React from 'react';
-import connect from 'ui/connect';
-import {TOKENS as MENU_TOKENS} from './menuPlugin';
-import {TOKENS as ACTION_TOKENS} from '../../actions/actionSystemPlugin';
import Menu, {MenuItem, MenuSeparator} from 'ui/components/Menu';
import Filler from 'ui/components/Filler';
import Fa from 'ui/components/Fa';
-import {TOKENS as KeyboardTokens} from '../../keyboard/keyboardPlugin';
import {mapActionBehavior} from '../../actions/actionButtonBehavior';
+import connect from 'ui/connect';
+import {combine, merger} from 'lstream';
+import mapContext from 'ui/mapContext';
+import decoratorChain from 'ui/decoratorChain';
function MenuHolder({menus}) {
return menus.map(({id, actions}) => );
@@ -48,26 +48,26 @@ function ActionMenuItem({label, cssIcons, icon32, icon96, enabled, hotKey, visib
return ;
}
-const ConnectedActionMenu = connect(ActionMenu,
- ({menuId}) => [MENU_TOKENS.menuState(menuId), KeyboardTokens.KEYMAP],
- {
- mapProps: ([menuState, keymap], {actions}) => Object.assign({keymap, actions}, menuState)
- });
+const ConnectedActionMenu = connect((streams, props) =>
+ combine(
+ streams.ui.menu.states[props.menuId],
+ streams.ui.keymap)
+ .map(([s, keymap]) => ({...s, keymap})))
+(ActionMenu);
-let ConnectedMenuItem = connect(ActionMenuItem,
- ({actionId}) => [ACTION_TOKENS.actionState(actionId), ACTION_TOKENS.actionAppearance(actionId)],
- {
- mapProps: ([{enabled, visible}, {label, cssIcons, icon32, icon96}]) => ({
- enabled, visible, label, cssIcons, icon32, icon96
- }),
- mapActions: mapActionBehavior(props => props.actionId)
- }
-);
+let ConnectedMenuItem = decoratorChain(
-export default connect(MenuHolder, MENU_TOKENS.MENUS, {
- mapProps: ([menus]) => ({menus})
-});
+ connect((streams, {actionId}) =>
+ combine(
+ streams.action.state[actionId],
+ streams.action.appearance[actionId]).map(merger)),
+
+ mapContext(mapActionBehavior(props => props.actionId))
+)
+(ActionMenuItem);
+
+export default connect(streams => streams.ui.menu.all.map(menus => ({menus})))(MenuHolder);
diff --git a/web/app/cad/dom/menu/menuPlugin.js b/web/app/cad/dom/menu/menuPlugin.js
index 1d8e58c9..f2e71f30 100644
--- a/web/app/cad/dom/menu/menuPlugin.js
+++ b/web/app/cad/dom/menu/menuPlugin.js
@@ -1,53 +1,54 @@
-import {createToken} from 'bus';
+import {state} from '../../../../../modules/lstream';
-export function activate({bus, services}) {
+export function activate({bus, services, streams}) {
- bus.enableState(TOKENS.MENUS, []);
- bus.enableState(TOKENS.OPENED, []);
+ streams.ui.menu = {
+ all: state([]),
+ opened: state([]),
+ states: {}
+ };
function registerMenus(menus) {
let menusToAdd = [];
let showMenuActions = [];
menus.forEach(({id, actions, ...appearance}) => {
- let stateToken = TOKENS.menuState(id);
- bus.enableState(stateToken, {
+ let menuState = state({
visible: false,
orientationUp: false,
x: undefined,
y: undefined
});
+ streams.ui.menu.states[id] = menuState;
if (!appearance.label) {
appearance.label = id;
}
showMenuActions.push({
id: 'menu.' + id,
appearance,
- invoke: (ctx, hints) => bus.updateStates([stateToken, TOKENS.OPENED],
- ([state, opened]) => [Object.assign(state, {visible: true}, hints), [id, ...opened]]
- )
+ invoke: (ctx, hints) => {
+ menuState.mutate(v => {
+ Object.assign(v, hints);
+ v.visible = true;
+ });
+ streams.ui.menu.opened.mutate(v => v.push(id));
+ }
});
-
menusToAdd.push({id, actions});
});
services.action.registerActions(showMenuActions);
- bus.updateState(TOKENS.MENUS, menus => [...menus, ...menusToAdd]);
+ streams.ui.menu.all.update(menus => [...menus, ...menusToAdd]);
}
- bus.subscribe(TOKENS.CLOSE_ALL, () => {
- bus.state[TOKENS.OPENED].forEach(openedMenu => bus.setState(TOKENS.menuState(openedMenu), {visible: false}));
- bus.updateState(TOKENS.OPENED, () => []);
- });
+ function closeAll() {
+ if (streams.ui.menu.opened.value.length > 0) {
+ streams.ui.menu.opened.value.forEach(id => streams.ui.menu.states[id].mutate(s => s.visible = false));
+ streams.ui.menu.opened.mutate(opened => opened.length = 0);
+ }
+ }
- services.menu = { registerMenus }
+ services.menu = { registerMenus, closeAll }
}
-export const TOKENS = {
- menuState: id => createToken('menu', 'state', id),
- MENUS: createToken('menus'),
- CLOSE_ALL: createToken('menus', 'closeAll'),
- OPENED: createToken('menus', 'opened')
-};
-
export function isMenuAction(actionId) {
return actionId.startsWith('menu.');
}
diff --git a/web/app/cad/dom/uiEntryPointsPlugin.js b/web/app/cad/dom/uiEntryPointsPlugin.js
index c4e9cb06..a5959b5d 100644
--- a/web/app/cad/dom/uiEntryPointsPlugin.js
+++ b/web/app/cad/dom/uiEntryPointsPlugin.js
@@ -1,25 +1,19 @@
-import {createToken} from 'bus';
+import {state} from 'lstream';
+export function activate({bus, streams}) {
-export function activate({bus}) {
+ streams.ui = {
+ controlBars: {
+ left: state([]),
+ right: state([])
+ },
+ toolbars: {
+ left: state([]),
+ leftSecondary: state([]),
+ right: state([])
+ }
+ };
- bus.enableState(TOKENS.CONTROL_BAR_LEFT, []);
- bus.enableState(TOKENS.CONTROL_BAR_RIGHT, []);
-
- bus.enableState(TOKENS.TOOLBAR_BAR_LEFT, []);
- bus.enableState(TOKENS.TOOLBAR_BAR_LEFT_SECONDARY, []);
- bus.enableState(TOKENS.TOOLBAR_BAR_RIGHT, []);
-
}
-const NS = 'ui.config';
-
-export const TOKENS = {
- CONTROL_BAR_LEFT: createToken(NS, 'controlBar.left'),
- CONTROL_BAR_RIGHT: createToken(NS, 'controlBar.right'),
-
- TOOLBAR_BAR_LEFT: createToken(NS, 'toolbar.left'),
- TOOLBAR_BAR_LEFT_SECONDARY: createToken(NS, 'toolbar.left.secondary'),
- TOOLBAR_BAR_RIGHT: createToken(NS, 'toolbar.right'),
-};
diff --git a/web/app/cad/init/startApplication.js b/web/app/cad/init/startApplication.js
index 5af615b3..afb4f24a 100644
--- a/web/app/cad/init/startApplication.js
+++ b/web/app/cad/init/startApplication.js
@@ -1,4 +1,3 @@
-import Bus from 'bus';
import * as LifecyclePlugin from './lifecyclePlugin';
import * as AppTabsPlugin from '../dom/appTabsPlugin';
import * as DomPlugin from '../dom/domPlugin';
@@ -19,9 +18,10 @@ import * as ProjectPlugin from '../projectPlugin';
import * as SketcherPlugin from '../sketch/sketcherPlugin';
import * as tpiPlugin from '../tpi/tpiPlugin';
-
import * as PartModellerPlugin from '../part/partModellerPlugin';
+import context from 'context';
+
import startReact from "../dom/startReact";
import {APP_READY_TOKEN} from './lifecyclePlugin';
@@ -35,8 +35,8 @@ export default function startApplication(callback) {
StoragePlugin,
AppTabsPlugin,
ActionSystemPlugin,
- MenuPlugin,
UiEntryPointsPlugin,
+ MenuPlugin,
KeyboardPlugin,
WizardPlugin,
CraftEnginesPlugin,
@@ -55,11 +55,6 @@ export default function startApplication(callback) {
...applicationPlugins,
];
- let context = {
- bus: new Bus(),
- services: {}
- };
-
activatePlugins(preUIPlugins, context);
startReact(context, () => {
diff --git a/web/app/cad/keyboard/keyboardPlugin.js b/web/app/cad/keyboard/keyboardPlugin.js
index 582c0239..3c1a8e5e 100644
--- a/web/app/cad/keyboard/keyboardPlugin.js
+++ b/web/app/cad/keyboard/keyboardPlugin.js
@@ -1,21 +1,17 @@
import Mousetrap from 'mousetrap';
import DefaultKeymap from './keymaps/default';
+import {isMenuAction} from '../dom/menu/menuPlugin';
+import {state} from 'lstream';
-import {createToken} from "bus";
-import {TOKENS as ACTION_TOKENS} from "../actions/actionSystemPlugin";
-import {isMenuAction, TOKENS as MENU_TOKENS} from "../dom/menu/menuPlugin";
-
-export function activate({bus, services}) {
- bus.enableState(TOKENS.KEYMAP, DefaultKeymap);
-
+export function activate({services, streams}) {
+ streams.ui.keymap = state(DefaultKeymap);
let keymap = DefaultKeymap;
//to attach to a dom element: Mousetrap(domElement).bind(...
for (let action of Object.keys(keymap)) {
const dataProvider = getDataProvider(action, services);
- let actionToken = ACTION_TOKENS.actionRun(action);
- Mousetrap.bind(keymap[action], () => bus.dispatch(actionToken, dataProvider ? dataProvider() : undefined));
+ Mousetrap.bind(keymap[action], () => services.action.run(actionToken, dataProvider ? dataProvider() : undefined));
}
- Mousetrap.bind('esc', () => bus.dispatch(MENU_TOKENS.CLOSE_ALL));
+ Mousetrap.bind('esc', services.menu.closeAll)
}
function getDataProvider(action, services) {
@@ -33,6 +29,3 @@ function getDataProvider(action, services) {
}
-export const TOKENS = {
- KEYMAP: createToken('keymap')
-};
\ No newline at end of file
diff --git a/web/app/cad/part/uiConfigPlugin.js b/web/app/cad/part/uiConfigPlugin.js
index ae933aec..cc9fd2c7 100644
--- a/web/app/cad/part/uiConfigPlugin.js
+++ b/web/app/cad/part/uiConfigPlugin.js
@@ -1,25 +1,23 @@
import CoreActions from '../actions/coreActions';
import OperationActions from '../actions/operationActions';
import HistoryActions from '../actions/historyActions';
-import {TOKENS as UI_TOKENS} from '../dom/uiEntryPointsPlugin';
-import menuConfig from "./menuConfig";
+import menuConfig from './menuConfig';
-export function activate({bus, services}) {
+export function activate({bus, services, streams}) {
+ streams.ui.controlBars.left.value = ['menu.file', 'menu.craft', 'menu.boolean', 'menu.primitives', 'Donate', 'GitHub'];
+ streams.ui.controlBars.right.value = [
+ ['Info', {label: null}],
+ ['RefreshSketches', {label: null}],
+ ['ShowSketches', {label: 'sketches'}], ['DeselectAll', {label: null}], ['ToggleCameraMode', {label: null}]
+ ];
+ streams.ui.toolbars.left.value = ['PLANE', 'EditFace', 'EXTRUDE', 'CUT', 'REVOLVE'];
+ streams.ui.toolbars.leftSecondary.value = ['INTERSECTION', 'DIFFERENCE', 'UNION'];
+ streams.ui.toolbars.right.value = ['Save', 'StlExport'];
+
services.action.registerActions(CoreActions);
services.action.registerActions(OperationActions);
services.action.registerActions(HistoryActions);
services.menu.registerMenus(menuConfig);
-
- bus.dispatch(UI_TOKENS.CONTROL_BAR_LEFT, ['menu.file', 'menu.craft', 'menu.boolean', 'menu.primitives', 'Donate', 'GitHub']);
- bus.dispatch(UI_TOKENS.CONTROL_BAR_RIGHT, [
- ['Info', {label: null}],
- ['RefreshSketches', {label: null}],
- ['ShowSketches', {label: 'sketches'}], ['DeselectAll', {label: null}], ['ToggleCameraMode', {label: null}]
- ]);
-
- bus.dispatch(UI_TOKENS.TOOLBAR_BAR_LEFT, ['PLANE', 'EditFace', 'EXTRUDE', 'CUT', 'REVOLVE']);
- bus.dispatch(UI_TOKENS.TOOLBAR_BAR_LEFT_SECONDARY, ['INTERSECTION', 'DIFFERENCE', 'UNION']);
- bus.dispatch(UI_TOKENS.TOOLBAR_BAR_RIGHT, ['Save', 'StlExport']);
}
\ No newline at end of file
diff --git a/web/app/cad/scene/controls/pickControlPlugin.js b/web/app/cad/scene/controls/pickControlPlugin.js
index 20c17dd5..a27afd28 100644
--- a/web/app/cad/scene/controls/pickControlPlugin.js
+++ b/web/app/cad/scene/controls/pickControlPlugin.js
@@ -1,7 +1,7 @@
import * as mask from 'gems/mask'
import {getAttribute, setAttribute} from '../../../../../modules/scene/objectData';
-import {TOKENS as UI_TOKENS} from '../../dom/uiEntryPointsPlugin';
import {FACE, EDGE, SKETCH_OBJECT} from '../entites';
+import {state} from '../../../../../modules/lstream';
export const PICK_KIND = {
FACE: mask.type(1),
@@ -12,8 +12,9 @@ export const PICK_KIND = {
const SELECTABLE_ENTITIES = [FACE, EDGE, SKETCH_OBJECT];
export function activate(context) {
+ const {services, streams} = context;
initStateAndServices(context);
- let domElement = context.services.viewer.sceneSetup.domElement();
+ let domElement = services.viewer.sceneSetup.domElement();
domElement.addEventListener('mousedown', mousedown, false);
domElement.addEventListener('mouseup', mouseup, false);
@@ -41,26 +42,25 @@ export function activate(context) {
}
}
- function selected(key, object) {
- let selection = context.bus.state[key];
- return selection !== undefined && selection.indexOf(object) !== -1;
+ function selected(selection, object) {
+ return selection.value.indexOf(object) !== -1;
}
function handlePick(event) {
raycastObjects(event, PICK_KIND.FACE | PICK_KIND.SKETCH | PICK_KIND.EDGE, (object, kind) => {
if (kind === PICK_KIND.FACE) {
- if (!selected('selection_face', object.id)) {
- context.services.cadScene.showBasis(object.basis(), object.depth());
- context.bus.dispatch('selection_face', [object.id]);
+ if (!selected(streams.selection.face, object.id)) {
+ services.cadScene.showBasis(object.basis(), object.depth());
+ streams.selection.face.next([object.id]);
return false;
}
} else if (kind === PICK_KIND.SKETCH) {
- if (!selected('selection_sketchObject', object.id)) {
- context.bus.dispatch('selection_sketchObject', [object.id]);
+ if (!selected(streams.selection.sketchObject, object.id)) {
+ streams.selection.sketchObject.next([object.id]);
return false;
}
} else if (kind === PICK_KIND.EDGE) {
- if (dispatchSelection('selection_edge', object.id, event)) {
+ if (dispatchSelection(streams.selection.edge, object.id, event)) {
return false;
}
}
@@ -68,25 +68,25 @@ export function activate(context) {
});
}
- function dispatchSelection(selectionToken, selectee, event) {
- if (selected(selectionToken, selectee)) {
+ function dispatchSelection(selection, selectee, event) {
+ if (selected(selection, selectee)) {
return false;
}
let multiMode = event.shiftKey;
- context.bus.updateState(selectionToken, selection => multiMode ? [...selection, selectee] : [selectee]);
+ selection.update(value => multiMode ? [...value, selectee] : [selectee]);
return true;
}
function handleSolidPick(e) {
raycastObjects(e, PICK_KIND.FACE, (sketchFace) => {
- context.bus.dispatch('selection_solid', sketchFace.solid);
- context.services.viewer.render();
+ streams.selection.solid.next([sketchFace.solid]);
+ services.viewer.render();
return false;
});
}
function raycastObjects(event, kind, visitor) {
- let pickResults = context.services.viewer.raycast(event, context.services.cadScene.workGroup);
+ let pickResults = services.viewer.raycast(event, services.cadScene.workGroup);
const pickers = [
(pickResult) => {
if (mask.is(kind, PICK_KIND.SKETCH) && pickResult.object instanceof THREE.Line) {
@@ -127,39 +127,30 @@ export function activate(context) {
}
}
-function initStateAndServices({bus, services}) {
+function initStateAndServices({streams, services}) {
services.selection = {
};
+ streams.selection = {
+ };
+
SELECTABLE_ENTITIES.forEach(entity => {
let entitySelectApi = {
objects: [],
single: undefined
};
services.selection[entity] = entitySelectApi;
- let selType = entitySelectionToken(entity);
- bus.enableState(selType, []);
- bus.subscribe(selType, selection => {
+ let selectionState = state([]);
+ streams.selection[entity] = selectionState;
+ selectionState.attach(selection => {
entitySelectApi.objects = selection.map(id => services.cadRegistry.findEntity(entity, id));
entitySelectApi.single = entitySelectApi.objects[0];
});
- entitySelectApi.select = selection => bus.dispatch(selType, selection);
+ entitySelectApi.select = selection => selectionState.value = selection;
});
}
-const selectionTokenMap = {};
-SELECTABLE_ENTITIES.forEach(e => selectionTokenMap[e] = `selection_${e}`);
-
-export function entitySelectionToken(entity) {
- let token = selectionTokenMap[entity];
- if (!token) {
- throw "entity isn't selectable " + entity;
- }
- return token;
-}
-
-
diff --git a/web/app/cad/scene/selectionMarker/abstractSelectionMarker.js b/web/app/cad/scene/selectionMarker/abstractSelectionMarker.js
index 2e03c116..d5cf769b 100644
--- a/web/app/cad/scene/selectionMarker/abstractSelectionMarker.js
+++ b/web/app/cad/scene/selectionMarker/abstractSelectionMarker.js
@@ -7,7 +7,7 @@ export class AbstractSelectionMarker {
this.context = context;
this.entity = entity;
this.selection = [];
- this.context.bus.subscribe(entitySelectionToken(entity), this.update);
+ this.context.streams.selection[entity].attach(this.update);
}
update = () => {
@@ -19,7 +19,7 @@ export class AbstractSelectionMarker {
}
this.selection = [];
}
- this.context.bus.dispatch('scene:update');
+ this.context.services.viewer.render();
return;
}
@@ -33,7 +33,7 @@ export class AbstractSelectionMarker {
this.selection.splice(this.selection.indexOf(obj), 1);
this.unMark(obj);
}
- this.context.bus.dispatch('scene:update');
+ this.context.services.viewer.render();
};
mark(obj) {