stuff
This commit is contained in:
parent
a0e63e4f94
commit
5a07ba5f56
|
@ -2,4 +2,5 @@
|
||||||
module.exports = Object.assign(
|
module.exports = Object.assign(
|
||||||
{},
|
{},
|
||||||
require('./pulse'),
|
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,
|
flatten,
|
||||||
memoizeWith,
|
memoizeWith,
|
||||||
pick,
|
pick,
|
||||||
|
filter,
|
||||||
} = require('ramda');
|
} = require('ramda');
|
||||||
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
@ -14,17 +15,20 @@ const r = require('r-dom');
|
||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
const { bindActionCreators } = require('redux');
|
const { bindActionCreators } = require('redux');
|
||||||
|
|
||||||
const {
|
|
||||||
GraphView,
|
|
||||||
Edge,
|
|
||||||
} = require('react-digraph');
|
|
||||||
|
|
||||||
const math = require('mathjs');
|
const math = require('mathjs');
|
||||||
|
|
||||||
|
const { Edge } = require('react-digraph');
|
||||||
|
|
||||||
|
const d = require('../../utils/d');
|
||||||
|
|
||||||
const { pulse: pulseActions } = require('../../actions');
|
const { pulse: pulseActions } = require('../../actions');
|
||||||
|
|
||||||
const { getPaiByTypeAndIndex } = require('../../selectors');
|
const { getPaiByTypeAndIndex } = require('../../selectors');
|
||||||
|
|
||||||
|
const {
|
||||||
|
GraphView,
|
||||||
|
} = require('./satellites-graph');
|
||||||
|
|
||||||
Edge.calculateOffset = function (nodeSize, source, target) {
|
Edge.calculateOffset = function (nodeSize, source, target) {
|
||||||
const arrowVector = math.matrix([ target.x - source.x, target.y - source.y ]);
|
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));
|
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 => ({
|
const paiToEdge = memoize(pai => ({
|
||||||
|
id: key(pai),
|
||||||
source: sourceKey(pai),
|
source: sourceKey(pai),
|
||||||
target: targetKey(pai),
|
target: targetKey(pai),
|
||||||
index: pai.index,
|
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 size = 120;
|
||||||
const s2 = size / 2;
|
const s2 = size / 2;
|
||||||
|
|
||||||
|
@ -213,48 +190,48 @@ const renderNode = (nodeRef, data, key, selected, hovered) => r({
|
||||||
source: Source,
|
source: Source,
|
||||||
client: Client,
|
client: Client,
|
||||||
module: Module,
|
module: Module,
|
||||||
}[data.type], {
|
}[data.type] || Module, {
|
||||||
selected,
|
selected,
|
||||||
hovered,
|
hovered,
|
||||||
});
|
});
|
||||||
|
|
||||||
const DebugText = ({ dgo, pai, open = false }) => r.div({
|
const DebugText = ({ dgo, pai, props }) => r.div({
|
||||||
style: {
|
style: {
|
||||||
fontSize: '50%',
|
fontSize: '50%',
|
||||||
},
|
},
|
||||||
}, [
|
}, props.preferences.showDebugInfo ? [
|
||||||
open && JSON.stringify(dgo, null, 2),
|
JSON.stringify(dgo, null, 2),
|
||||||
open && JSON.stringify(pai, null, 2),
|
JSON.stringify(pai, null, 2),
|
||||||
]);
|
] : []);
|
||||||
|
|
||||||
const SinkText = ({ dgo, pai }) => r.div([
|
const SinkText = ({ dgo, pai, props }) => r.div([
|
||||||
r.div({
|
r.div({
|
||||||
title: pai.name,
|
title: pai.name,
|
||||||
}, pai.description),
|
}, 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({
|
r.div({
|
||||||
title: pai.name,
|
title: pai.name,
|
||||||
}, pai.description),
|
}, 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({
|
r.div({
|
||||||
}, pai.name),
|
}, 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({
|
r.div({
|
||||||
title: pai.properties.module.description,
|
title: pai.properties.module.description,
|
||||||
}, pai.name),
|
}, pai.name),
|
||||||
r(DebugText, { dgo, pai }),
|
r(DebugText, { dgo, pai, props }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const renderNodeText = dgo => r('foreignObject', {
|
const renderNodeText = props => dgo => r('foreignObject', {
|
||||||
x: -s2,
|
x: -s2,
|
||||||
y: -s2,
|
y: -s2,
|
||||||
}, r.div({
|
}, r.div({
|
||||||
|
@ -274,6 +251,7 @@ const renderNodeText = dgo => r('foreignObject', {
|
||||||
}[dgo.type] || ModuleText, {
|
}[dgo.type] || ModuleText, {
|
||||||
dgo,
|
dgo,
|
||||||
pai: dgoToPai.get(dgo),
|
pai: dgoToPai.get(dgo),
|
||||||
|
props,
|
||||||
})));
|
})));
|
||||||
|
|
||||||
const afterRenderEdge = (id, element, edge, edgeContainer) => {
|
const afterRenderEdge = (id, element, edge, edgeContainer) => {
|
||||||
|
@ -289,6 +267,26 @@ class Graph extends React.Component {
|
||||||
this.state = {
|
this.state = {
|
||||||
selected: null,
|
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) {
|
onSelectNode(selected) {
|
||||||
|
@ -296,7 +294,6 @@ class Graph extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
onCreateNode() {
|
onCreateNode() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onUpdateNode() {
|
onUpdateNode() {
|
||||||
|
@ -322,17 +319,33 @@ class Graph extends React.Component {
|
||||||
onDeleteEdge() {}
|
onDeleteEdge() {}
|
||||||
|
|
||||||
render() {
|
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, [
|
const edges = map(paiToEdge, flatten(map(values, [
|
||||||
this.props.infos.sinkInputs,
|
this.props.infos.sinkInputs,
|
||||||
this.props.infos.sourceOutputs,
|
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 => {
|
nodes.forEach(node => {
|
||||||
if (node.x !== undefined) {
|
if (node.x !== undefined) {
|
||||||
return;
|
return;
|
||||||
|
@ -359,6 +372,7 @@ class Graph extends React.Component {
|
||||||
style: {},
|
style: {},
|
||||||
}, r(GraphView, {
|
}, r(GraphView, {
|
||||||
nodeKey: 'id',
|
nodeKey: 'id',
|
||||||
|
edgeKey: 'id',
|
||||||
|
|
||||||
nodes,
|
nodes,
|
||||||
edges,
|
edges,
|
||||||
|
@ -367,14 +381,14 @@ class Graph extends React.Component {
|
||||||
|
|
||||||
...graphConfig,
|
...graphConfig,
|
||||||
|
|
||||||
onSelectNode: this.onSelectNode.bind(this),
|
onSelectNode: this.onSelectNode,
|
||||||
onCreateNode: this.onCreateNode.bind(this),
|
onCreateNode: this.onCreateNode,
|
||||||
onUpdateNode: this.onUpdateNode.bind(this),
|
onUpdateNode: this.onUpdateNode,
|
||||||
onDeleteNode: this.onDeleteNode.bind(this),
|
onDeleteNode: this.onDeleteNode,
|
||||||
onSelectEdge: this.onSelectEdge.bind(this),
|
onSelectEdge: this.onSelectEdge,
|
||||||
onCreateEdge: this.onCreateEdge.bind(this),
|
onCreateEdge: this.onCreateEdge,
|
||||||
onSwapEdge: this.onSwapEdge.bind(this),
|
onSwapEdge: this.onSwapEdge,
|
||||||
onDeleteEdge: this.onDeleteEdge.bind(this),
|
onDeleteEdge: this.onDeleteEdge,
|
||||||
|
|
||||||
showGraphControls: false,
|
showGraphControls: false,
|
||||||
|
|
||||||
|
@ -384,14 +398,19 @@ class Graph extends React.Component {
|
||||||
|
|
||||||
renderDefs,
|
renderDefs,
|
||||||
renderNode,
|
renderNode,
|
||||||
renderNodeText,
|
renderNodeText: renderNodeText(this.props),
|
||||||
afterRenderEdge,
|
afterRenderEdge,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = connect(
|
module.exports = connect(
|
||||||
state => state.pulse,
|
state => ({
|
||||||
|
objects: state.pulse.objects,
|
||||||
|
infos: state.pulse.infos,
|
||||||
|
|
||||||
|
preferences: state.preferences,
|
||||||
|
}),
|
||||||
dispatch => bindActionCreators(pick([
|
dispatch => bindActionCreators(pick([
|
||||||
'moveSinkInput',
|
'moveSinkInput',
|
||||||
'moveSourceOutput',
|
'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;
|
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 {
|
.view-wrapper .graph {
|
||||||
background: var(--themeBaseColor);
|
background: var(--themeBaseColor);
|
||||||
}
|
}
|
||||||
|
@ -39,3 +61,32 @@ body {
|
||||||
.view-wrapper .graph .arrow {
|
.view-wrapper .graph .arrow {
|
||||||
fill: var(--successColor);
|
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 { combineReducers } = require('redux');
|
||||||
|
|
||||||
const { reducer: pulse, initialState: pulseInitialState } = require('./pulse');
|
const { reducer: pulse, initialState: pulseInitialState } = require('./pulse');
|
||||||
|
const { reducer: preferences, initialState: preferencesInitialState } = require('./preferences');
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
pulse: pulseInitialState,
|
pulse: pulseInitialState,
|
||||||
|
preferences: preferencesInitialState,
|
||||||
};
|
};
|
||||||
|
|
||||||
const reducer = combineReducers({
|
const reducer = combineReducers({
|
||||||
pulse,
|
pulse,
|
||||||
|
preferences,
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = {
|
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 */
|
/* global document */
|
||||||
|
|
||||||
|
const React = require('react');
|
||||||
|
|
||||||
const r = require('r-dom');
|
const r = require('r-dom');
|
||||||
|
|
||||||
const { render } = require('react-dom');
|
const { render } = require('react-dom');
|
||||||
|
@ -9,14 +11,16 @@ const { Provider } = require('react-redux');
|
||||||
const createStore = require('./store');
|
const createStore = require('./store');
|
||||||
|
|
||||||
const Graph = require('./components/graph');
|
const Graph = require('./components/graph');
|
||||||
|
const Preferences = require('./components/preferences');
|
||||||
|
|
||||||
const theme = require('./utils/theme');
|
const theme = require('./utils/theme');
|
||||||
|
|
||||||
const Root = () => r(Provider, {
|
const Root = () => r(Provider, {
|
||||||
store: createStore(),
|
store: createStore(),
|
||||||
}, [
|
}, r(React.Fragment, [
|
||||||
r(Graph),
|
r(Graph),
|
||||||
]);
|
r(Preferences),
|
||||||
|
]));
|
||||||
|
|
||||||
Object.entries(theme.colors).forEach(([ key, value ]) => {
|
Object.entries(theme.colors).forEach(([ key, value ]) => {
|
||||||
document.body.style.setProperty('--' + 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