Icons stub + more filters

This commit is contained in:
futpib 2018-11-09 00:51:38 +03:00
parent 851c2f1327
commit 9ed2faa58c
9 changed files with 178 additions and 24 deletions

34
actions/icons.js Normal file
View 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,
],
},
});

View File

@ -3,4 +3,5 @@ module.exports = Object.assign(
{},
require('./pulse'),
require('./preferences'),
require('./icons'),
);

View File

@ -6,6 +6,8 @@ const {
memoizeWith,
path,
filter,
forEach,
merge,
} = require('ramda');
const React = require('react');
@ -21,7 +23,10 @@ const { Edge } = require('react-digraph');
const d = require('../../utils/d');
const { pulse: pulseActions } = require('../../actions');
const {
pulse: pulseActions,
icons: iconsActions,
} = require('../../actions');
const { getPaiByTypeAndIndex } = require('../../selectors');
@ -82,6 +87,12 @@ const paiToEdge = memoize(pai => ({
type: pai.type,
}));
const getPaiIcon = memoize(pai => {
return null ||
path([ 'properties', 'application', 'icon_name' ], pai) ||
path([ 'properties', 'device', 'icon_name' ], pai);
});
const graphConfig = {
nodeTypes: {},
@ -197,44 +208,44 @@ const renderNode = (nodeRef, data, key, selected, hovered) => r({
hovered,
});
const DebugText = ({ dgo, pai, props }) => r.div({
const DebugText = ({ dgo, pai, state }) => r.div({
style: {
fontSize: '50%',
},
}, props.preferences.showDebugInfo ? [
}, state.preferences.showDebugInfo ? [
JSON.stringify(dgo, null, 2),
JSON.stringify(pai, null, 2),
] : []);
const SinkText = ({ dgo, pai, props }) => r.div([
const SinkText = ({ dgo, pai, state }) => r.div([
r.div({
title: pai.name,
}, 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({
title: pai.name,
}, 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({
title: path('properties.application.process.binary'.split('.'), pai),
}, 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({
title: pai.properties.module.description,
}, 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,
y: -s2,
}, r.div({
@ -245,6 +256,11 @@ const renderNodeText = props => dgo => r('foreignObject', {
padding: 2,
whiteSpace: 'pre',
backgroundRepeat: 'no-repeat',
backgroundSize: '60%',
backgroundPosition: 'center',
backgroundImage: (icon => icon && `url(${icon})`)(state.icons[getPaiIcon(dgoToPai.get(dgo))]),
},
}, r({
sink: SinkText,
@ -254,14 +270,13 @@ const renderNodeText = props => dgo => r('foreignObject', {
}[dgo.type] || ModuleText, {
dgo,
pai: dgoToPai.get(dgo),
props,
state,
})));
const afterRenderEdge = (id, element, edge, edgeContainer) => {
if (edge.type) {
edgeContainer.classList.add(edge.type);
}
//const edgeOverlay = edgeContainer.querySelector('.edge-overlay-path');
};
class Graph extends React.Component {
@ -272,6 +287,8 @@ class Graph extends React.Component {
selected: null,
};
this._requestedIcons = new Set();
Object.assign(this, {
onSelectNode: this.onSelectNode.bind(this),
onCreateNode: this.onCreateNode.bind(this),
@ -289,10 +306,29 @@ class Graph extends React.Component {
(nextProps.objects === this.props.objects) &&
(nextProps.infos === this.props.infos) &&
(nextProps.preferences === this.props.preferences) &&
(nextProps.icons === this.props.icons) &&
(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) {
this.setState({ selected });
}
@ -327,25 +363,52 @@ class Graph extends React.Component {
}
render() {
const edges = map(paiToEdge, flatten(map(values, [
let edges = map(paiToEdge, flatten(map(values, [
this.props.infos.sinkInputs,
this.props.infos.sourceOutputs,
])));
const connectedNodeKeys = {};
const connectedNodeKeys = new Set();
edges.forEach(edge => {
connectedNodeKeys[edge.source] = true;
connectedNodeKeys[edge.target] = true;
connectedNodeKeys.add(edge.source);
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')
) {
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;
}, map(paoToNode, flatten(map(values, [
this.props.objects.sinks,
@ -354,6 +417,10 @@ class Graph extends React.Component {
this.props.objects.modules,
]))));
edges = filter(edge => {
return filteredNodeKeys.has(edge.source) && filteredNodeKeys.has(edge.target);
}, edges);
nodes.forEach(node => {
if (node.x !== undefined) {
return;
@ -417,7 +484,9 @@ module.exports = connect(
objects: state.pulse.objects,
infos: state.pulse.infos,
icons: state.icons,
preferences: state.preferences,
}),
dispatch => bindActionCreators(pulseActions, dispatch),
dispatch => bindActionCreators(merge(pulseActions, iconsActions), dispatch),
)(Graph);

View File

@ -63,6 +63,20 @@ const Preferences = withStateHandlers(
}, '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(Checkbox, {
checked: props.preferences.showDebugInfo,

View File

@ -5,11 +5,17 @@ body {
font: -webkit-control;
}
div {
box-sizing: border-box;
}
button {
background: var(--themeBgColor);
color: var(--themeTextColor);
border: 1px solid var(--borders);
user-select: none;
padding: 8px;
}
button:hover {
@ -27,6 +33,12 @@ button:active {
top: 1px;
}
.checkbox {
user-select: none;
padding: 8px;
}
.view-wrapper .graph {
background: var(--themeBaseColor);
}
@ -95,10 +107,6 @@ button:active {
margin-bottom: 1rem;
}
.checkbox {
user-select: none;
}
.view-wrapper .graph .edge-mouse-handler {
stroke-width: 30px;
}

View File

@ -7,5 +7,7 @@ app.on('ready', () => {
const win = new BrowserWindow({
backgroundColor: theme.colors.themeBaseColor,
});
win.setAutoHideMenuBar(true);
win.setMenuBarVisibility(false);
win.loadFile('index.html');
});

19
reducers/icons.js Normal file
View 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,
};

View File

@ -3,15 +3,18 @@ const { combineReducers } = require('redux');
const { reducer: pulse, initialState: pulseInitialState } = require('./pulse');
const { reducer: preferences, initialState: preferencesInitialState } = require('./preferences');
const { reducer: icons, initialState: iconsInitialState } = require('./icons');
const initialState = {
pulse: pulseInitialState,
preferences: preferencesInitialState,
icons: iconsInitialState,
};
const reducer = combineReducers({
pulse,
preferences,
icons,
});
module.exports = {

View File

@ -12,6 +12,10 @@ const initialState = {
hideDisconnectedModules: true,
hideDisconnectedSources: false,
hideDisconnectedSinks: false,
hideMonitors: false,
hidePulseaudioApps: true,
showDebugInfo: false,
};