Hotkeys
This commit is contained in:
parent
bfc3fe078c
commit
2af5f85dc6
|
@ -6,79 +6,95 @@ const {
|
||||||
sortBy,
|
sortBy,
|
||||||
} = require('ramda');
|
} = require('ramda');
|
||||||
|
|
||||||
|
const React = require('react');
|
||||||
|
|
||||||
const r = require('r-dom');
|
const r = require('r-dom');
|
||||||
|
|
||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
const { bindActionCreators } = require('redux');
|
const { bindActionCreators } = require('redux');
|
||||||
|
|
||||||
const { withStateHandlers } = require('recompose');
|
|
||||||
|
|
||||||
const { pulse: pulseActions } = require('../../actions');
|
const { pulse: pulseActions } = require('../../actions');
|
||||||
|
|
||||||
const Button = require('../button');
|
const Button = require('../button');
|
||||||
const Label = require('../label');
|
const Label = require('../label');
|
||||||
const Select = require('../select');
|
const Select = require('../select');
|
||||||
|
|
||||||
const Preferences = withStateHandlers(
|
class Cards extends React.Component {
|
||||||
{
|
constructor(props) {
|
||||||
open: false,
|
super(props);
|
||||||
},
|
|
||||||
{
|
|
||||||
toggle: ({ open }) => () => ({ open: !open }),
|
|
||||||
},
|
|
||||||
)(({ open, toggle, ...props }) => r.div({
|
|
||||||
classSet: {
|
|
||||||
panel: true,
|
|
||||||
cards: true,
|
|
||||||
open,
|
|
||||||
},
|
|
||||||
}, open ? [
|
|
||||||
r.div([
|
|
||||||
r(Button, {
|
|
||||||
style: { width: '100%' },
|
|
||||||
autoFocus: true,
|
|
||||||
onClick: toggle,
|
|
||||||
}, 'Close'),
|
|
||||||
]),
|
|
||||||
|
|
||||||
r.hr(),
|
this.state = {
|
||||||
|
open: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
...map(card => r(Label, {
|
toggle() {
|
||||||
title: card.name,
|
this.setState({ open: !this.state.open });
|
||||||
}, [
|
}
|
||||||
r(Label, [
|
|
||||||
path([ 'properties', 'device', 'description' ], card),
|
|
||||||
]),
|
|
||||||
|
|
||||||
r(Select, {
|
close() {
|
||||||
options: sortBy(p => -p.priority, card.profiles),
|
this.setState({ open: false });
|
||||||
optionValue: p => p.name,
|
}
|
||||||
optionText: p => [
|
|
||||||
p.description,
|
render() {
|
||||||
!p.available && '(unavailable)',
|
const { open } = this.state;
|
||||||
]
|
const toggle = this.toggle.bind(this);
|
||||||
.filter(Boolean)
|
|
||||||
.join(' '),
|
return r.div({
|
||||||
value: card.activeProfileName,
|
classSet: {
|
||||||
onChange: e => {
|
panel: true,
|
||||||
props.actions.setCardProfile(card.index, e.target.value);
|
cards: true,
|
||||||
|
open,
|
||||||
},
|
},
|
||||||
}),
|
}, open ? [
|
||||||
]), values(props.cards)),
|
r.div([
|
||||||
|
r(Button, {
|
||||||
|
style: { width: '100%' },
|
||||||
|
autoFocus: true,
|
||||||
|
onClick: toggle,
|
||||||
|
}, 'Close'),
|
||||||
|
]),
|
||||||
|
|
||||||
props.preferences.showDebugInfo && r.pre({
|
r.hr(),
|
||||||
style: {
|
|
||||||
fontSize: '0.75em',
|
...map(card => r(Label, {
|
||||||
},
|
title: card.name,
|
||||||
}, [
|
}, [
|
||||||
JSON.stringify(props, null, 2),
|
r(Label, [
|
||||||
]),
|
path([ 'properties', 'device', 'description' ], card),
|
||||||
] : [
|
]),
|
||||||
r(Button, {
|
|
||||||
autoFocus: true,
|
r(Select, {
|
||||||
onClick: toggle,
|
options: sortBy(p => -p.priority, card.profiles),
|
||||||
}, 'Cards'),
|
optionValue: p => p.name,
|
||||||
]));
|
optionText: p => [
|
||||||
|
p.description,
|
||||||
|
!p.available && '(unavailable)',
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' '),
|
||||||
|
value: card.activeProfileName,
|
||||||
|
onChange: e => {
|
||||||
|
this.props.actions.setCardProfile(card.index, e.target.value);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]), values(this.props.cards)),
|
||||||
|
|
||||||
|
this.props.preferences.showDebugInfo && r.pre({
|
||||||
|
style: {
|
||||||
|
fontSize: '0.75em',
|
||||||
|
},
|
||||||
|
}, [
|
||||||
|
JSON.stringify(this.props, null, 2),
|
||||||
|
]),
|
||||||
|
] : [
|
||||||
|
r(Button, {
|
||||||
|
autoFocus: true,
|
||||||
|
onClick: toggle,
|
||||||
|
}, 'Cards'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = connect(
|
module.exports = connect(
|
||||||
state => ({
|
state => ({
|
||||||
|
@ -88,4 +104,6 @@ module.exports = connect(
|
||||||
dispatch => ({
|
dispatch => ({
|
||||||
actions: bindActionCreators(pulseActions, dispatch),
|
actions: bindActionCreators(pulseActions, dispatch),
|
||||||
}),
|
}),
|
||||||
)(Preferences);
|
null,
|
||||||
|
{ withRef: true },
|
||||||
|
)(Cards);
|
||||||
|
|
|
@ -1,16 +1,23 @@
|
||||||
|
/* global document */
|
||||||
|
|
||||||
const {
|
const {
|
||||||
map,
|
|
||||||
values,
|
|
||||||
flatten,
|
|
||||||
path,
|
|
||||||
filter,
|
|
||||||
forEach,
|
|
||||||
merge,
|
|
||||||
repeat,
|
|
||||||
defaultTo,
|
|
||||||
prop,
|
|
||||||
all,
|
all,
|
||||||
|
bind,
|
||||||
|
compose,
|
||||||
|
defaultTo,
|
||||||
|
filter,
|
||||||
|
find,
|
||||||
|
flatten,
|
||||||
|
forEach,
|
||||||
|
keys,
|
||||||
|
map,
|
||||||
|
merge,
|
||||||
|
path,
|
||||||
|
pick,
|
||||||
|
prop,
|
||||||
|
repeat,
|
||||||
|
sortBy,
|
||||||
|
values,
|
||||||
} = require('ramda');
|
} = require('ramda');
|
||||||
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
@ -20,6 +27,8 @@ const r = require('r-dom');
|
||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
const { bindActionCreators } = require('redux');
|
const { bindActionCreators } = require('redux');
|
||||||
|
|
||||||
|
const { HotKeys } = require('react-hotkeys');
|
||||||
|
|
||||||
const d = require('../../utils/d');
|
const d = require('../../utils/d');
|
||||||
const memoize = require('../../utils/memoize');
|
const memoize = require('../../utils/memoize');
|
||||||
|
|
||||||
|
@ -43,6 +52,8 @@ const { size } = require('../../constants/view');
|
||||||
|
|
||||||
const VolumeSlider = require('../../components/volume-slider');
|
const VolumeSlider = require('../../components/volume-slider');
|
||||||
|
|
||||||
|
const { keyMap } = require('../hot-keys');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
GraphView,
|
GraphView,
|
||||||
} = require('./satellites-graph');
|
} = require('./satellites-graph');
|
||||||
|
@ -53,6 +64,49 @@ const {
|
||||||
|
|
||||||
const LayoutEngine = require('./layout-engine');
|
const LayoutEngine = require('./layout-engine');
|
||||||
|
|
||||||
|
const leftOf = (x, xs) => {
|
||||||
|
const i = ((xs.indexOf(x) + xs.length - 1) % xs.length);
|
||||||
|
return xs[i];
|
||||||
|
};
|
||||||
|
|
||||||
|
const rightOf = (x, xs) => {
|
||||||
|
const i = ((xs.indexOf(x) + 1) % xs.length);
|
||||||
|
return xs[i];
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectionObjectTypes = {
|
||||||
|
order: [
|
||||||
|
'source',
|
||||||
|
'sourceOutput',
|
||||||
|
'client|module',
|
||||||
|
'sinkInput',
|
||||||
|
'sink',
|
||||||
|
],
|
||||||
|
|
||||||
|
left(type) {
|
||||||
|
return leftOf(type, this.order);
|
||||||
|
},
|
||||||
|
|
||||||
|
right(type) {
|
||||||
|
return rightOf(type, this.order);
|
||||||
|
},
|
||||||
|
|
||||||
|
fromPulseType(type) {
|
||||||
|
if (type === 'client' || type === 'module') {
|
||||||
|
return 'client|module';
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
},
|
||||||
|
|
||||||
|
toPulsePredicate(type) {
|
||||||
|
type = this.fromPulseType(type);
|
||||||
|
if (type === 'client|module') {
|
||||||
|
return o => (o.type === 'client' || o.type === 'module');
|
||||||
|
}
|
||||||
|
return o => o.type === type;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const dgoToPai = new WeakMap();
|
const dgoToPai = new WeakMap();
|
||||||
|
|
||||||
const key = pao => `${pao.type}-${pao.index}`;
|
const key = pao => `${pao.type}-${pao.index}`;
|
||||||
|
@ -97,14 +151,6 @@ const getPaiIcon = memoize(pai => {
|
||||||
path([ 'properties', 'device', 'icon_name' ], pai);
|
path([ 'properties', 'device', 'icon_name' ], pai);
|
||||||
});
|
});
|
||||||
|
|
||||||
const graphConfig = {
|
|
||||||
nodeTypes: {},
|
|
||||||
|
|
||||||
nodeSubtypes: {},
|
|
||||||
|
|
||||||
edgeTypes: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
const s2 = size / 2;
|
const s2 = size / 2;
|
||||||
|
|
||||||
const Sink = () => r.path({
|
const Sink = () => r.path({
|
||||||
|
@ -485,6 +531,82 @@ class Graph extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromProps(props) {
|
||||||
|
let edges = map(paoToEdge, flatten(map(values, [
|
||||||
|
props.objects.sinkInputs,
|
||||||
|
props.objects.sourceOutputs,
|
||||||
|
props.derivations.monitorSources,
|
||||||
|
])));
|
||||||
|
|
||||||
|
const connectedNodeKeys = new Set();
|
||||||
|
edges.forEach(edge => {
|
||||||
|
connectedNodeKeys.add(edge.source);
|
||||||
|
connectedNodeKeys.add(edge.target);
|
||||||
|
});
|
||||||
|
|
||||||
|
const filteredNodeKeys = new Set();
|
||||||
|
|
||||||
|
const nodes = filter(node => {
|
||||||
|
if ((props.preferences.hideDisconnectedClients && node.type === 'client') ||
|
||||||
|
(props.preferences.hideDisconnectedModules && node.type === 'module') ||
|
||||||
|
(props.preferences.hideDisconnectedSources && node.type === 'source') ||
|
||||||
|
(props.preferences.hideDisconnectedSinks && node.type === 'sink')
|
||||||
|
) {
|
||||||
|
if (!connectedNodeKeys.has(node.id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const pai = dgoToPai.get(node);
|
||||||
|
if (pai) {
|
||||||
|
if (props.preferences.hideMonitors &&
|
||||||
|
pai.properties.device &&
|
||||||
|
pai.properties.device.class === 'monitor'
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.preferences.hidePulseaudioApps) {
|
||||||
|
const binary = path([ 'properties', 'application', 'process', 'binary' ], pai) || '';
|
||||||
|
const name = path([ 'properties', 'application', 'name' ], pai) || '';
|
||||||
|
if (binary.startsWith('pavucontrol') ||
|
||||||
|
binary.startsWith('kmix') ||
|
||||||
|
name === 'paclient.js'
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredNodeKeys.add(node.id);
|
||||||
|
return true;
|
||||||
|
}, map(paoToNode, flatten(map(values, [
|
||||||
|
props.objects.sinks,
|
||||||
|
props.objects.sources,
|
||||||
|
props.objects.clients,
|
||||||
|
props.objects.modules,
|
||||||
|
]))));
|
||||||
|
|
||||||
|
edges = filter(edge => {
|
||||||
|
if (props.preferences.hideMonitorSourceEdges && edge.type === 'monitorSource') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return filteredNodeKeys.has(edge.source) && filteredNodeKeys.has(edge.target);
|
||||||
|
}, edges);
|
||||||
|
|
||||||
|
nodes.forEach(node => {
|
||||||
|
const pai = getPaiByTypeAndIndex(node.type, node.index)({ pulse: props });
|
||||||
|
dgoToPai.set(node, pai);
|
||||||
|
});
|
||||||
|
|
||||||
|
edges.forEach(edge => {
|
||||||
|
const pai = getPaiByTypeAndIndex(edge.type, edge.index)({ pulse: props });
|
||||||
|
dgoToPai.set(edge, pai);
|
||||||
|
});
|
||||||
|
|
||||||
|
return { nodes, edges };
|
||||||
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
return !(
|
return !(
|
||||||
(nextProps.objects === this.props.objects) &&
|
(nextProps.objects === this.props.objects) &&
|
||||||
|
@ -497,6 +619,9 @@ class Graph extends React.Component {
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.getIconPath('audio-volume-muted');
|
this.getIconPath('audio-volume-muted');
|
||||||
|
|
||||||
|
this.graphViewElement = document.querySelector('#graph .view-wrapper');
|
||||||
|
this.graphViewElement.setAttribute('tabindex', '-1');
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
|
@ -550,15 +675,11 @@ class Graph extends React.Component {
|
||||||
const pai = dgoToPai.get(data);
|
const pai = dgoToPai.get(data);
|
||||||
if (pai && event.button === 1) {
|
if (pai && event.button === 1) {
|
||||||
if (pai.type === 'sink' ||
|
if (pai.type === 'sink' ||
|
||||||
pai.type === 'source'
|
pai.type === 'source' ||
|
||||||
|
pai.type === 'client' ||
|
||||||
|
pai.type === 'module'
|
||||||
) {
|
) {
|
||||||
this.toggleMute(pai);
|
this.toggleMute(pai);
|
||||||
} else if (pai.type === 'client') {
|
|
||||||
const sinkInputs = getClientSinkInputs(pai)({ pulse: this.props });
|
|
||||||
this.toggleAllMute(sinkInputs);
|
|
||||||
} else if (pai.type === 'module') {
|
|
||||||
const sinkInputs = getModuleSinkInputs(pai)({ pulse: this.props });
|
|
||||||
this.toggleAllMute(sinkInputs);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -616,85 +737,118 @@ class Graph extends React.Component {
|
||||||
this.props.setSinkMute(pai.index, muted);
|
this.props.setSinkMute(pai.index, muted);
|
||||||
} else if (pai.type === 'source') {
|
} else if (pai.type === 'source') {
|
||||||
this.props.setSourceMute(pai.index, muted);
|
this.props.setSourceMute(pai.index, muted);
|
||||||
|
} else if (pai.type === 'client') {
|
||||||
|
const sinkInputs = getClientSinkInputs(pai)({ pulse: this.props });
|
||||||
|
this.toggleAllMute(sinkInputs);
|
||||||
|
} else if (pai.type === 'module') {
|
||||||
|
const sinkInputs = getModuleSinkInputs(pai)({ pulse: this.props });
|
||||||
|
this.toggleAllMute(sinkInputs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
this.graphViewElement.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
deselect() {
|
||||||
|
this.setState({ selected: null });
|
||||||
|
}
|
||||||
|
|
||||||
|
hotKeyMute() {
|
||||||
|
if (!this.state.selected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pai = dgoToPai.get(this.state.selected);
|
||||||
|
|
||||||
|
if (!pai) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.toggleMute(pai);
|
||||||
|
}
|
||||||
|
|
||||||
|
_findNextObjectForSelection(object, direction) {
|
||||||
|
const { type } = object || { type: 'client' };
|
||||||
|
const predicate = selectionObjectTypes.toPulsePredicate(type);
|
||||||
|
const candidates = compose(
|
||||||
|
sortBy(prop('index')),
|
||||||
|
filter(predicate),
|
||||||
|
)(this.state.nodes.concat(this.state.edges));
|
||||||
|
return (direction === 'up' ? leftOf : rightOf)(object, candidates);
|
||||||
|
}
|
||||||
|
|
||||||
|
hotKeyFocusDown() {
|
||||||
|
const selected = this._findNextObjectForSelection(this.state.selected, 'down');
|
||||||
|
this.setState({ selected });
|
||||||
|
}
|
||||||
|
|
||||||
|
hotKeyFocusUp() {
|
||||||
|
const selected = this._findNextObjectForSelection(this.state.selected, 'up');
|
||||||
|
this.setState({ selected });
|
||||||
|
}
|
||||||
|
|
||||||
|
_findAnyObjectForSelection(types) {
|
||||||
|
let node = null;
|
||||||
|
for (const type of types) {
|
||||||
|
const predicate = selectionObjectTypes.toPulsePredicate(type);
|
||||||
|
node = find(predicate, this.state.nodes) || find(predicate, this.state.edges);
|
||||||
|
if (node) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
_focusHorizontal(direction) {
|
||||||
|
if (!this.state.selected) {
|
||||||
|
this.setState({
|
||||||
|
selected: this._findAnyObjectForSelection(direction === 'left' ? [
|
||||||
|
'sourceOutput',
|
||||||
|
'source',
|
||||||
|
] : [
|
||||||
|
'sinkInput',
|
||||||
|
'sink',
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const type0 = this.state.selected.type;
|
||||||
|
const type1 = selectionObjectTypes[direction](
|
||||||
|
selectionObjectTypes.fromPulseType(type0),
|
||||||
|
);
|
||||||
|
const type2 = selectionObjectTypes[direction](type1);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
selected: this._findAnyObjectForSelection([
|
||||||
|
type1,
|
||||||
|
type2,
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
hotKeyFocusLeft() {
|
||||||
|
this._focusHorizontal('left');
|
||||||
|
}
|
||||||
|
|
||||||
|
hotKeyFocusRight() {
|
||||||
|
this._focusHorizontal('right');
|
||||||
|
}
|
||||||
|
|
||||||
|
hotKeyVolumeDown() {
|
||||||
|
}
|
||||||
|
|
||||||
|
hotKeyVolumeUp() {
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let edges = map(paoToEdge, flatten(map(values, [
|
const { nodes, edges } = this.state;
|
||||||
this.props.objects.sinkInputs,
|
|
||||||
this.props.objects.sourceOutputs,
|
|
||||||
this.props.derivations.monitorSources,
|
|
||||||
])));
|
|
||||||
|
|
||||||
const connectedNodeKeys = new Set();
|
return r(HotKeys, {
|
||||||
edges.forEach(edge => {
|
handlers: map(f => bind(f, this), pick(keys(keyMap), this)),
|
||||||
connectedNodeKeys.add(edge.source);
|
}, r.div({
|
||||||
connectedNodeKeys.add(edge.target);
|
|
||||||
});
|
|
||||||
|
|
||||||
const filteredNodeKeys = new Set();
|
|
||||||
|
|
||||||
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')
|
|
||||||
) {
|
|
||||||
if (!connectedNodeKeys.has(node.id)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const pai = dgoToPai.get(node);
|
|
||||||
if (pai) {
|
|
||||||
if (this.props.preferences.hideMonitors &&
|
|
||||||
pai.properties.device &&
|
|
||||||
pai.properties.device.class === 'monitor'
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.preferences.hidePulseaudioApps) {
|
|
||||||
const binary = path([ 'properties', 'application', 'process', 'binary' ], pai) || '';
|
|
||||||
const name = path([ 'properties', 'application', 'name' ], pai) || '';
|
|
||||||
if (binary.startsWith('pavucontrol') ||
|
|
||||||
binary.startsWith('kmix') ||
|
|
||||||
name === 'paclient.js'
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filteredNodeKeys.add(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,
|
|
||||||
]))));
|
|
||||||
|
|
||||||
edges = filter(edge => {
|
|
||||||
if (this.props.preferences.hideMonitorSourceEdges && edge.type === 'monitorSource') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return filteredNodeKeys.has(edge.source) && filteredNodeKeys.has(edge.target);
|
|
||||||
}, edges);
|
|
||||||
|
|
||||||
nodes.forEach(node => {
|
|
||||||
const pai = getPaiByTypeAndIndex(node.type, node.index)({ pulse: this.props });
|
|
||||||
dgoToPai.set(node, pai);
|
|
||||||
});
|
|
||||||
|
|
||||||
edges.forEach(edge => {
|
|
||||||
const pai = getPaiByTypeAndIndex(edge.type, edge.index)({ pulse: this.props });
|
|
||||||
dgoToPai.set(edge, pai);
|
|
||||||
});
|
|
||||||
|
|
||||||
return r.div({
|
|
||||||
id: 'graph',
|
id: 'graph',
|
||||||
style: {},
|
|
||||||
}, r(GraphView, {
|
}, r(GraphView, {
|
||||||
nodeKey: 'id',
|
nodeKey: 'id',
|
||||||
edgeKey: 'id',
|
edgeKey: 'id',
|
||||||
|
@ -704,7 +858,9 @@ class Graph extends React.Component {
|
||||||
|
|
||||||
selected: this.state.selected,
|
selected: this.state.selected,
|
||||||
|
|
||||||
...graphConfig,
|
nodeTypes: {},
|
||||||
|
nodeSubtypes: {},
|
||||||
|
edgeTypes: {},
|
||||||
|
|
||||||
onSelectNode: this.onSelectNode,
|
onSelectNode: this.onSelectNode,
|
||||||
onCreateNode: this.onCreateNode,
|
onCreateNode: this.onCreateNode,
|
||||||
|
@ -733,7 +889,7 @@ class Graph extends React.Component {
|
||||||
|
|
||||||
renderEdge,
|
renderEdge,
|
||||||
renderEdgeText: renderEdgeText(this.props),
|
renderEdgeText: renderEdgeText(this.props),
|
||||||
}));
|
})));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -751,4 +907,6 @@ module.exports = connect(
|
||||||
preferences: state.preferences,
|
preferences: state.preferences,
|
||||||
}),
|
}),
|
||||||
dispatch => bindActionCreators(merge(pulseActions, iconsActions), dispatch),
|
dispatch => bindActionCreators(merge(pulseActions, iconsActions), dispatch),
|
||||||
|
null,
|
||||||
|
{ withRef: true },
|
||||||
)(Graph);
|
)(Graph);
|
||||||
|
|
|
@ -70,7 +70,7 @@ class GraphView extends React.Component {
|
||||||
selected: null,
|
selected: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.graph = React.createRef();
|
this.graphViewRef = this.props.graphViewRef || React.createRef();
|
||||||
|
|
||||||
Object.assign(this, {
|
Object.assign(this, {
|
||||||
onSwapEdge: this.onSwapEdge.bind(this),
|
onSwapEdge: this.onSwapEdge.bind(this),
|
||||||
|
@ -144,7 +144,7 @@ class GraphView extends React.Component {
|
||||||
const createdEdgeId = `edge-${sourceNode[nodeKey]}-${targetNode[nodeKey]}-container`;
|
const createdEdgeId = `edge-${sourceNode[nodeKey]}-${targetNode[nodeKey]}-container`;
|
||||||
const createdEdge = document.getElementById(createdEdgeId);
|
const createdEdge = document.getElementById(createdEdgeId);
|
||||||
createdEdge.remove();
|
createdEdge.remove();
|
||||||
this.graph.current.forceUpdate();
|
this.graphViewRef.current.forceUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
onNodeMove(position, nodeId, shiftKey) {
|
onNodeMove(position, nodeId, shiftKey) {
|
||||||
|
@ -153,7 +153,7 @@ class GraphView extends React.Component {
|
||||||
if (satelliteNodes) {
|
if (satelliteNodes) {
|
||||||
this.constructor.repositionSatellites(position, satelliteNodes);
|
this.constructor.repositionSatellites(position, satelliteNodes);
|
||||||
satelliteNodes.forEach(satelliteNode => {
|
satelliteNodes.forEach(satelliteNode => {
|
||||||
this.graph.current.handleNodeMove(satelliteNode, satelliteNode[nodeKey], shiftKey);
|
this.graphViewRef.current.handleNodeMove(satelliteNode, satelliteNode[nodeKey], shiftKey);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -220,7 +220,7 @@ class GraphView extends React.Component {
|
||||||
|
|
||||||
selected,
|
selected,
|
||||||
|
|
||||||
ref: this.graph,
|
ref: this.graphViewRef,
|
||||||
|
|
||||||
nodes,
|
nodes,
|
||||||
edges,
|
edges,
|
||||||
|
|
75
components/hot-keys/index.js
Normal file
75
components/hot-keys/index.js
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
|
||||||
|
const {
|
||||||
|
keys,
|
||||||
|
pick,
|
||||||
|
map,
|
||||||
|
bind,
|
||||||
|
} = require('ramda');
|
||||||
|
|
||||||
|
const React = require('react');
|
||||||
|
|
||||||
|
const r = require('r-dom');
|
||||||
|
|
||||||
|
const { HotKeys } = require('react-hotkeys');
|
||||||
|
|
||||||
|
const keyMap = {
|
||||||
|
hotKeyEscape: 'escape',
|
||||||
|
|
||||||
|
hotKeyFocusCards: 'c',
|
||||||
|
hotKeyFocusGraph: 'g',
|
||||||
|
hotKeyFocusPreferences: 'p',
|
||||||
|
|
||||||
|
hotKeyFocusDown: [ 'j', 'down' ],
|
||||||
|
hotKeyFocusUp: [ 'k', 'up' ],
|
||||||
|
hotKeyFocusLeft: [ 'h', 'left' ],
|
||||||
|
hotKeyFocusRight: [ 'l', 'right' ],
|
||||||
|
|
||||||
|
hotKeyMute: 'm',
|
||||||
|
};
|
||||||
|
|
||||||
|
class MyHotKeys extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.graphRef = React.createRef();
|
||||||
|
this.cardsRef = React.createRef();
|
||||||
|
this.preferencesRef = React.createRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
hotKeyFocusCards() {
|
||||||
|
this.cardsRef.current.getWrappedInstance().toggle();
|
||||||
|
this.preferencesRef.current.getWrappedInstance().close();
|
||||||
|
}
|
||||||
|
|
||||||
|
hotKeyFocusGraph() {
|
||||||
|
this.cardsRef.current.getWrappedInstance().close();
|
||||||
|
this.preferencesRef.current.getWrappedInstance().close();
|
||||||
|
this.graphRef.current.getWrappedInstance().focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
hotKeyFocusPreferences() {
|
||||||
|
this.preferencesRef.current.getWrappedInstance().toggle();
|
||||||
|
this.cardsRef.current.getWrappedInstance().close();
|
||||||
|
}
|
||||||
|
|
||||||
|
hotKeyEscape() {
|
||||||
|
this.hotKeyFocusGraph();
|
||||||
|
this.graphRef.current.getWrappedInstance().deselect();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return r(HotKeys, {
|
||||||
|
keyMap,
|
||||||
|
handlers: map(f => bind(f, this), pick(keys(keyMap), this)),
|
||||||
|
}, this.props.children({
|
||||||
|
graphRef: this.graphRef,
|
||||||
|
cardsRef: this.cardsRef,
|
||||||
|
preferencesRef: this.preferencesRef,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
HotKeys: MyHotKeys,
|
||||||
|
keyMap,
|
||||||
|
};
|
|
@ -4,148 +4,166 @@ const {
|
||||||
defaultTo,
|
defaultTo,
|
||||||
} = require('ramda');
|
} = require('ramda');
|
||||||
|
|
||||||
|
const React = require('react');
|
||||||
|
|
||||||
const r = require('r-dom');
|
const r = require('r-dom');
|
||||||
|
|
||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
const { bindActionCreators } = require('redux');
|
const { bindActionCreators } = require('redux');
|
||||||
|
|
||||||
const { withStateHandlers } = require('recompose');
|
|
||||||
|
|
||||||
const { preferences: preferencesActions } = require('../../actions');
|
const { preferences: preferencesActions } = require('../../actions');
|
||||||
|
|
||||||
const Button = require('../button');
|
const Button = require('../button');
|
||||||
const Checkbox = require('../checkbox');
|
const Checkbox = require('../checkbox');
|
||||||
const NumberInput = require('../number-input');
|
const NumberInput = require('../number-input');
|
||||||
|
|
||||||
const Preferences = withStateHandlers(
|
class Preferences extends React.Component {
|
||||||
{
|
constructor(props) {
|
||||||
open: false,
|
super(props);
|
||||||
},
|
|
||||||
{
|
|
||||||
toggle: ({ open }) => () => ({ open: !open }),
|
|
||||||
},
|
|
||||||
)(({ open, toggle, ...props }) => r.div({
|
|
||||||
classSet: {
|
|
||||||
panel: true,
|
|
||||||
preferences: true,
|
|
||||||
open,
|
|
||||||
},
|
|
||||||
}, open ? [
|
|
||||||
r.div([
|
|
||||||
r(Button, {
|
|
||||||
style: { width: '100%' },
|
|
||||||
autoFocus: true,
|
|
||||||
onClick: toggle,
|
|
||||||
}, 'Close'),
|
|
||||||
]),
|
|
||||||
|
|
||||||
r.hr(),
|
this.state = {
|
||||||
|
open: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
r.div([
|
toggle() {
|
||||||
r(Checkbox, {
|
this.setState({ open: !this.state.open });
|
||||||
checked: props.preferences.hideDisconnectedClients,
|
}
|
||||||
onChange: () => props.actions.toggle('hideDisconnectedClients'),
|
|
||||||
}, 'Hide disconnected clients'),
|
|
||||||
]),
|
|
||||||
|
|
||||||
r.div([
|
close() {
|
||||||
r(Checkbox, {
|
this.setState({ open: false });
|
||||||
checked: props.preferences.hideDisconnectedModules,
|
}
|
||||||
onChange: () => props.actions.toggle('hideDisconnectedModules'),
|
|
||||||
}, 'Hide disconnected modules'),
|
|
||||||
]),
|
|
||||||
|
|
||||||
r.div([
|
render() {
|
||||||
r(Checkbox, {
|
const { open } = this.state;
|
||||||
checked: props.preferences.hideDisconnectedSource,
|
const toggle = this.toggle.bind(this);
|
||||||
onChange: () => props.actions.toggle('hideDisconnectedSource'),
|
|
||||||
}, 'Hide disconnected source'),
|
|
||||||
]),
|
|
||||||
|
|
||||||
r.div([
|
return r.div({
|
||||||
r(Checkbox, {
|
classSet: {
|
||||||
checked: props.preferences.hideDisconnectedSinks,
|
panel: true,
|
||||||
onChange: () => props.actions.toggle('hideDisconnectedSinks'),
|
preferences: true,
|
||||||
}, 'Hide disconnected sinks'),
|
open,
|
||||||
]),
|
|
||||||
|
|
||||||
r.hr(),
|
|
||||||
|
|
||||||
r.div([
|
|
||||||
r(Checkbox, {
|
|
||||||
checked: props.preferences.hideMonitorSourceEdges,
|
|
||||||
onChange: () => props.actions.toggle('hideMonitorSourceEdges'),
|
|
||||||
}, 'Hide monitor source edges'),
|
|
||||||
]),
|
|
||||||
|
|
||||||
r.div([
|
|
||||||
r(Checkbox, {
|
|
||||||
checked: props.preferences.hideMonitors,
|
|
||||||
onChange: () => props.actions.toggle('hideMonitors'),
|
|
||||||
}, 'Hide monitors'),
|
|
||||||
]),
|
|
||||||
|
|
||||||
r.div([
|
|
||||||
r(Checkbox, {
|
|
||||||
checked: props.preferences.hidePulseaudioApps,
|
|
||||||
onChange: () => props.actions.toggle('hidePulseaudioApps'),
|
|
||||||
}, 'Hide pulseaudio applications'),
|
|
||||||
]),
|
|
||||||
|
|
||||||
r.hr(),
|
|
||||||
|
|
||||||
r.div([
|
|
||||||
r(Checkbox, {
|
|
||||||
checked: props.preferences.hideVolumeThumbnails,
|
|
||||||
onChange: () => props.actions.toggle('hideVolumeThumbnails'),
|
|
||||||
}, 'Hide volume thumbnails'),
|
|
||||||
]),
|
|
||||||
|
|
||||||
r.div([
|
|
||||||
r(Checkbox, {
|
|
||||||
checked: props.preferences.lockChannelsTogether,
|
|
||||||
onChange: () => props.actions.toggle('lockChannelsTogether'),
|
|
||||||
}, 'Lock channels together'),
|
|
||||||
]),
|
|
||||||
|
|
||||||
r.div([
|
|
||||||
r(NumberInput, {
|
|
||||||
type: 'number',
|
|
||||||
value: defaultTo(150, Math.round(props.preferences.maxVolume * 100)),
|
|
||||||
onChange: e => {
|
|
||||||
const v = defaultTo(150, Math.max(0, parseInt(e.target.value, 10)));
|
|
||||||
props.actions.set({ maxVolume: v / 100 });
|
|
||||||
},
|
},
|
||||||
}, 'Maximum volume: '),
|
}, open ? [
|
||||||
]),
|
r.div([
|
||||||
|
r(Button, {
|
||||||
|
style: { width: '100%' },
|
||||||
|
autoFocus: true,
|
||||||
|
onClick: toggle,
|
||||||
|
}, 'Close'),
|
||||||
|
]),
|
||||||
|
|
||||||
r.hr(),
|
r.hr(),
|
||||||
|
|
||||||
r.div([
|
r.div([
|
||||||
r(Checkbox, {
|
r(Checkbox, {
|
||||||
checked: props.preferences.showDebugInfo,
|
checked: this.props.preferences.hideDisconnectedClients,
|
||||||
onChange: () => props.actions.toggle('showDebugInfo'),
|
onChange: () => this.props.actions.toggle('hideDisconnectedClients'),
|
||||||
}, 'Show debug info'),
|
}, 'Hide disconnected clients'),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
r.hr(),
|
r.div([
|
||||||
|
r(Checkbox, {
|
||||||
|
checked: this.props.preferences.hideDisconnectedModules,
|
||||||
|
onChange: () => this.props.actions.toggle('hideDisconnectedModules'),
|
||||||
|
}, 'Hide disconnected modules'),
|
||||||
|
]),
|
||||||
|
|
||||||
r.div([
|
r.div([
|
||||||
r(Button, {
|
r(Checkbox, {
|
||||||
style: { width: '100%' },
|
checked: this.props.preferences.hideDisconnectedSource,
|
||||||
onClick: props.actions.resetDefaults,
|
onChange: () => this.props.actions.toggle('hideDisconnectedSource'),
|
||||||
}, 'Reset to defaults'),
|
}, 'Hide disconnected source'),
|
||||||
]),
|
]),
|
||||||
] : [
|
|
||||||
r(Button, {
|
r.div([
|
||||||
autoFocus: true,
|
r(Checkbox, {
|
||||||
onClick: toggle,
|
checked: this.props.preferences.hideDisconnectedSinks,
|
||||||
}, 'Preferences'),
|
onChange: () => this.props.actions.toggle('hideDisconnectedSinks'),
|
||||||
]));
|
}, 'Hide disconnected sinks'),
|
||||||
|
]),
|
||||||
|
|
||||||
|
r.hr(),
|
||||||
|
|
||||||
|
r.div([
|
||||||
|
r(Checkbox, {
|
||||||
|
checked: this.props.preferences.hideMonitorSourceEdges,
|
||||||
|
onChange: () => this.props.actions.toggle('hideMonitorSourceEdges'),
|
||||||
|
}, 'Hide monitor source edges'),
|
||||||
|
]),
|
||||||
|
|
||||||
|
r.div([
|
||||||
|
r(Checkbox, {
|
||||||
|
checked: this.props.preferences.hideMonitors,
|
||||||
|
onChange: () => this.props.actions.toggle('hideMonitors'),
|
||||||
|
}, 'Hide monitors'),
|
||||||
|
]),
|
||||||
|
|
||||||
|
r.div([
|
||||||
|
r(Checkbox, {
|
||||||
|
checked: this.props.preferences.hidePulseaudioApps,
|
||||||
|
onChange: () => this.props.actions.toggle('hidePulseaudioApps'),
|
||||||
|
}, 'Hide pulseaudio applications'),
|
||||||
|
]),
|
||||||
|
|
||||||
|
r.hr(),
|
||||||
|
|
||||||
|
r.div([
|
||||||
|
r(Checkbox, {
|
||||||
|
checked: this.props.preferences.hideVolumeThumbnails,
|
||||||
|
onChange: () => this.props.actions.toggle('hideVolumeThumbnails'),
|
||||||
|
}, 'Hide volume thumbnails'),
|
||||||
|
]),
|
||||||
|
|
||||||
|
r.div([
|
||||||
|
r(Checkbox, {
|
||||||
|
checked: this.props.preferences.lockChannelsTogether,
|
||||||
|
onChange: () => this.props.actions.toggle('lockChannelsTogether'),
|
||||||
|
}, 'Lock channels together'),
|
||||||
|
]),
|
||||||
|
|
||||||
|
r.div([
|
||||||
|
r(NumberInput, {
|
||||||
|
type: 'number',
|
||||||
|
value: defaultTo(150, Math.round(this.props.preferences.maxVolume * 100)),
|
||||||
|
onChange: e => {
|
||||||
|
const v = defaultTo(150, Math.max(0, parseInt(e.target.value, 10)));
|
||||||
|
this.props.actions.set({ maxVolume: v / 100 });
|
||||||
|
},
|
||||||
|
}, 'Maximum volume: '),
|
||||||
|
]),
|
||||||
|
|
||||||
|
r.hr(),
|
||||||
|
|
||||||
|
r.div([
|
||||||
|
r(Checkbox, {
|
||||||
|
checked: this.props.preferences.showDebugInfo,
|
||||||
|
onChange: () => this.props.actions.toggle('showDebugInfo'),
|
||||||
|
}, 'Show debug info'),
|
||||||
|
]),
|
||||||
|
|
||||||
|
r.hr(),
|
||||||
|
|
||||||
|
r.div([
|
||||||
|
r(Button, {
|
||||||
|
style: { width: '100%' },
|
||||||
|
onClick: this.props.actions.resetDefaults,
|
||||||
|
}, 'Reset to defaults'),
|
||||||
|
]),
|
||||||
|
] : [
|
||||||
|
r(Button, {
|
||||||
|
autoFocus: true,
|
||||||
|
onClick: toggle,
|
||||||
|
}, 'Preferences'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = connect(
|
module.exports = connect(
|
||||||
state => pick([ 'preferences' ], state),
|
state => pick([ 'preferences' ], state),
|
||||||
dispatch => ({
|
dispatch => ({
|
||||||
actions: bindActionCreators(preferencesActions, dispatch),
|
actions: bindActionCreators(preferencesActions, dispatch),
|
||||||
}),
|
}),
|
||||||
|
null,
|
||||||
|
{ withRef: true },
|
||||||
)(Preferences);
|
)(Preferences);
|
||||||
|
|
|
@ -9,6 +9,10 @@ div {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div[tabindex="-1"]:focus {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
background: var(--themeBgColor);
|
background: var(--themeBgColor);
|
||||||
color: var(--themeTextColor);
|
color: var(--themeTextColor);
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
"react": "^16.6.0",
|
"react": "^16.6.0",
|
||||||
"react-digraph": "^5.1.3",
|
"react-digraph": "^5.1.3",
|
||||||
"react-dom": "^16.6.0",
|
"react-dom": "^16.6.0",
|
||||||
|
"react-hotkeys": "^1.1.4",
|
||||||
"react-redux": "^5.1.0",
|
"react-redux": "^5.1.0",
|
||||||
"recompose": "^0.30.0",
|
"recompose": "^0.30.0",
|
||||||
"redux": "^4.0.1",
|
"redux": "^4.0.1",
|
||||||
|
|
11
renderer.js
11
renderer.js
|
@ -1,7 +1,5 @@
|
||||||
/* 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');
|
||||||
|
@ -13,15 +11,16 @@ const createStore = require('./store');
|
||||||
const Graph = require('./components/graph');
|
const Graph = require('./components/graph');
|
||||||
const Cards = require('./components/cards');
|
const Cards = require('./components/cards');
|
||||||
const Preferences = require('./components/preferences');
|
const Preferences = require('./components/preferences');
|
||||||
|
const { HotKeys } = require('./components/hot-keys');
|
||||||
|
|
||||||
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(HotKeys, {}, ({ graphRef, cardsRef, preferencesRef }) => [
|
||||||
r(Graph),
|
r(Graph, { ref: graphRef }),
|
||||||
r(Cards),
|
r(Cards, { ref: cardsRef }),
|
||||||
r(Preferences),
|
r(Preferences, { ref: preferencesRef }),
|
||||||
]));
|
]));
|
||||||
|
|
||||||
Object.entries(theme.colors).forEach(([ key, value ]) => {
|
Object.entries(theme.colors).forEach(([ key, value ]) => {
|
||||||
|
|
28
yarn.lock
28
yarn.lock
|
@ -3921,11 +3921,21 @@ lodash.get@^4.4.2:
|
||||||
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
|
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
|
||||||
integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
|
integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
|
||||||
|
|
||||||
|
lodash.isboolean@^3.0.3:
|
||||||
|
version "3.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
|
||||||
|
integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=
|
||||||
|
|
||||||
lodash.isequal@^4.5.0:
|
lodash.isequal@^4.5.0:
|
||||||
version "4.5.0"
|
version "4.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
|
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
|
||||||
integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
|
integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
|
||||||
|
|
||||||
|
lodash.isobject@^3.0.2:
|
||||||
|
version "3.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d"
|
||||||
|
integrity sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0=
|
||||||
|
|
||||||
lodash.kebabcase@^4.0.1:
|
lodash.kebabcase@^4.0.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36"
|
resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36"
|
||||||
|
@ -4251,6 +4261,11 @@ moment@2.x.x:
|
||||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"
|
resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"
|
||||||
integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=
|
integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=
|
||||||
|
|
||||||
|
mousetrap@^1.5.2:
|
||||||
|
version "1.6.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.2.tgz#caadd9cf886db0986fb2fee59a82f6bd37527587"
|
||||||
|
integrity sha512-jDjhi7wlHwdO6q6DS7YRmSHcuI+RVxadBkLt3KHrhd3C2b+w5pKefg3oj5beTcHZyVFA9Aksf+yEE1y5jxUjVA==
|
||||||
|
|
||||||
ms@2.0.0:
|
ms@2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||||
|
@ -4900,7 +4915,7 @@ promise@^7.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
asap "~2.0.3"
|
asap "~2.0.3"
|
||||||
|
|
||||||
prop-types@^15.6.1, prop-types@^15.6.2:
|
prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2:
|
||||||
version "15.6.2"
|
version "15.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
|
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
|
||||||
integrity sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==
|
integrity sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==
|
||||||
|
@ -5032,6 +5047,17 @@ react-dom@^16.6.0:
|
||||||
prop-types "^15.6.2"
|
prop-types "^15.6.2"
|
||||||
scheduler "^0.10.0"
|
scheduler "^0.10.0"
|
||||||
|
|
||||||
|
react-hotkeys@^1.1.4:
|
||||||
|
version "1.1.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-hotkeys/-/react-hotkeys-1.1.4.tgz#a0712aa2e0c03a759fd7885808598497a4dace72"
|
||||||
|
integrity sha1-oHEqouDAOnWf14hYCFmEl6TaznI=
|
||||||
|
dependencies:
|
||||||
|
lodash.isboolean "^3.0.3"
|
||||||
|
lodash.isequal "^4.5.0"
|
||||||
|
lodash.isobject "^3.0.2"
|
||||||
|
mousetrap "^1.5.2"
|
||||||
|
prop-types "^15.6.0"
|
||||||
|
|
||||||
react-icon-base@2.1.0:
|
react-icon-base@2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-icon-base/-/react-icon-base-2.1.0.tgz#a196e33fdf1e7aaa1fda3aefbb68bdad9e82a79d"
|
resolved "https://registry.yarnpkg.com/react-icon-base/-/react-icon-base-2.1.0.tgz#a196e33fdf1e7aaa1fda3aefbb68bdad9e82a79d"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user