Icons stub + more filters
This commit is contained in:
parent
851c2f1327
commit
9ed2faa58c
34
actions/icons.js
Normal file
34
actions/icons.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
|
||||||
|
const { createActions: createActionCreators } = require('redux-actions');
|
||||||
|
|
||||||
|
// const { getIconPath } = require('../modules/get-gtk-icon');
|
||||||
|
const getIconPath = () => {
|
||||||
|
throw new Error('stub');
|
||||||
|
};
|
||||||
|
|
||||||
|
const fallbacks = new Map(Object.entries({
|
||||||
|
'audio-card-pci': 'audio-card',
|
||||||
|
'audio-card-usb': 'audio-card',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const getIconPathFallback = async (icon, size) => {
|
||||||
|
try {
|
||||||
|
return await getIconPath(icon, size);
|
||||||
|
} catch (error) {
|
||||||
|
if (error.message === 'No icon found') {
|
||||||
|
if (fallbacks.has(icon)) {
|
||||||
|
return getIconPathFallback(fallbacks.get(icon), size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = createActionCreators({
|
||||||
|
ICONS: {
|
||||||
|
GET_ICON_PATH: [
|
||||||
|
(icon, size) => getIconPathFallback(icon, size),
|
||||||
|
icon => icon,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
|
@ -3,4 +3,5 @@ module.exports = Object.assign(
|
||||||
{},
|
{},
|
||||||
require('./pulse'),
|
require('./pulse'),
|
||||||
require('./preferences'),
|
require('./preferences'),
|
||||||
|
require('./icons'),
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,6 +6,8 @@ const {
|
||||||
memoizeWith,
|
memoizeWith,
|
||||||
path,
|
path,
|
||||||
filter,
|
filter,
|
||||||
|
forEach,
|
||||||
|
merge,
|
||||||
} = require('ramda');
|
} = require('ramda');
|
||||||
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
@ -21,7 +23,10 @@ const { Edge } = require('react-digraph');
|
||||||
|
|
||||||
const d = require('../../utils/d');
|
const d = require('../../utils/d');
|
||||||
|
|
||||||
const { pulse: pulseActions } = require('../../actions');
|
const {
|
||||||
|
pulse: pulseActions,
|
||||||
|
icons: iconsActions,
|
||||||
|
} = require('../../actions');
|
||||||
|
|
||||||
const { getPaiByTypeAndIndex } = require('../../selectors');
|
const { getPaiByTypeAndIndex } = require('../../selectors');
|
||||||
|
|
||||||
|
@ -82,6 +87,12 @@ const paiToEdge = memoize(pai => ({
|
||||||
type: pai.type,
|
type: pai.type,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const getPaiIcon = memoize(pai => {
|
||||||
|
return null ||
|
||||||
|
path([ 'properties', 'application', 'icon_name' ], pai) ||
|
||||||
|
path([ 'properties', 'device', 'icon_name' ], pai);
|
||||||
|
});
|
||||||
|
|
||||||
const graphConfig = {
|
const graphConfig = {
|
||||||
nodeTypes: {},
|
nodeTypes: {},
|
||||||
|
|
||||||
|
@ -197,44 +208,44 @@ const renderNode = (nodeRef, data, key, selected, hovered) => r({
|
||||||
hovered,
|
hovered,
|
||||||
});
|
});
|
||||||
|
|
||||||
const DebugText = ({ dgo, pai, props }) => r.div({
|
const DebugText = ({ dgo, pai, state }) => r.div({
|
||||||
style: {
|
style: {
|
||||||
fontSize: '50%',
|
fontSize: '50%',
|
||||||
},
|
},
|
||||||
}, props.preferences.showDebugInfo ? [
|
}, state.preferences.showDebugInfo ? [
|
||||||
JSON.stringify(dgo, null, 2),
|
JSON.stringify(dgo, null, 2),
|
||||||
JSON.stringify(pai, null, 2),
|
JSON.stringify(pai, null, 2),
|
||||||
] : []);
|
] : []);
|
||||||
|
|
||||||
const SinkText = ({ dgo, pai, props }) => r.div([
|
const SinkText = ({ dgo, pai, state }) => r.div([
|
||||||
r.div({
|
r.div({
|
||||||
title: pai.name,
|
title: pai.name,
|
||||||
}, pai.description),
|
}, pai.description),
|
||||||
r(DebugText, { dgo, pai, props }),
|
r(DebugText, { dgo, pai, state }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const SourceText = ({ dgo, pai, props }) => r.div([
|
const SourceText = ({ dgo, pai, state }) => r.div([
|
||||||
r.div({
|
r.div({
|
||||||
title: pai.name,
|
title: pai.name,
|
||||||
}, pai.description),
|
}, pai.description),
|
||||||
r(DebugText, { dgo, pai, props }),
|
r(DebugText, { dgo, pai, state }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const ClientText = ({ dgo, pai, props }) => r.div([
|
const ClientText = ({ dgo, pai, state }) => r.div([
|
||||||
r.div({
|
r.div({
|
||||||
title: path('properties.application.process.binary'.split('.'), pai),
|
title: path('properties.application.process.binary'.split('.'), pai),
|
||||||
}, pai.name),
|
}, pai.name),
|
||||||
r(DebugText, { dgo, pai, props }),
|
r(DebugText, { dgo, pai, state }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const ModuleText = ({ dgo, pai, props }) => r.div([
|
const ModuleText = ({ dgo, pai, state }) => r.div([
|
||||||
r.div({
|
r.div({
|
||||||
title: pai.properties.module.description,
|
title: pai.properties.module.description,
|
||||||
}, pai.name),
|
}, pai.name),
|
||||||
r(DebugText, { dgo, pai, props }),
|
r(DebugText, { dgo, pai, state }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const renderNodeText = props => dgo => r('foreignObject', {
|
const renderNodeText = state => dgo => r('foreignObject', {
|
||||||
x: -s2,
|
x: -s2,
|
||||||
y: -s2,
|
y: -s2,
|
||||||
}, r.div({
|
}, r.div({
|
||||||
|
@ -245,6 +256,11 @@ const renderNodeText = props => dgo => r('foreignObject', {
|
||||||
padding: 2,
|
padding: 2,
|
||||||
|
|
||||||
whiteSpace: 'pre',
|
whiteSpace: 'pre',
|
||||||
|
|
||||||
|
backgroundRepeat: 'no-repeat',
|
||||||
|
backgroundSize: '60%',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
backgroundImage: (icon => icon && `url(${icon})`)(state.icons[getPaiIcon(dgoToPai.get(dgo))]),
|
||||||
},
|
},
|
||||||
}, r({
|
}, r({
|
||||||
sink: SinkText,
|
sink: SinkText,
|
||||||
|
@ -254,14 +270,13 @@ const renderNodeText = props => dgo => r('foreignObject', {
|
||||||
}[dgo.type] || ModuleText, {
|
}[dgo.type] || ModuleText, {
|
||||||
dgo,
|
dgo,
|
||||||
pai: dgoToPai.get(dgo),
|
pai: dgoToPai.get(dgo),
|
||||||
props,
|
state,
|
||||||
})));
|
})));
|
||||||
|
|
||||||
const afterRenderEdge = (id, element, edge, edgeContainer) => {
|
const afterRenderEdge = (id, element, edge, edgeContainer) => {
|
||||||
if (edge.type) {
|
if (edge.type) {
|
||||||
edgeContainer.classList.add(edge.type);
|
edgeContainer.classList.add(edge.type);
|
||||||
}
|
}
|
||||||
//const edgeOverlay = edgeContainer.querySelector('.edge-overlay-path');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class Graph extends React.Component {
|
class Graph extends React.Component {
|
||||||
|
@ -272,6 +287,8 @@ class Graph extends React.Component {
|
||||||
selected: null,
|
selected: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this._requestedIcons = new Set();
|
||||||
|
|
||||||
Object.assign(this, {
|
Object.assign(this, {
|
||||||
onSelectNode: this.onSelectNode.bind(this),
|
onSelectNode: this.onSelectNode.bind(this),
|
||||||
onCreateNode: this.onCreateNode.bind(this),
|
onCreateNode: this.onCreateNode.bind(this),
|
||||||
|
@ -289,10 +306,29 @@ class Graph extends React.Component {
|
||||||
(nextProps.objects === this.props.objects) &&
|
(nextProps.objects === this.props.objects) &&
|
||||||
(nextProps.infos === this.props.infos) &&
|
(nextProps.infos === this.props.infos) &&
|
||||||
(nextProps.preferences === this.props.preferences) &&
|
(nextProps.preferences === this.props.preferences) &&
|
||||||
|
(nextProps.icons === this.props.icons) &&
|
||||||
(nextState.selected === this.state.selected)
|
(nextState.selected === this.state.selected)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
forEach(pai => {
|
||||||
|
const icon = getPaiIcon(pai);
|
||||||
|
if (!icon) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this._requestedIcons.has(icon) && !this.props.icons[icon]) {
|
||||||
|
this.props.getIconPath(icon, 128);
|
||||||
|
}
|
||||||
|
this._requestedIcons.add(icon);
|
||||||
|
}, flatten(map(values, [
|
||||||
|
this.props.infos.sinks,
|
||||||
|
this.props.infos.sources,
|
||||||
|
this.props.infos.clients,
|
||||||
|
this.props.infos.modules,
|
||||||
|
])));
|
||||||
|
}
|
||||||
|
|
||||||
onSelectNode(selected) {
|
onSelectNode(selected) {
|
||||||
this.setState({ selected });
|
this.setState({ selected });
|
||||||
}
|
}
|
||||||
|
@ -327,25 +363,52 @@ class Graph extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const edges = map(paiToEdge, flatten(map(values, [
|
let edges = map(paiToEdge, flatten(map(values, [
|
||||||
this.props.infos.sinkInputs,
|
this.props.infos.sinkInputs,
|
||||||
this.props.infos.sourceOutputs,
|
this.props.infos.sourceOutputs,
|
||||||
])));
|
])));
|
||||||
|
|
||||||
const connectedNodeKeys = {};
|
const connectedNodeKeys = new Set();
|
||||||
edges.forEach(edge => {
|
edges.forEach(edge => {
|
||||||
connectedNodeKeys[edge.source] = true;
|
connectedNodeKeys.add(edge.source);
|
||||||
connectedNodeKeys[edge.target] = true;
|
connectedNodeKeys.add(edge.target);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const filteredNodeKeys = new Set();
|
||||||
|
|
||||||
const nodes = filter(node => {
|
const nodes = filter(node => {
|
||||||
if ((this.props.preferences.hideDisconnectedClients && node.type === 'client') ||
|
if ((this.props.preferences.hideDisconnectedClients && node.type === 'client') ||
|
||||||
(this.props.preferences.hideDisconnectedModules && node.type === 'module') ||
|
(this.props.preferences.hideDisconnectedModules && node.type === 'module') ||
|
||||||
(this.props.preferences.hideDisconnectedSources && node.type === 'source') ||
|
(this.props.preferences.hideDisconnectedSources && node.type === 'source') ||
|
||||||
(this.props.preferences.hideDisconnectedSinks && node.type === 'sink')
|
(this.props.preferences.hideDisconnectedSinks && node.type === 'sink')
|
||||||
) {
|
) {
|
||||||
return connectedNodeKeys[node.id];
|
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;
|
return true;
|
||||||
}, map(paoToNode, flatten(map(values, [
|
}, map(paoToNode, flatten(map(values, [
|
||||||
this.props.objects.sinks,
|
this.props.objects.sinks,
|
||||||
|
@ -354,6 +417,10 @@ class Graph extends React.Component {
|
||||||
this.props.objects.modules,
|
this.props.objects.modules,
|
||||||
]))));
|
]))));
|
||||||
|
|
||||||
|
edges = filter(edge => {
|
||||||
|
return filteredNodeKeys.has(edge.source) && filteredNodeKeys.has(edge.target);
|
||||||
|
}, edges);
|
||||||
|
|
||||||
nodes.forEach(node => {
|
nodes.forEach(node => {
|
||||||
if (node.x !== undefined) {
|
if (node.x !== undefined) {
|
||||||
return;
|
return;
|
||||||
|
@ -417,7 +484,9 @@ module.exports = connect(
|
||||||
objects: state.pulse.objects,
|
objects: state.pulse.objects,
|
||||||
infos: state.pulse.infos,
|
infos: state.pulse.infos,
|
||||||
|
|
||||||
|
icons: state.icons,
|
||||||
|
|
||||||
preferences: state.preferences,
|
preferences: state.preferences,
|
||||||
}),
|
}),
|
||||||
dispatch => bindActionCreators(pulseActions, dispatch),
|
dispatch => bindActionCreators(merge(pulseActions, iconsActions), dispatch),
|
||||||
)(Graph);
|
)(Graph);
|
||||||
|
|
|
@ -63,6 +63,20 @@ const Preferences = withStateHandlers(
|
||||||
}, 'Hide disconnected sinks'),
|
}, 'Hide disconnected sinks'),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
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.div([
|
r.div([
|
||||||
r(Checkbox, {
|
r(Checkbox, {
|
||||||
checked: props.preferences.showDebugInfo,
|
checked: props.preferences.showDebugInfo,
|
||||||
|
|
16
index.css
16
index.css
|
@ -5,11 +5,17 @@ body {
|
||||||
font: -webkit-control;
|
font: -webkit-control;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
background: var(--themeBgColor);
|
background: var(--themeBgColor);
|
||||||
color: var(--themeTextColor);
|
color: var(--themeTextColor);
|
||||||
border: 1px solid var(--borders);
|
border: 1px solid var(--borders);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button:hover {
|
||||||
|
@ -27,6 +33,12 @@ button:active {
|
||||||
top: 1px;
|
top: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.view-wrapper .graph {
|
.view-wrapper .graph {
|
||||||
background: var(--themeBaseColor);
|
background: var(--themeBaseColor);
|
||||||
}
|
}
|
||||||
|
@ -95,10 +107,6 @@ button:active {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox {
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.view-wrapper .graph .edge-mouse-handler {
|
.view-wrapper .graph .edge-mouse-handler {
|
||||||
stroke-width: 30px;
|
stroke-width: 30px;
|
||||||
}
|
}
|
||||||
|
|
2
index.js
2
index.js
|
@ -7,5 +7,7 @@ app.on('ready', () => {
|
||||||
const win = new BrowserWindow({
|
const win = new BrowserWindow({
|
||||||
backgroundColor: theme.colors.themeBaseColor,
|
backgroundColor: theme.colors.themeBaseColor,
|
||||||
});
|
});
|
||||||
|
win.setAutoHideMenuBar(true);
|
||||||
|
win.setMenuBarVisibility(false);
|
||||||
win.loadFile('index.html');
|
win.loadFile('index.html');
|
||||||
});
|
});
|
||||||
|
|
19
reducers/icons.js
Normal file
19
reducers/icons.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
|
||||||
|
const {
|
||||||
|
merge,
|
||||||
|
} = require('ramda');
|
||||||
|
|
||||||
|
const { handleActions } = require('redux-actions');
|
||||||
|
|
||||||
|
const { icons } = require('../actions');
|
||||||
|
|
||||||
|
const initialState = {};
|
||||||
|
|
||||||
|
const reducer = handleActions({
|
||||||
|
[icons.getIconPath + '_FULFILLED']: (state, { payload, meta }) => merge(state, { [meta]: payload }),
|
||||||
|
}, initialState);
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
initialState,
|
||||||
|
reducer,
|
||||||
|
};
|
|
@ -3,15 +3,18 @@ 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 { reducer: preferences, initialState: preferencesInitialState } = require('./preferences');
|
||||||
|
const { reducer: icons, initialState: iconsInitialState } = require('./icons');
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
pulse: pulseInitialState,
|
pulse: pulseInitialState,
|
||||||
preferences: preferencesInitialState,
|
preferences: preferencesInitialState,
|
||||||
|
icons: iconsInitialState,
|
||||||
};
|
};
|
||||||
|
|
||||||
const reducer = combineReducers({
|
const reducer = combineReducers({
|
||||||
pulse,
|
pulse,
|
||||||
preferences,
|
preferences,
|
||||||
|
icons,
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
|
@ -12,6 +12,10 @@ const initialState = {
|
||||||
hideDisconnectedModules: true,
|
hideDisconnectedModules: true,
|
||||||
hideDisconnectedSources: false,
|
hideDisconnectedSources: false,
|
||||||
hideDisconnectedSinks: false,
|
hideDisconnectedSinks: false,
|
||||||
|
|
||||||
|
hideMonitors: false,
|
||||||
|
hidePulseaudioApps: true,
|
||||||
|
|
||||||
showDebugInfo: false,
|
showDebugInfo: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user