Add default sinks/sources, fix server info updates

This commit is contained in:
futpib 2018-11-19 18:11:17 +03:00
parent 1185e26329
commit 0916d0eb44
10 changed files with 199 additions and 55 deletions

View File

@ -8,6 +8,7 @@ const { iconThemeNames } = require('../utils/theme');
const fallbacks = new Map(Object.entries({
'audio-card-pci': 'audio-card',
'audio-card-usb': 'audio-card',
starred: 'starred-symbolic',
}));
const cache = new Map();

View File

@ -14,6 +14,8 @@ module.exports = createActionCreators({
INFO: null,
SERVER_INFO: null,
MOVE_SINK_INPUT: (sinkInputIndex, destSinkIndex) => ({ sinkInputIndex, destSinkIndex }),
MOVE_SOURCE_OUTPUT: (sourceOutputIndex, destSourceIndex) => ({ sourceOutputIndex, destSourceIndex }),
@ -41,5 +43,8 @@ module.exports = createActionCreators({
SET_SOURCE_MUTE: (index, muted) => ({ index, muted }),
SET_SINK_INPUT_MUTE_BY_INDEX: (index, muted) => ({ index, muted }),
SET_SOURCE_OUTPUT_MUTE_BY_INDEX: (index, muted) => ({ index, muted }),
SET_DEFAULT_SINK_BY_NAME: name => ({ name }),
SET_DEFAULT_SOURCE_BY_NAME: name => ({ name }),
},
});

View File

@ -15,6 +15,7 @@ const {
max,
merge,
min,
omit,
path,
pick,
prop,
@ -419,6 +420,20 @@ const VolumeControls = ({ pai, state }) => {
]);
};
const Icon = ({ state, name, ...props }) => {
const src = state.icons[name];
if (!src) {
return r(React.Fragment);
}
return r.img({
className: 'node-name-icon',
src,
...props,
});
};
const DebugText = ({ dgo, pai, state }) => r.div({
style: {
fontSize: '50%',
@ -431,8 +446,19 @@ const DebugText = ({ dgo, pai, state }) => r.div({
const SinkText = ({ dgo, pai, state, selected }) => r.div([
r.div({
className: 'node-name',
}, [
state.serverInfo.defaultSinkName === pai.name && r(React.Fragment, [
r(Icon, {
state,
name: 'starred',
title: 'Default sink',
}),
' ',
]),
r.span({
title: pai.name,
}, pai.description),
]),
!selected && r(VolumeThumbnail, { pai, state }),
selected && r(VolumeControls, { pai, state }),
r(DebugText, { dgo, pai, state }),
@ -441,8 +467,19 @@ const SinkText = ({ dgo, pai, state, selected }) => r.div([
const SourceText = ({ dgo, pai, state, selected }) => r.div([
r.div({
className: 'node-name',
}, [
state.serverInfo.defaultSourceName === pai.name && r(React.Fragment, [
r(Icon, {
state,
name: 'starred',
title: 'Default source',
}),
' ',
]),
r.span({
title: pai.name,
}, pai.description),
]),
!selected && r(VolumeThumbnail, { pai, state }),
selected && r(VolumeControls, { pai, state }),
r(DebugText, { dgo, pai, state }),
@ -527,6 +564,14 @@ class GraphContextMenu extends React.PureComponent {
return r(PopupMenu, {
onClose: this.props.onClose,
}, [
this.props.canSetAsDefault() && r(React.Fragment, [
r(MenuItem, {
label: 'Set as default',
onClick: this.props.onSetAsDefault,
}),
r(MenuItem.Separator),
]),
r(MenuItem, {
label: 'Delete',
onClick: this.props.onDelete,
@ -560,8 +605,12 @@ class Graph extends React.Component {
onDeleteEdge: this.onDeleteEdge.bind(this),
onEdgeMouseDown: this.onEdgeMouseDown.bind(this),
onContextMenuDelete: this.onContextMenuDelete.bind(this),
onContextMenuClose: this.onContextMenuClose.bind(this),
canContextMenuSetAsDefault: this.canContextMenuSetAsDefault.bind(this),
onContextMenuSetAsDefault: this.onContextMenuSetAsDefault.bind(this),
onContextMenuDelete: this.onContextMenuDelete.bind(this),
});
}
@ -674,6 +723,7 @@ class Graph extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
return !(
(nextProps.serverInfo === this.props.serverInfo) &&
(nextProps.objects === this.props.objects) &&
(nextProps.infos === this.props.infos) &&
(nextProps.preferences === this.props.preferences) &&
@ -685,7 +735,7 @@ class Graph extends React.Component {
}
componentDidMount() {
this.getIconPath('audio-volume-muted');
this.getIconPath('starred');
this.graphViewElement = document.querySelector('#graph .view-wrapper');
this.graphViewElement.setAttribute('tabindex', '-1');
@ -873,6 +923,41 @@ class Graph extends React.Component {
});
}
canContextMenuSetAsDefault() {
const { contexted } = this.state;
const pai = dgoToPai.get(contexted);
if (pai && pai.type === 'sink' && pai.name !== this.props.serverInfo.defaultSinkName) {
return true;
}
if (pai && pai.type === 'source' && pai.name !== this.props.serverInfo.defaultSourceName) {
return true;
}
return false;
}
setAsDefault(data) {
const pai = dgoToPai.get(data);
if (pai.type === 'sink') {
this.props.setDefaultSinkByName(pai.name);
}
if (pai.type === 'source') {
this.props.setDefaultSourceByName(pai.name);
}
}
onContextMenuSetAsDefault() {
this.setAsDefault(this.state.contexted);
}
hotKeySetAsDefault() {
this.setAsDefault(this.state.selected);
}
focus() {
this.graphViewElement.focus();
}
@ -1176,6 +1261,10 @@ class Graph extends React.Component {
this.state.contexted && r(GraphContextMenu, {
onClose: this.onContextMenuClose,
canSetAsDefault: this.canContextMenuSetAsDefault,
onSetAsDefault: this.onContextMenuSetAsDefault,
onDelete: this.onContextMenuDelete,
}),
]));
@ -1184,6 +1273,8 @@ class Graph extends React.Component {
module.exports = connect(
state => ({
serverInfo: state.pulse.serverInfo,
objects: state.pulse.objects,
infos: state.pulse.infos,
@ -1195,7 +1286,9 @@ module.exports = connect(
preferences: state.preferences,
}),
dispatch => bindActionCreators(merge(pulseActions, iconsActions), dispatch),
dispatch => bindActionCreators(omit([
'serverInfo',
], merge(pulseActions, iconsActions)), dispatch),
null,
{ withRef: true },
)(Graph);

View File

@ -31,6 +31,8 @@ const keyMap = {
hotKeyMute: 'space',
hotKeyShiftMute: 'shift+space',
hotKeySetAsDefault: 'f',
};
class MyHotKeys extends React.Component {

View File

@ -255,10 +255,18 @@ div[tabindex="-1"]:focus {
}
.node-name {
pointer-events: initial;
user-select: none;
overflow: hidden;
text-overflow: ellipsis;
}
.node-name-icon {
height: 1em;
vertical-align: text-top;
}
.volume-thumbnail-ruler-line {
stroke-width: 2px;
stroke: var(--borders);

View File

@ -21,8 +21,8 @@
]
},
"dependencies": {
"@futpib/paclient": "^0.0.7",
"@futpib/react-electron-menu": "^0.3.0",
"@futpib/paclient": "^0.0.8",
"@futpib/react-electron-menu": "^0.3.1",
"bluebird": "^3.5.3",
"camelcase": "^5.0.0",
"d3": "^5.7.0",

View File

@ -21,6 +21,8 @@ const { things } = require('../constants/pulse');
const initialState = {
state: 'closed',
serverInfo: {},
objects: fromPairs(map(({ key }) => [ key, {} ], things)),
infos: fromPairs(map(({ key }) => [ key, {} ], things)),
@ -33,6 +35,15 @@ const reducer = combineReducers({
[pulse.close]: always('closed'),
}, initialState.state),
serverInfo: handleActions({
[pulse.serverInfo]: (state, { payload }) => {
return equals(state, payload) ?
state :
payload;
},
[pulse.close]: always(initialState.serverInfo),
}, initialState.serverInfo),
objects: combineReducers(fromPairs(map(({ key, type }) => [ key, handleActions({
[pulse.new]: (state, { payload }) => {
if (payload.type !== type) {

View File

@ -80,11 +80,12 @@ module.exports = store => {
.on('ready', () => {
store.dispatch(pulseActions.ready());
pa.subscribe('all');
getServerInfo();
things.forEach(({ method, type }) => {
pa[method]((err, infos) => {
if (err) {
throw err;
}
handleError(err);
infos.forEach(info => {
const { index } = info;
info.type = info.type || type;
@ -100,7 +101,7 @@ module.exports = store => {
})
.on('new', (type, index) => {
if (type === 'server') {
pa.end(); // Reconnect
getServerInfo();
return;
}
store.dispatch(pulseActions.new({ type, index }));
@ -108,7 +109,7 @@ module.exports = store => {
})
.on('change', (type, index) => {
if (type === 'server') {
pa.end(); // Reconnect
getServerInfo();
return;
}
store.dispatch(pulseActions.change({ type, index }));
@ -134,6 +135,16 @@ module.exports = store => {
reconnect();
const getServerInfo = () => {
pa.getServerInfo((err, info) => {
if (err) {
handleError(err);
} else {
store.dispatch(pulseActions.serverInfo(info));
}
});
};
const handleError = error => {
if (!error) {
return;
@ -228,6 +239,15 @@ module.exports = store => {
pa.setSourceOutputMuteByIndex(index, muted, handleError);
return state;
},
[pulseActions.setDefaultSinkByName]: (state, { payload: { name } }) => {
pa.setDefaultSinkByName(name, handleError);
return state;
},
[pulseActions.setDefaultSourceByName]: (state, { payload: { name } }) => {
pa.setDefaultSourceByName(name, handleError);
return state;
},
}, null);
return next => action => {

View File

@ -1,7 +1,5 @@
1 exit Terminate the daemon
1 load-module Load a module (args: name, arguments)
1 set-default-sink Set the default sink (args: index|name)
1 set-default-source Set the default source (args: index|name)
2 set-sink-port Change the port of a sink (args: index|name, port-name)
2 set-source-port Change the port of a source (args: index|name, port-name)
3 describe-module Describe a module (arg: name)

View File

@ -84,18 +84,19 @@
dependencies:
arrify "^1.0.1"
"@futpib/paclient@^0.0.7":
version "0.0.7"
resolved "https://registry.yarnpkg.com/@futpib/paclient/-/paclient-0.0.7.tgz#d8957135ba81888f5e92812d8e9e4e8e1ebf935f"
integrity sha512-fjpJaS3LHuo+51/7g3dqpZBGO2wZtnLAWYKVk5CIBsfqn3345xJaEe0HfLpBxPAdpAHRTcTz5aWXlhOWsBClHA==
"@futpib/paclient@^0.0.8":
version "0.0.8"
resolved "https://registry.yarnpkg.com/@futpib/paclient/-/paclient-0.0.8.tgz#c7530d2175798aba9ca21e3d0312bbfa3fd18a44"
integrity sha512-Uaup+EdAWKtfuos4wBlDuUWeZfj/OtTtllGpniFTElEiD+MDvryzq64t/Ibokt3a5TkVY3M2O69YZaGH2J6Gqw==
"@futpib/react-electron-menu@^0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@futpib/react-electron-menu/-/react-electron-menu-0.3.0.tgz#33a9f5bb21823805a9782daf8ba2f8df02cde8f7"
integrity sha512-RqlD74LUvDTP5gvHwUy1qGwBLvNnjPmgbDzl8+n2t9Z5WkHsCMAXnsYfEyuKxWMkivCrEYeLBwYEardtm6hRiA==
"@futpib/react-electron-menu@^0.3.1":
version "0.3.1"
resolved "https://registry.yarnpkg.com/@futpib/react-electron-menu/-/react-electron-menu-0.3.1.tgz#0ef7c97c0c8c4b1f112d62609d0411155493ad90"
integrity sha512-qhQqCJr4vLjipkLdUPCm0zkL/6sKoXVv3DMUgbIuB6sMy+mBIqpuADDQUf4A2Uz1o/4zViHhNVN4ZY/y/M3GHA==
dependencies:
react "^15.4.2"
react-test-renderer "^15.4.2"
prop-types "^15.6.2"
react "^16.6.3"
react-test-renderer "^16.6.3"
"@ladjs/time-require@^0.1.4":
version "0.1.4"
@ -1407,15 +1408,6 @@ create-error-class@^3.0.0:
dependencies:
capture-stack-trace "^1.0.0"
create-react-class@^15.6.0:
version "15.6.3"
resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.3.tgz#2d73237fb3f970ae6ebe011a9e66f46dbca80036"
integrity sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==
dependencies:
fbjs "^0.8.9"
loose-envify "^1.3.1"
object-assign "^4.1.1"
cross-spawn@^5.0.1:
version "5.1.0"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
@ -2531,7 +2523,7 @@ fast-levenshtein@~2.0.4:
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
fbjs@^0.8.1, fbjs@^0.8.9:
fbjs@^0.8.1:
version "0.8.17"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd"
integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=
@ -4944,7 +4936,7 @@ promise@^7.1.1:
dependencies:
asap "~2.0.3"
prop-types@^15.5.10, prop-types@^15.5.6, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2:
prop-types@^15.5.6, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2:
version "15.6.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
integrity sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==
@ -5111,6 +5103,11 @@ react-is@^16.3.2, react-is@^16.6.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.6.0.tgz#456645144581a6e99f6816ae2bd24ee94bdd0c01"
integrity sha512-q8U7k0Fi7oxF1HvQgyBjPwDXeMplEsArnKt2iYhuIF86+GBbgLHdAmokL3XUFjTd7Q363OSNG55FOGUdONVn1g==
react-is@^16.6.3:
version "16.6.3"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.6.3.tgz#d2d7462fcfcbe6ec0da56ad69047e47e56e7eac0"
integrity sha512-u7FDWtthB4rWibG/+mFbVd5FvdI20yde86qKGx4lVUTWmPlSWQ4QxbBIrrs+HnXGbxOUlUzTAP/VDmvCwaP2yA==
react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.2:
version "3.0.4"
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
@ -5129,13 +5126,15 @@ react-redux@^5.1.0:
react-is "^16.6.0"
react-lifecycles-compat "^3.0.0"
react-test-renderer@^15.4.2:
version "15.6.2"
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-15.6.2.tgz#d0333434fc2c438092696ca770da5ed48037efa8"
integrity sha1-0DM0NPwsQ4CSaWyncNpe1IA376g=
react-test-renderer@^16.6.3:
version "16.6.3"
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.6.3.tgz#5f3a1a7d5c3379d46f7052b848b4b72e47c89f38"
integrity sha512-B5bCer+qymrQz/wN03lT0LppbZUDRq6AMfzMKrovzkGzfO81a9T+PWQW6MzkWknbwODQH/qpJno/yFQLX5IWrQ==
dependencies:
fbjs "^0.8.9"
object-assign "^4.1.0"
object-assign "^4.1.1"
prop-types "^15.6.2"
react-is "^16.6.3"
scheduler "^0.11.2"
react-transition-group@^1.2.0:
version "1.2.1"
@ -5148,17 +5147,6 @@ react-transition-group@^1.2.0:
prop-types "^15.5.6"
warning "^3.0.0"
react@^15.4.2:
version "15.6.2"
resolved "https://registry.yarnpkg.com/react/-/react-15.6.2.tgz#dba0434ab439cfe82f108f0f511663908179aa72"
integrity sha1-26BDSrQ5z+gvEI8PURZjkIF5qnI=
dependencies:
create-react-class "^15.6.0"
fbjs "^0.8.9"
loose-envify "^1.1.0"
object-assign "^4.1.0"
prop-types "^15.5.10"
react@^16.6.0:
version "16.6.0"
resolved "https://registry.yarnpkg.com/react/-/react-16.6.0.tgz#b34761cfaf3e30f5508bc732fb4736730b7da246"
@ -5169,6 +5157,16 @@ react@^16.6.0:
prop-types "^15.6.2"
scheduler "^0.10.0"
react@^16.6.3:
version "16.6.3"
resolved "https://registry.yarnpkg.com/react/-/react-16.6.3.tgz#25d77c91911d6bbdd23db41e70fb094cc1e0871c"
integrity sha512-zCvmH2vbEolgKxtqXL2wmGCUxUyNheYn/C+PD1YAjfxHC54+MhdruyhO7QieQrYsYeTxrn93PM2y0jRH1zEExw==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.2"
scheduler "^0.11.2"
read-pkg-up@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
@ -5745,6 +5743,14 @@ scheduler@^0.10.0:
loose-envify "^1.1.0"
object-assign "^4.1.1"
scheduler@^0.11.2:
version "0.11.2"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.11.2.tgz#a8db5399d06eba5abac51b705b7151d2319d33d3"
integrity sha512-+WCP3s3wOaW4S7C1tl3TEXp4l9lJn0ZK8G3W3WKRWmw77Z2cIFUW2MiNTMHn5sCjxN+t7N43HAOOgMjyAg5hlg==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
seed-random@2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/seed-random/-/seed-random-2.2.0.tgz#2a9b19e250a817099231a5b99a4daf80b7fbed54"