From 4ab936986f57f3be9f75fd7a936a04d420f3556c Mon Sep 17 00:00:00 2001 From: futpib Date: Mon, 19 Nov 2018 23:11:01 +0300 Subject: [PATCH] Add `Connect to server` form --- components/button/index.js | 1 + components/menu/index.js | 16 +++-- components/modals/confirmation.js | 51 +++++++++++++++ components/modals/connect-to-server.js | 87 ++++++++++++++++++++++++++ components/modals/index.js | 67 +++++++------------- renderer.js | 5 +- store/pulse-middleware.js | 2 +- 7 files changed, 176 insertions(+), 53 deletions(-) create mode 100644 components/modals/confirmation.js create mode 100644 components/modals/connect-to-server.js diff --git a/components/button/index.js b/components/button/index.js index d0c420f..321eec9 100644 --- a/components/button/index.js +++ b/components/button/index.js @@ -14,6 +14,7 @@ const ref = memoizeWith(autoFocus => String(Boolean(autoFocus)), autoFocus => in const Button = props => r.button({ ref, className: 'button', + type: 'button', ...props, }, props.children); diff --git a/components/menu/index.js b/components/menu/index.js index adbc376..15f1529 100644 --- a/components/menu/index.js +++ b/components/menu/index.js @@ -11,15 +11,23 @@ const { Provider, } = require('@futpib/react-electron-menu'); -const MenuProvider = ({ children }) => r(Provider, { electron }, r(React.Fragment, {}, [ - r(WindowMenu), +const MenuProvider = ({ children, ...props }) => r(Provider, { electron }, r(React.Fragment, {}, [ + r(WindowMenu, props), ...[].concat(children), ])); -const WindowMenu = () => r(WindowMenuBase, [ +const WindowMenu = ({ openConnectToServerModal }) => r(WindowMenuBase, [ r(MenuItem, { - label: 'App', + label: 'File', }, [ + r(MenuItem, { + label: 'Connect to server...', + accelerator: 'CommandOrControl+N', + onClick: openConnectToServerModal, + }), + + r(MenuItem.Separator), + r(MenuItem, { label: 'Quit', role: 'quit', diff --git a/components/modals/confirmation.js b/components/modals/confirmation.js new file mode 100644 index 0000000..94a9096 --- /dev/null +++ b/components/modals/confirmation.js @@ -0,0 +1,51 @@ + +const r = require('r-dom'); + +const React = require('react'); + +const Modal = require('react-modal'); + +const Checkbox = require('../checkbox'); +const Button = require('../button'); + +class ConfirmationModal extends React.PureComponent { + render() { + const { target, confirmation, onConfirm, onCancel } = this.props; + + return r(Modal, { + isOpen: Boolean(confirmation), + onRequestClose: onCancel, + }, [ + confirmation === 'unloadModuleByIndex' && r(React.Fragment, [ + r.h3('Module unload confirmation'), + + target && r.p([ + 'You are about to unload ', + r.code(target.name), + '.', + 'This may not be easily undoable and may impair sound playback on your system.', + ]), + ]), + + r(Checkbox, { + checked: this.props.preferences.doNotAskForConfirmations, + onChange: () => this.props.toggle('doNotAskForConfirmations'), + }, 'Do not ask for confirmations'), + + r.div({ + className: 'button-group', + }, [ + r(Button, { + onClick: onCancel, + }, 'Cancel'), + + r(Button, { + onClick: onConfirm, + autoFocus: true, + }, 'Confirm'), + ]), + ]); + } +} + +module.exports = ConfirmationModal; diff --git a/components/modals/connect-to-server.js b/components/modals/connect-to-server.js new file mode 100644 index 0000000..5fc20ee --- /dev/null +++ b/components/modals/connect-to-server.js @@ -0,0 +1,87 @@ + +const { spawn } = require('child_process'); + +const { + merge, +} = require('ramda'); + +const r = require('r-dom'); + +const React = require('react'); + +const Modal = require('react-modal'); + +const Button = require('../button'); +const Label = require('../label'); +const Input = require('../input'); + +class ConnectToServerModal extends React.PureComponent { + constructor(props) { + super(props); + + this.state = { + value: 'tcp:remote-computer.lan', + }; + + this.handleSubmit = this.handleSubmit.bind(this); + } + + handleSubmit(e) { + e.preventDefault(); + + const subprocess = spawn('pagraphcontrol', [], { + detached: true, + stdio: 'ignore', + env: merge(process.env, { + PULSE_SERVER: this.state.value, + }), + }); + + subprocess.unref(); + + this.props.onRequestClose(); + } + + render() { + const { isOpen, onRequestClose } = this.props; + + return r(Modal, { + isOpen, + onRequestClose, + }, [ + r.h3('Connect to PulseAudio server'), + + r.form({ + onSubmit: this.handleSubmit, + }, [ + r(Label, [ + r.div({ + title: 'Same format as PULSE_SERVER', + }, 'Specify the server to connect to:'), + r.p([ + r(Input, { + style: { width: '100%' }, + autoFocus: true, + value: this.state.value, + onChange: e => this.setState({ value: e.target.value }), + }), + ]), + ]), + + r.div({ + className: 'button-group', + }, [ + r(Button, { + onClick: onRequestClose, + }, 'Cancel'), + + r(Button, { + type: 'submit', + }, 'Connect'), + ]), + ]), + ]); + } +} + +module.exports = ConnectToServerModal; diff --git a/components/modals/index.js b/components/modals/index.js index 3ede793..3706c70 100644 --- a/components/modals/index.js +++ b/components/modals/index.js @@ -26,8 +26,8 @@ const { const { modules } = require('../../constants/pulse'); -const Checkbox = require('../checkbox'); -const Button = require('../button'); +const ConnectToServerModal = require('./connect-to-server'); +const ConfirmationModal = require('./confirmation'); Modal.setAppElement('#root'); @@ -36,46 +36,6 @@ Modal.defaultStyles = { content: {}, }; -class ConfirmationModal extends React.PureComponent { - render() { - const { target, confirmation, onConfirm, onCancel } = this.props; - - return r(Modal, { - isOpen: Boolean(confirmation), - onRequestClose: onCancel, - }, [ - confirmation === 'unloadModuleByIndex' && r(React.Fragment, [ - r.h3('Module unload confirmation'), - - target && r.p([ - 'You are about to unload ', - r.code(target.name), - '.', - 'This may not be easily undoable and may impair sound playback on your system.', - ]), - ]), - - r(Checkbox, { - checked: this.props.preferences.doNotAskForConfirmations, - onChange: () => this.props.toggle('doNotAskForConfirmations'), - }, 'Do not ask for confirmations'), - - r.div({ - className: 'button-group', - }, [ - r(Button, { - onClick: onCancel, - }, 'Cancel'), - - r(Button, { - onClick: onConfirm, - autoFocus: true, - }, 'Confirm'), - ]), - ]); - } -} - class Modals extends React.PureComponent { constructor(props) { super(props); @@ -84,15 +44,21 @@ class Modals extends React.PureComponent { target: null, confirmation: null, continuation: null, + + connectToServerModalOpen: false, + + actions: { + openConnectToServerModal: this.openConnectToServerModal.bind(this), + }, }; this.state = this.initialState; this.handleCancel = this.handleCancel.bind(this); } - static getDerivedStateFromProps(props) { + static getDerivedStateFromProps(props, state) { return { - actions: mapObjIndexed((f, name) => function (...args) { + actions: merge(state.actions, mapObjIndexed((f, name) => function (...args) { const continuation = () => { props[name](...args); this.setState(this.initialState); @@ -123,10 +89,14 @@ class Modals extends React.PureComponent { return null; }, - }), + })), }; } + openConnectToServerModal() { + this.setState({ connectToServerModalOpen: true }); + } + handleCancel() { this.setState(this.initialState); } @@ -136,7 +106,7 @@ class Modals extends React.PureComponent { const { actions, target, confirmation, continuation } = this.state; return r(React.Fragment, [ - ...children({ actions: map(a => a.bind(this), actions) }), + ...[].concat(children({ actions: map(a => a.bind(this), actions) })), r(ConfirmationModal, { target, @@ -147,6 +117,11 @@ class Modals extends React.PureComponent { preferences, toggle, }), + + r(ConnectToServerModal, { + isOpen: this.state.connectToServerModalOpen, + onRequestClose: this.handleCancel, + }), ]); } } diff --git a/renderer.js b/renderer.js index ef43e95..cbf5fac 100644 --- a/renderer.js +++ b/renderer.js @@ -20,10 +20,11 @@ const theme = require('./utils/theme'); const Root = () => r(ReduxProvider, { store: createStore(), -}, r(MenuProvider, { }, r(HotKeys, { }, ({ graphRef, cardsRef, preferencesRef }) => r(Modals, { -}, ({ actions }) => [ +}, ({ actions }) => r(MenuProvider, { + ...actions, +}, [ r(Graph, { ref: graphRef, ...actions }), r(Cards, { ref: cardsRef }), r(Preferences, { ref: preferencesRef }), diff --git a/store/pulse-middleware.js b/store/pulse-middleware.js index 4fc0c55..196206e 100644 --- a/store/pulse-middleware.js +++ b/store/pulse-middleware.js @@ -119,7 +119,7 @@ module.exports = store => { store.dispatch(pulseActions.remove({ type, index })); }) .on('error', error => { - console.error(error); + handleError(error); }); const reconnect = () => new Bluebird((resolve, reject) => {