// Copyright (c) 2021 Robin Davies // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import React, { SyntheticEvent, Component } from 'react'; import IconButton from '@material-ui/core/IconButton'; import Typography from '@material-ui/core/Typography'; import { PiPedalModel, PiPedalModelFactory, State } from './PiPedalModel'; import ButtonBase from "@material-ui/core/ButtonBase"; import { TransitionProps } from '@material-ui/core/transitions/transition'; import Slide from '@material-ui/core/Slide'; import AppBar from '@material-ui/core/AppBar'; import Toolbar from '@material-ui/core/Toolbar'; import { Theme, withStyles, WithStyles, createStyles } from '@material-ui/core/styles'; import ArrowBackIcon from '@material-ui/icons/ArrowBack'; import JackConfiguration, { JackChannelSelection } from './Jack'; import Divider from '@material-ui/core/Divider'; import SelectChannelsDialog from './SelectChannelsDialog'; import SelectMidiChannelsDialog from './SelectMidiChannelsDialog'; import SelectHoverBackground from './SelectHoverBackground'; import JackServerSettings from './JackServerSettings'; import JackServerSettingsDialog from './JackServerSettingsDialog'; import JackHostStatus from './JackHostStatus'; import WifiConfigSettings from './WifiConfigSettings'; import WifiConfigDialog from './WifiConfigDialog'; import DialogEx from './DialogEx' interface SettingsDialogProps extends WithStyles { open: boolean; onClose: () => void; }; interface SettingsDialogState { jackConfiguration: JackConfiguration; jackSettings: JackChannelSelection; jackServerSettings: JackServerSettings; jackStatus?: JackHostStatus; wifiConfigSettings: WifiConfigSettings; showWifiConfigDialog: boolean; showInputSelectDialog: boolean; showOutputSelectDialog: boolean; showMidiSelectDialog: boolean; showJackServerSettingsDialog: boolean; shuttingDown: boolean; restarting: boolean; }; const styles = (theme: Theme) => createStyles({ dialogAppBar: { position: 'relative', top: 0, left: 0 }, dialogTitle: { marginLeft: theme.spacing(2), flex: 1, }, sectionHead: { marginLeft: 24, marginRight: 24, marginTop: 16, paddingBottom: 12 }, textBlock: { marginLeft: 24, marginRight: 24, marginTop: 16, paddingBottom: 0, opacity: 0.95 }, textBlockIndented: { marginLeft: 40, marginRight: 24, marginTop: 16, paddingBottom: 0, opacity: 0.95 }, setting: { minHeight: 64, width: "100%", textAlign: "left", paddingLeft: 22, paddingRight: 22 }, primaryItem: { }, secondaryItem: { } }); const Transition = React.forwardRef(function Transition( props: TransitionProps & { children?: React.ReactElement }, ref: React.Ref, ) { return ; }); const SettingsDialog = withStyles(styles, { withTheme: true })( class extends Component { model: PiPedalModel; constructor(props: SettingsDialogProps) { super(props); this.model = PiPedalModelFactory.getInstance(); this.handleDialogClose = this.handleDialogClose.bind(this); this.state = { jackServerSettings: this.model.jackServerSettings.get(), jackConfiguration: this.model.jackConfiguration.get(), jackStatus: undefined, jackSettings: this.model.jackSettings.get(), wifiConfigSettings: this.model.wifiConfigSettings.get(), showWifiConfigDialog: false, showInputSelectDialog: false, showOutputSelectDialog: false, showMidiSelectDialog: false, showJackServerSettingsDialog: false, shuttingDown: false, restarting: false }; this.handleJackConfigurationChanged = this.handleJackConfigurationChanged.bind(this); this.handleJackSettingsChanged = this.handleJackSettingsChanged.bind(this); this.handleJackServerSettingsChanged = this.handleJackServerSettingsChanged.bind(this); this.handleWifiConfigSettingsChanged = this.handleWifiConfigSettingsChanged.bind(this); this.handleConnectionStateChanged = this.handleConnectionStateChanged.bind(this); } handleConnectionStateChanged() : void { if (this.model.state.get() === State.Ready) { if (this.state.shuttingDown || this.state.restarting) { this.setState({shuttingDown: false, restarting: false}); } } } handleApplyWifiConfig(wifiConfigSettings: WifiConfigSettings): void { this.setState({showWifiConfigDialog: false}); this.model.setWifiConfigSettings(wifiConfigSettings) .then(() => { }) .catch((err) => { this.model.showAlert(err); }); } handleWifiConfigSettingsChanged(): void { this.setState( { wifiConfigSettings: this.model.wifiConfigSettings.get() } ) } handleJackSettingsChanged(): void { this.setState({ jackSettings: this.model.jackSettings.get() }); } handleJackServerSettingsChanged(): void { this.setState({ jackServerSettings: this.model.jackServerSettings.get() }); } handleJackConfigurationChanged(): void { this.setState({ jackConfiguration: this.model.jackConfiguration.get() }); } mounted: boolean = false; active: boolean = false; timerHandle?: NodeJS.Timeout; tick() { this.model.getJackStatus() .then((jackStatus) => this.setState( { jackStatus: jackStatus }) ) .catch((error) => { }); } updateActive() { let active = this.mounted && this.props.open; if (active !== this.active) { this.active = active; if (active) { this.model.state.addOnChangedHandler(this.handleConnectionStateChanged); this.model.jackSettings.addOnChangedHandler(this.handleJackSettingsChanged); this.model.jackConfiguration.addOnChangedHandler(this.handleJackConfigurationChanged); this.model.jackServerSettings.addOnChangedHandler(this.handleJackServerSettingsChanged); this.model.wifiConfigSettings.addOnChangedHandler(this.handleWifiConfigSettingsChanged); this.model.getJackStatus() .then((jackStatus) => this.setState( { jackStatus: jackStatus }) ) .catch((error) => { this.setState( { jackStatus: undefined }) }); this.handleConnectionStateChanged(); this.handleJackConfigurationChanged(); this.handleJackSettingsChanged(); this.handleJackServerSettingsChanged(); this.handleWifiConfigSettingsChanged(); this.timerHandle = setInterval(() => this.tick(), 1000); } else { if (this.timerHandle) { clearInterval(this.timerHandle); } this.model.state.removeOnChangedHandler(this.handleConnectionStateChanged); this.model.jackConfiguration.removeOnChangedHandler(this.handleJackConfigurationChanged); this.model.jackSettings.removeOnChangedHandler(this.handleJackSettingsChanged); this.model.jackServerSettings.removeOnChangedHandler(this.handleJackServerSettingsChanged); this.model.wifiConfigSettings.removeOnChangedHandler(this.handleWifiConfigSettingsChanged); } } } componentDidMount() { this.mounted = true; this.updateActive(); // scroll selected item into view. } componentDidUpdate() { this.updateActive(); } componentWillUnmount() { this.mounted = false; this.updateActive(); } handleDialogClose(e: SyntheticEvent) { this.props.onClose(); } handleMidiSelection() { this.setState({ showMidiSelectDialog: true }) } handleJackServerSettings() { this.setState({ showJackServerSettingsDialog: true, }); } handleInputSelection() { this.setState({ showInputSelectDialog: true, showOutputSelectDialog: false }); } handleOutputSelection() { this.setState({ showInputSelectDialog: false, showOutputSelectDialog: true }); } handleSelectMidiDialogResult(channels: string[] | null): void { if (channels) { let newSelection = this.state.jackSettings.clone(); newSelection.inputMidiPorts = channels; this.model.setJackSettings(newSelection); } this.setState({ showMidiSelectDialog: false, }); } handleSelectChannelsDialogResult(channels: string[] | null): void { if (channels) { let newSelection = this.state.jackSettings.clone(); if (this.state.showInputSelectDialog) { newSelection.inputAudioPorts = channels; } else { newSelection.outputAudioPorts = channels; } this.setState({ jackSettings: newSelection, showInputSelectDialog: false, showOutputSelectDialog: false }); this.model.setJackSettings(newSelection); } else { this.setState({ showInputSelectDialog: false, showOutputSelectDialog: false }); } } midiSummary(): string { let ports = this.state.jackSettings.inputMidiPorts; if (ports.length === 0) return "Disabled"; if (ports.length === 1) return ports[0]; return ports.length + " channels"; } handleShowWifiConfigDialog() { this.setState({ showWifiConfigDialog: true }); } handleRestart() { this.setState({ restarting: true }); this.model.restart() .then(() => { // this.setState({ restarting: true }); }) .catch((error) => { this.model.showAlert(error); this.setState({ restarting: false }); }); } handleShutdown() { this.setState({ shuttingDown: true }); this.model.shutdown() .then(() => { this.setState({ shuttingDown: true }); }) .catch((error) => { this.model.showAlert(error); this.setState({ shuttingDown: false }); }); } render() { let classes = this.props.classes; let isConfigValid = this.state.jackConfiguration.isValid; let selectedChannels: string[] = this.state.showInputSelectDialog ? this.state.jackSettings.inputAudioPorts : this.state.jackSettings.outputAudioPorts; let disableShutdown = this.state.shuttingDown || this.state.restarting; return ( { this.props.onClose() }} TransitionComponent={Transition} style={{userSelect: "none"}} >
Settings
AUDIO
{ (!isConfigValid) ? ( Status: Not configured. ): JackHostStatus.getDisplayView("Status:\u00A0", this.state.jackStatus) }
{ this.state.jackConfiguration.errorState !== "" && (
{this.state.jackConfiguration.errorState }
) } this.handleJackServerSettings()} >
Audio device {this.state.jackServerSettings.getSummaryText()}
this.setState({ showJackServerSettingsDialog: false })} onApply={(jackServerSettings) => { this.setState({ showJackServerSettingsDialog: false, jackServerSettings: jackServerSettings }); this.model.setJackServerSettings(jackServerSettings); }} /> this.handleInputSelection()} disabled={!isConfigValid} style={{ opacity: !isConfigValid ? 0.6 : 1.0 }} >
Input channels {this.state.jackSettings.getAudioInputDisplayValue(this.state.jackConfiguration)}
this.handleOutputSelection()} disabled={!isConfigValid} style={{ opacity: !isConfigValid ? 0.6 : 1.0 }} >
Output channels {this.state.jackSettings.getAudioOutputDisplayValue(this.state.jackConfiguration)}
MIDI this.handleMidiSelection()} >
Select MIDI input channels {this.midiSummary()}
SYSTEM this.handleShowWifiConfigDialog()} >
Configure Wi-Fi hotspot {this.state.wifiConfigSettings.getSummaryText()}
this.handleRestart()} >
{ this.state.restarting ? ( Rebooting... ) : ( Reboot ) }
this.handleShutdown()} >
{ this.state.shuttingDown ? ( Shutting down... ) : ( Shut down ) }
{ (this.state.showInputSelectDialog || this.state.showOutputSelectDialog) && ( this.handleSelectChannelsDialogResult(selectedChannels)} selectedChannels={selectedChannels} availableChannels={this.state.showInputSelectDialog ? this.state.jackConfiguration.inputAudioPorts : this.state.jackConfiguration.outputAudioPorts} /> ) } { (this.state.showMidiSelectDialog) && ( this.handleSelectMidiDialogResult(selectedChannels)} selectedChannels={this.state.jackSettings.inputMidiPorts} availableChannels={this.state.jackConfiguration.inputMidiPorts} /> ) } this.setState({showWifiConfigDialog: false})} onOk={(wifiConfigSettings) => this.handleApplyWifiConfig(wifiConfigSettings)} />
); } } ); export default SettingsDialog;