mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-22 08:26:26 +01:00
event stream api for organizing UI
This commit is contained in:
parent
7cbd001efc
commit
e226d416ee
44 changed files with 532 additions and 463 deletions
3
.babelrc
3
.babelrc
|
|
@ -1,3 +1,4 @@
|
|||
{
|
||||
"presets": ["es2015", "stage-2", "react", "flow"]
|
||||
"presets": ["es2015", "stage-2", "react", "flow"],
|
||||
"plugins": ["transform-decorators-legacy"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
9
modules/context/index.js
Normal file
9
modules/context/index.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import Bus from '../bus';
|
||||
import {observable} from 'mobx';
|
||||
|
||||
export default {
|
||||
services: {},
|
||||
streams: {},
|
||||
//@deprecated
|
||||
bus: new Bus()
|
||||
};
|
||||
50
modules/lstream/base.js
Normal file
50
modules/lstream/base.js
Normal file
|
|
@ -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);
|
||||
}
|
||||
38
modules/lstream/combine.js
Normal file
38
modules/lstream/combine.js
Normal file
|
|
@ -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 = {};
|
||||
41
modules/lstream/emitter.js
Normal file
41
modules/lstream/emitter.js
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
22
modules/lstream/index.js
Normal file
22
modules/lstream/index.js
Normal file
|
|
@ -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), {});
|
||||
38
modules/lstream/state.js
Normal file
38
modules/lstream/state.js
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
29
modules/ui/connect.js
Normal file
29
modules/ui/connect.js
Normal file
|
|
@ -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 <Component {...this.streamProps}
|
||||
{...this.props} />;
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
9
modules/ui/decoratorChain.js
Normal file
9
modules/ui/decoratorChain.js
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
11
modules/ui/mapContext.js
Normal file
11
modules/ui/mapContext.js
Normal file
|
|
@ -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 <Component {...actions} {...props} />
|
||||
}
|
||||
}
|
||||
}
|
||||
16
package.json
16
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",
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}};
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
};
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <Folder title={label}>
|
||||
{edges.map((subParams, i) =>
|
||||
<MDForm metadata={itemMetadata} params={subParams} onUpdate={onUpdate} key={i} />
|
||||
)}
|
||||
</Folder>;
|
||||
}
|
||||
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
import TextControl from "ui/components/controls/TextControl";
|
||||
|
||||
export default function FaceSelectionControl(props) {
|
||||
return <TextControl {...props} />
|
||||
}
|
||||
|
||||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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 <Folder title={label}>
|
||||
{value && value.map((itemData, i) =>
|
||||
<MDForm metadata={options.metadata} data={itemData} key={i} />
|
||||
)}
|
||||
</Folder>
|
||||
} else {
|
||||
return <Field key={index}>
|
||||
<Label>{label}</Label>
|
||||
{
|
||||
(() => {
|
||||
let commonProps = {initValue: value};
|
||||
if (type === 'number') {
|
||||
return <NumberControl {...commonProps} {...options} />;
|
||||
} else if (type === 'face') {
|
||||
return <FaceSelectionControl {...commonProps} {...options} />;
|
||||
} else if (type === 'choice') {
|
||||
return <RadioButtons {...commonProps}>
|
||||
{options.options.map(op => <RadioButton value={op} label={op} key={op}/>)}
|
||||
</RadioButtons>;
|
||||
} else {
|
||||
return <TextControl {...commonProps} {...options} />;
|
||||
}
|
||||
})()
|
||||
}
|
||||
</Field>
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function uiLabel(name) {
|
||||
return camelCaseSplit(name).map(w => w.toLowerCase()).join(' ');
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
|||
}
|
||||
</FormContext.Consumer>;
|
||||
}
|
||||
}
|
||||
|
||||
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 <MultiEntityImpl defaultValue={defaultValue} {...props}/>
|
||||
}
|
||||
|
||||
MultiEntity.contextTypes = {
|
||||
bus: PropTypes.object
|
||||
streams: PropTypes.object
|
||||
};
|
||||
|
|
@ -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}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
static contextTypes = {
|
||||
bus: PropTypes.object
|
||||
};
|
||||
});
|
||||
|
||||
export default function SingleEntity(props, {bus}) {
|
||||
return <SingleEntityImpl defaultValue={bus.state[entitySelectionToken(props.entity)][0]} {...props}/>
|
||||
export default function SingleEntity(props, {streams}) {
|
||||
return <SingleEntityImpl defaultValue={streams.selection[props.entity].value[0]} {...props}/>
|
||||
}
|
||||
|
||||
SingleEntity.contextTypes = {
|
||||
bus: PropTypes.object
|
||||
streams: PropTypes.object
|
||||
};
|
||||
|
|
@ -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(<BrepDebuggerWindow key='debug.BrepDebuggerWindow' auxGroup={services.cadScene.auxGroup} />);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 <AuxWidget visible={visible}
|
||||
left={x} top={y} className={ls.root} zIndex={550}>
|
||||
|
||||
return <AuxWidget visible={visible}
|
||||
left={x} top={y} className={ls.root} zIndex={550}>
|
||||
{visible && <Fragment>
|
||||
{hint && <div className={ls.hint}>{hint}</div>}
|
||||
{info && <div className={ls.info}>{info}</div>}
|
||||
|
|
@ -19,7 +18,11 @@ function ActionInfo({actionId, x, y, info, hint, hotKey}) {
|
|||
</AuxWidget>;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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 <ControlBar left={<LeftGroup />} right={<RightGroup />}/>;
|
||||
|
|
@ -17,7 +15,7 @@ export default function PlugableControlBar() {
|
|||
function ButtonGroup({actions}) {
|
||||
return actions.map(actionRef => {
|
||||
let [id, overrides] = toIdAndOverrides(actionRef);
|
||||
return <ConnectedActionButton key={id} actionId={id} {...overrides}/>;
|
||||
return <ConnectedActionButton key={id} actionId={id} {...overrides} />;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
|||
</ToolbarButton>
|
||||
}
|
||||
|
||||
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 => <ConfigurableToolbar {...props} small={small} />);
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}) => <ConnectedActionMenu key={id} menuId={id} actions={actions} />);
|
||||
|
|
@ -48,26 +48,26 @@ function ActionMenuItem({label, cssIcons, icon32, icon96, enabled, hotKey, visib
|
|||
return <MenuItem {...{label, icon, style, disabled: !enabled, hotKey, ...props}} />;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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.');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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, () => {
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
};
|
||||
|
|
@ -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']);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue