stuff
This commit is contained in:
parent
a0e63e4f94
commit
5a07ba5f56
|
@ -2,4 +2,5 @@
|
|||
module.exports = Object.assign(
|
||||
{},
|
||||
require('./pulse'),
|
||||
require('./preferences'),
|
||||
);
|
||||
|
|
10
actions/preferences.js
Normal file
10
actions/preferences.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
|
||||
const { createActions: createActionCreators } = require('redux-actions');
|
||||
|
||||
module.exports = createActionCreators({
|
||||
PREFERENCES: {
|
||||
SET: null,
|
||||
TOGGLE: null,
|
||||
RESET_DEFAULTS: null,
|
||||
},
|
||||
});
|
8
components/button/index.js
Normal file
8
components/button/index.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
|
||||
const r = require('r-dom');
|
||||
|
||||
const Button = props => r.button({
|
||||
...props,
|
||||
}, props.children);
|
||||
|
||||
module.exports = Button;
|
15
components/checkbox/index.js
Normal file
15
components/checkbox/index.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
|
||||
const r = require('r-dom');
|
||||
|
||||
const Checkbox = props => r.label({
|
||||
classSet: { checkbox: true },
|
||||
}, [
|
||||
r.input({
|
||||
...props,
|
||||
type: 'checkbox',
|
||||
}),
|
||||
|
||||
...[].concat(props.children),
|
||||
]);
|
||||
|
||||
module.exports = Checkbox;
|
24
components/graph/base.js
Normal file
24
components/graph/base.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
|
||||
const {
|
||||
GraphView: GraphViewBase,
|
||||
} = require('react-digraph');
|
||||
|
||||
class GraphView extends GraphViewBase {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
Object.assign(this, {
|
||||
_super_handleNodeMove: this.handleNodeMove,
|
||||
handleNodeMove: this.constructor.prototype.handleNodeMove.bind(this),
|
||||
});
|
||||
}
|
||||
|
||||
handleNodeMove(position, nodeId, shiftKey) {
|
||||
this._super_handleNodeMove(position, nodeId, shiftKey);
|
||||
if (this.props.onNodeMove) {
|
||||
this.props.onNodeMove(position, nodeId, shiftKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { GraphView };
|
|
@ -5,6 +5,7 @@ const {
|
|||
flatten,
|
||||
memoizeWith,
|
||||
pick,
|
||||
filter,
|
||||
} = require('ramda');
|
||||
|
||||
const React = require('react');
|
||||
|
@ -14,17 +15,20 @@ const r = require('r-dom');
|
|||
const { connect } = require('react-redux');
|
||||
const { bindActionCreators } = require('redux');
|
||||
|
||||
const {
|
||||
GraphView,
|
||||
Edge,
|
||||
} = require('react-digraph');
|
||||
|
||||
const math = require('mathjs');
|
||||
|
||||
const { Edge } = require('react-digraph');
|
||||
|
||||
const d = require('../../utils/d');
|
||||
|
||||
const { pulse: pulseActions } = require('../../actions');
|
||||
|
||||
const { getPaiByTypeAndIndex } = require('../../selectors');
|
||||
|
||||
const {
|
||||
GraphView,
|
||||
} = require('./satellites-graph');
|
||||
|
||||
Edge.calculateOffset = function (nodeSize, source, target) {
|
||||
const arrowVector = math.matrix([ target.x - source.x, target.y - source.y ]);
|
||||
const offsetLength = Math.max(0, Math.min((0.85 * size), (math.norm(arrowVector) / 2) - 40));
|
||||
|
@ -71,6 +75,7 @@ const paoToNode = memoize(pao => ({
|
|||
}));
|
||||
|
||||
const paiToEdge = memoize(pai => ({
|
||||
id: key(pai),
|
||||
source: sourceKey(pai),
|
||||
target: targetKey(pai),
|
||||
index: pai.index,
|
||||
|
@ -112,34 +117,6 @@ const graphConfig = {
|
|||
},
|
||||
};
|
||||
|
||||
class D {
|
||||
constructor(s = '') {
|
||||
this._s = s;
|
||||
}
|
||||
|
||||
_next(...args) {
|
||||
return new this.constructor([ this._s, ...args ].join(' '));
|
||||
}
|
||||
|
||||
moveTo(x, y) {
|
||||
return this._next('M', x, y);
|
||||
}
|
||||
|
||||
lineTo(x, y) {
|
||||
return this._next('L', x, y);
|
||||
}
|
||||
|
||||
close() {
|
||||
return this._next('z');
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this._s;
|
||||
}
|
||||
}
|
||||
|
||||
const d = () => new D();
|
||||
|
||||
const size = 120;
|
||||
const s2 = size / 2;
|
||||
|
||||
|
@ -213,48 +190,48 @@ const renderNode = (nodeRef, data, key, selected, hovered) => r({
|
|||
source: Source,
|
||||
client: Client,
|
||||
module: Module,
|
||||
}[data.type], {
|
||||
}[data.type] || Module, {
|
||||
selected,
|
||||
hovered,
|
||||
});
|
||||
|
||||
const DebugText = ({ dgo, pai, open = false }) => r.div({
|
||||
const DebugText = ({ dgo, pai, props }) => r.div({
|
||||
style: {
|
||||
fontSize: '50%',
|
||||
},
|
||||
}, [
|
||||
open && JSON.stringify(dgo, null, 2),
|
||||
open && JSON.stringify(pai, null, 2),
|
||||
]);
|
||||
}, props.preferences.showDebugInfo ? [
|
||||
JSON.stringify(dgo, null, 2),
|
||||
JSON.stringify(pai, null, 2),
|
||||
] : []);
|
||||
|
||||
const SinkText = ({ dgo, pai }) => r.div([
|
||||
const SinkText = ({ dgo, pai, props }) => r.div([
|
||||
r.div({
|
||||
title: pai.name,
|
||||
}, pai.description),
|
||||
r(DebugText, { dgo, pai }),
|
||||
r(DebugText, { dgo, pai, props }),
|
||||
]);
|
||||
|
||||
const SourceText = ({ dgo, pai }) => r.div([
|
||||
const SourceText = ({ dgo, pai, props }) => r.div([
|
||||
r.div({
|
||||
title: pai.name,
|
||||
}, pai.description),
|
||||
r(DebugText, { dgo, pai }),
|
||||
r(DebugText, { dgo, pai, props }),
|
||||
]);
|
||||
|
||||
const ClientText = ({ dgo, pai }) => r.div([
|
||||
const ClientText = ({ dgo, pai, props }) => r.div([
|
||||
r.div({
|
||||
}, pai.name),
|
||||
r(DebugText, { dgo, pai }),
|
||||
r(DebugText, { dgo, pai, props }),
|
||||
]);
|
||||
|
||||
const ModuleText = ({ dgo, pai }) => r.div([
|
||||
const ModuleText = ({ dgo, pai, props }) => r.div([
|
||||
r.div({
|
||||
title: pai.properties.module.description,
|
||||
}, pai.name),
|
||||
r(DebugText, { dgo, pai }),
|
||||
r(DebugText, { dgo, pai, props }),
|
||||
]);
|
||||
|
||||
const renderNodeText = dgo => r('foreignObject', {
|
||||
const renderNodeText = props => dgo => r('foreignObject', {
|
||||
x: -s2,
|
||||
y: -s2,
|
||||
}, r.div({
|
||||
|
@ -274,6 +251,7 @@ const renderNodeText = dgo => r('foreignObject', {
|
|||
}[dgo.type] || ModuleText, {
|
||||
dgo,
|
||||
pai: dgoToPai.get(dgo),
|
||||
props,
|
||||
})));
|
||||
|
||||
const afterRenderEdge = (id, element, edge, edgeContainer) => {
|
||||
|
@ -289,6 +267,26 @@ class Graph extends React.Component {
|
|||
this.state = {
|
||||
selected: null,
|
||||
};
|
||||
|
||||
Object.assign(this, {
|
||||
onSelectNode: this.onSelectNode.bind(this),
|
||||
onCreateNode: this.onCreateNode.bind(this),
|
||||
onUpdateNode: this.onUpdateNode.bind(this),
|
||||
onDeleteNode: this.onDeleteNode.bind(this),
|
||||
onSelectEdge: this.onSelectEdge.bind(this),
|
||||
onCreateEdge: this.onCreateEdge.bind(this),
|
||||
onSwapEdge: this.onSwapEdge.bind(this),
|
||||
onDeleteEdge: this.onDeleteEdge.bind(this),
|
||||
});
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return !(
|
||||
(nextProps.objects === this.props.objects) &&
|
||||
(nextProps.infos === this.props.infos) &&
|
||||
(nextProps.preferences === this.props.preferences) &&
|
||||
(nextState.selected === this.state.selected)
|
||||
);
|
||||
}
|
||||
|
||||
onSelectNode(selected) {
|
||||
|
@ -296,7 +294,6 @@ class Graph extends React.Component {
|
|||
}
|
||||
|
||||
onCreateNode() {
|
||||
|
||||
}
|
||||
|
||||
onUpdateNode() {
|
||||
|
@ -322,17 +319,33 @@ class Graph extends React.Component {
|
|||
onDeleteEdge() {}
|
||||
|
||||
render() {
|
||||
const nodes = map(paoToNode, flatten(map(values, [
|
||||
this.props.objects.sinks,
|
||||
this.props.objects.sources,
|
||||
this.props.objects.clients,
|
||||
this.props.objects.modules,
|
||||
])));
|
||||
const edges = map(paiToEdge, flatten(map(values, [
|
||||
this.props.infos.sinkInputs,
|
||||
this.props.infos.sourceOutputs,
|
||||
])));
|
||||
|
||||
const connectedNodeKeys = {};
|
||||
edges.forEach(edge => {
|
||||
connectedNodeKeys[edge.source] = true;
|
||||
connectedNodeKeys[edge.target] = true;
|
||||
});
|
||||
|
||||
const nodes = filter(node => {
|
||||
if ((this.props.preferences.hideDisconnectedClients && node.type === 'client') ||
|
||||
(this.props.preferences.hideDisconnectedModules && node.type === 'module') ||
|
||||
(this.props.preferences.hideDisconnectedSources && node.type === 'source') ||
|
||||
(this.props.preferences.hideDisconnectedSinks && node.type === 'sink')
|
||||
) {
|
||||
return connectedNodeKeys[node.id];
|
||||
}
|
||||
return true;
|
||||
}, map(paoToNode, flatten(map(values, [
|
||||
this.props.objects.sinks,
|
||||
this.props.objects.sources,
|
||||
this.props.objects.clients,
|
||||
this.props.objects.modules,
|
||||
]))));
|
||||
|
||||
nodes.forEach(node => {
|
||||
if (node.x !== undefined) {
|
||||
return;
|
||||
|
@ -359,6 +372,7 @@ class Graph extends React.Component {
|
|||
style: {},
|
||||
}, r(GraphView, {
|
||||
nodeKey: 'id',
|
||||
edgeKey: 'id',
|
||||
|
||||
nodes,
|
||||
edges,
|
||||
|
@ -367,14 +381,14 @@ class Graph extends React.Component {
|
|||
|
||||
...graphConfig,
|
||||
|
||||
onSelectNode: this.onSelectNode.bind(this),
|
||||
onCreateNode: this.onCreateNode.bind(this),
|
||||
onUpdateNode: this.onUpdateNode.bind(this),
|
||||
onDeleteNode: this.onDeleteNode.bind(this),
|
||||
onSelectEdge: this.onSelectEdge.bind(this),
|
||||
onCreateEdge: this.onCreateEdge.bind(this),
|
||||
onSwapEdge: this.onSwapEdge.bind(this),
|
||||
onDeleteEdge: this.onDeleteEdge.bind(this),
|
||||
onSelectNode: this.onSelectNode,
|
||||
onCreateNode: this.onCreateNode,
|
||||
onUpdateNode: this.onUpdateNode,
|
||||
onDeleteNode: this.onDeleteNode,
|
||||
onSelectEdge: this.onSelectEdge,
|
||||
onCreateEdge: this.onCreateEdge,
|
||||
onSwapEdge: this.onSwapEdge,
|
||||
onDeleteEdge: this.onDeleteEdge,
|
||||
|
||||
showGraphControls: false,
|
||||
|
||||
|
@ -384,14 +398,19 @@ class Graph extends React.Component {
|
|||
|
||||
renderDefs,
|
||||
renderNode,
|
||||
renderNodeText,
|
||||
renderNodeText: renderNodeText(this.props),
|
||||
afterRenderEdge,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connect(
|
||||
state => state.pulse,
|
||||
state => ({
|
||||
objects: state.pulse.objects,
|
||||
infos: state.pulse.infos,
|
||||
|
||||
preferences: state.preferences,
|
||||
}),
|
||||
dispatch => bindActionCreators(pick([
|
||||
'moveSinkInput',
|
||||
'moveSourceOutput',
|
||||
|
|
147
components/graph/satellites-graph.js
Normal file
147
components/graph/satellites-graph.js
Normal file
|
@ -0,0 +1,147 @@
|
|||
/* global document */
|
||||
|
||||
const {
|
||||
map,
|
||||
prop,
|
||||
groupBy,
|
||||
flatten,
|
||||
addIndex,
|
||||
mapObjIndexed,
|
||||
values,
|
||||
} = require('ramda');
|
||||
|
||||
const React = require('react');
|
||||
|
||||
const r = require('r-dom');
|
||||
|
||||
const plusMinus = require('../../utils/plus-minus');
|
||||
|
||||
const {
|
||||
GraphView: GraphViewBase,
|
||||
} = require('./base');
|
||||
|
||||
const mapIndexed = addIndex(map);
|
||||
|
||||
const Satellite = () => r(React.Fragment);
|
||||
|
||||
const satelliteSpread = 36;
|
||||
|
||||
class GraphView extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
edgesByTargetNodeKey: {},
|
||||
satelliteNodesByTargetNodeKey: {},
|
||||
};
|
||||
|
||||
this.graph = React.createRef();
|
||||
|
||||
Object.assign(this, {
|
||||
onSwapEdge: this.onSwapEdge.bind(this),
|
||||
onNodeMove: this.onNodeMove.bind(this),
|
||||
|
||||
renderNode: this.renderNode.bind(this),
|
||||
renderNodeText: this.renderNodeText.bind(this),
|
||||
});
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props) {
|
||||
const { nodeKey, edgeKey } = props;
|
||||
|
||||
const edgesByTargetNodeKey = groupBy(prop('target'), props.edges);
|
||||
const satelliteNodesByTargetNodeKey = map(map(edge => ({
|
||||
[nodeKey]: `${edge.target}__satellite__${edge[edgeKey]}`,
|
||||
edge: edge[edgeKey],
|
||||
source: edge.source,
|
||||
target: edge.target,
|
||||
type: 'satellite',
|
||||
})), edgesByTargetNodeKey);
|
||||
|
||||
return { edgesByTargetNodeKey, satelliteNodesByTargetNodeKey };
|
||||
}
|
||||
|
||||
static repositionSatellites(position, satelliteNodes) {
|
||||
satelliteNodes.forEach((satelliteNode, i) => {
|
||||
satelliteNode.x = position.x;
|
||||
satelliteNode.y = position.y +
|
||||
(satelliteSpread * plusMinus(i)) +
|
||||
((satelliteSpread / 2) * ((satelliteNodes.length + 1) % 2));
|
||||
});
|
||||
}
|
||||
|
||||
onSwapEdge(sourceNode, targetNode, edge) {
|
||||
this.props.onSwapEdge(sourceNode, targetNode, edge);
|
||||
|
||||
const { nodeKey } = this.props;
|
||||
|
||||
const createdEdgeId = `edge-${sourceNode[nodeKey]}-${targetNode[nodeKey]}-container`;
|
||||
const createdEdge = document.getElementById(createdEdgeId);
|
||||
createdEdge.remove();
|
||||
this.graph.current.forceUpdate();
|
||||
}
|
||||
|
||||
onNodeMove(position, nodeId, shiftKey) {
|
||||
const { nodeKey } = this.props;
|
||||
const satelliteNodes = this.state.satelliteNodesByTargetNodeKey[nodeId];
|
||||
if (satelliteNodes) {
|
||||
this.constructor.repositionSatellites(position, satelliteNodes);
|
||||
satelliteNodes.forEach(satelliteNode => {
|
||||
this.graph.current.handleNodeMove(satelliteNode, satelliteNode[nodeKey], shiftKey);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
renderNode(nodeRef, dgo, key, selected, hovered) {
|
||||
if (dgo.type !== 'satellite') {
|
||||
return this.props.renderNode(nodeRef, dgo, key, selected, hovered);
|
||||
}
|
||||
|
||||
return r(Satellite);
|
||||
}
|
||||
|
||||
renderNodeText(dgo) {
|
||||
if (dgo.type !== 'satellite') {
|
||||
return this.props.renderNodeText(dgo);
|
||||
}
|
||||
|
||||
return r(React.Fragment);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { nodeKey } = this.props;
|
||||
const { edgesByTargetNodeKey, satelliteNodesByTargetNodeKey } = this.state;
|
||||
|
||||
const nodes = flatten(map(node => {
|
||||
const satelliteNodes = satelliteNodesByTargetNodeKey[node[nodeKey]] || [];
|
||||
this.constructor.repositionSatellites(node, satelliteNodes);
|
||||
return satelliteNodes.concat(node);
|
||||
}, this.props.nodes));
|
||||
|
||||
const edges = flatten(values(mapObjIndexed((edges, target) => mapIndexed((edge, i) => ({
|
||||
id: edge.id,
|
||||
source: edge.source,
|
||||
target: satelliteNodesByTargetNodeKey[target][i][nodeKey],
|
||||
originalTarget: edge.target,
|
||||
index: edge.index,
|
||||
type: edge.type,
|
||||
}), edges), edgesByTargetNodeKey)));
|
||||
|
||||
return r(GraphViewBase, {
|
||||
...this.props,
|
||||
|
||||
ref: this.graph,
|
||||
|
||||
nodes,
|
||||
edges,
|
||||
|
||||
onSwapEdge: this.onSwapEdge,
|
||||
onNodeMove: this.onNodeMove,
|
||||
|
||||
renderNode: this.renderNode,
|
||||
renderNodeText: this.renderNodeText,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { GraphView };
|
90
components/preferences/index.js
Normal file
90
components/preferences/index.js
Normal file
|
@ -0,0 +1,90 @@
|
|||
|
||||
const {
|
||||
pick,
|
||||
} = require('ramda');
|
||||
|
||||
const r = require('r-dom');
|
||||
|
||||
const { connect } = require('react-redux');
|
||||
const { bindActionCreators } = require('redux');
|
||||
|
||||
const { withStateHandlers } = require('recompose');
|
||||
|
||||
const { preferences: preferencesActions } = require('../../actions');
|
||||
|
||||
const Button = require('../button');
|
||||
const Checkbox = require('../checkbox');
|
||||
|
||||
const Preferences = withStateHandlers(
|
||||
{
|
||||
open: false,
|
||||
},
|
||||
{
|
||||
toggle: ({ open }) => () => ({ open: !open }),
|
||||
},
|
||||
)(({ open, toggle, ...props }) => r.div({
|
||||
classSet: {
|
||||
preferences: true,
|
||||
open,
|
||||
},
|
||||
}, open ? [
|
||||
r.div([
|
||||
r(Button, {
|
||||
style: { width: '100%' },
|
||||
onClick: toggle,
|
||||
}, 'Close'),
|
||||
]),
|
||||
|
||||
r.div([
|
||||
r(Checkbox, {
|
||||
checked: props.preferences.hideDisconnectedClients,
|
||||
onChange: () => props.actions.toggle('hideDisconnectedClients'),
|
||||
}, 'Hide disconnected clients'),
|
||||
]),
|
||||
|
||||
r.div([
|
||||
r(Checkbox, {
|
||||
checked: props.preferences.hideDisconnectedModules,
|
||||
onChange: () => props.actions.toggle('hideDisconnectedModules'),
|
||||
}, 'Hide disconnected modules'),
|
||||
]),
|
||||
|
||||
r.div([
|
||||
r(Checkbox, {
|
||||
checked: props.preferences.hideDisconnectedSource,
|
||||
onChange: () => props.actions.toggle('hideDisconnectedSource'),
|
||||
}, 'Hide disconnected source'),
|
||||
]),
|
||||
|
||||
r.div([
|
||||
r(Checkbox, {
|
||||
checked: props.preferences.hideDisconnectedSinks,
|
||||
onChange: () => props.actions.toggle('hideDisconnectedSinks'),
|
||||
}, 'Hide disconnected sinks'),
|
||||
]),
|
||||
|
||||
r.div([
|
||||
r(Checkbox, {
|
||||
checked: props.preferences.showDebugInfo,
|
||||
onChange: () => props.actions.toggle('showDebugInfo'),
|
||||
}, 'Show debug info'),
|
||||
]),
|
||||
|
||||
r.div([
|
||||
r(Button, {
|
||||
style: { width: '100%' },
|
||||
onClick: props.actions.resetDefaults,
|
||||
}, 'Reset to defaults'),
|
||||
]),
|
||||
] : [
|
||||
r(Button, {
|
||||
onClick: toggle,
|
||||
}, 'Props'),
|
||||
]));
|
||||
|
||||
module.exports = connect(
|
||||
state => pick([ 'preferences' ], state),
|
||||
dispatch => ({
|
||||
actions: bindActionCreators(preferencesActions, dispatch),
|
||||
}),
|
||||
)(Preferences);
|
51
index.css
51
index.css
|
@ -5,6 +5,28 @@ body {
|
|||
font: -webkit-control;
|
||||
}
|
||||
|
||||
button {
|
||||
background: var(--themeBgColor);
|
||||
color: var(--themeTextColor);
|
||||
border: 1px solid var(--borders);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
border-color: var(--themeSelectedBgColor);
|
||||
}
|
||||
|
||||
button:focus {
|
||||
outline: none;
|
||||
border-color: var(--themeSelectedBgColor);
|
||||
}
|
||||
|
||||
button:active {
|
||||
background: var(--themeSelectedBgColor);
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
.view-wrapper .graph {
|
||||
background: var(--themeBaseColor);
|
||||
}
|
||||
|
@ -39,3 +61,32 @@ body {
|
|||
.view-wrapper .graph .arrow {
|
||||
fill: var(--successColor);
|
||||
}
|
||||
|
||||
.preferences {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
padding: 1rem;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.preferences:not(.open) {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.preferences:not(.open) > * {
|
||||
pointer-events: initial;
|
||||
}
|
||||
|
||||
.preferences.open {
|
||||
background: var(--themeBgColor);
|
||||
}
|
||||
|
||||
.preferences > div {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
user-select: none;
|
||||
}
|
||||
|
|
|
@ -2,13 +2,16 @@
|
|||
const { combineReducers } = require('redux');
|
||||
|
||||
const { reducer: pulse, initialState: pulseInitialState } = require('./pulse');
|
||||
const { reducer: preferences, initialState: preferencesInitialState } = require('./preferences');
|
||||
|
||||
const initialState = {
|
||||
pulse: pulseInitialState,
|
||||
preferences: preferencesInitialState,
|
||||
};
|
||||
|
||||
const reducer = combineReducers({
|
||||
pulse,
|
||||
preferences,
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
|
|
27
reducers/preferences.js
Normal file
27
reducers/preferences.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
|
||||
const {
|
||||
merge,
|
||||
} = require('ramda');
|
||||
|
||||
const { handleActions } = require('redux-actions');
|
||||
|
||||
const { preferences } = require('../actions');
|
||||
|
||||
const initialState = {
|
||||
hideDisconnectedClients: true,
|
||||
hideDisconnectedModules: true,
|
||||
hideDisconnectedSources: false,
|
||||
hideDisconnectedSinks: false,
|
||||
showDebugInfo: false,
|
||||
};
|
||||
|
||||
const reducer = handleActions({
|
||||
[preferences.set]: (state, { payload }) => merge(state, payload),
|
||||
[preferences.toggle]: (state, { payload }) => merge(state, { [payload]: !state[payload] }),
|
||||
[preferences.resetDefaults]: () => initialState,
|
||||
}, initialState);
|
||||
|
||||
module.exports = {
|
||||
initialState,
|
||||
reducer,
|
||||
};
|
|
@ -1,5 +1,7 @@
|
|||
/* global document */
|
||||
|
||||
const React = require('react');
|
||||
|
||||
const r = require('r-dom');
|
||||
|
||||
const { render } = require('react-dom');
|
||||
|
@ -9,14 +11,16 @@ const { Provider } = require('react-redux');
|
|||
const createStore = require('./store');
|
||||
|
||||
const Graph = require('./components/graph');
|
||||
const Preferences = require('./components/preferences');
|
||||
|
||||
const theme = require('./utils/theme');
|
||||
|
||||
const Root = () => r(Provider, {
|
||||
store: createStore(),
|
||||
}, [
|
||||
}, r(React.Fragment, [
|
||||
r(Graph),
|
||||
]);
|
||||
r(Preferences),
|
||||
]));
|
||||
|
||||
Object.entries(theme.colors).forEach(([ key, value ]) => {
|
||||
document.body.style.setProperty('--' + key, value);
|
||||
|
|
30
utils/d/index.js
Normal file
30
utils/d/index.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
|
||||
class D {
|
||||
constructor(s = '') {
|
||||
this._s = s;
|
||||
}
|
||||
|
||||
_next(...args) {
|
||||
return new this.constructor([ this._s, ...args ].join(' '));
|
||||
}
|
||||
|
||||
moveTo(x, y) {
|
||||
return this._next('M', x, y);
|
||||
}
|
||||
|
||||
lineTo(x, y) {
|
||||
return this._next('L', x, y);
|
||||
}
|
||||
|
||||
close() {
|
||||
return this._next('z');
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this._s;
|
||||
}
|
||||
}
|
||||
|
||||
const d = () => new D();
|
||||
|
||||
module.exports = d;
|
4
utils/plus-minus.js
Normal file
4
utils/plus-minus.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
|
||||
const plusMinus = i => Math.ceil(i / 2) * ((2 * ((i + 1) % 2)) - 1);
|
||||
|
||||
module.exports = plusMinus;
|
13
utils/plus-minus.test.js
Normal file
13
utils/plus-minus.test.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
|
||||
import test from 'ava';
|
||||
|
||||
import { map, range } from 'ramda';
|
||||
|
||||
import plusMinus from './plus-minus';
|
||||
|
||||
test(t => {
|
||||
t.deepEqual(
|
||||
map(plusMinus, range(0, 7)),
|
||||
[ 0, -1, 1, -2, 2, -3, 3 ],
|
||||
);
|
||||
});
|
Loading…
Reference in New Issue
Block a user