WiFi Channel Settings.

This commit is contained in:
Robin Davies
2021-08-20 10:35:37 -04:00
parent 140f2abd39
commit f6aa331d77
40 changed files with 2409 additions and 597 deletions
+1 -1
View File
@@ -646,7 +646,7 @@ const App = withStyles(appStyles)(class extends ResizeResponsiveComponent<AppPro
)}
</div>
)}
<TemporaryDrawer position='left' title="piddle"
<TemporaryDrawer position='left' title="PiPedal"
is_open={this.state.isDrawerOpen} onClose={() => { this.hideDrawer(); }} >
<List subheader={
<ListSubheader component="div" id="nested-list-subheader">Banks</ListSubheader>
+119
View File
@@ -0,0 +1,119 @@
import React from 'react';
import Dialog, {DialogProps} from '@material-ui/core/Dialog';
interface DialogExProps extends DialogProps {
tag: string;
}
interface DialogExState {
}
class DialogEx extends React.Component<DialogExProps,DialogExState> {
constructor(props: DialogExProps)
{
super(props);
this.state = {
};
this.handlePopState = this.handlePopState.bind(this);
}
mounted: boolean = false;
hasHooks: boolean = false;
stateWasPopped: boolean = false;
handlePopState(e: any): any {
this.stateWasPopped = true;
let shouldClose = (!e.state || !e.state[this.props.tag]);
if (shouldClose)
{
if (this.props.onClose)
{
this.props.onClose(e,"backdropClick");
}
}
}
updateHooks() : void {
let wantHooks = this.mounted && this.props.open;
if (wantHooks !== this.hasHooks)
{
this.hasHooks = wantHooks;
if (this.hasHooks)
{
this.stateWasPopped = false;
window.addEventListener("popstate",this.handlePopState);
// eslint-disable-next-line no-restricted-globals
let state = history.state;
if (!state)
{
state = {};
}
state[this.props.tag] = true;
// eslint-disable-next-line no-restricted-globals
history.pushState(
state,
"",
"#" + this.props.tag
);
} else {
window.removeEventListener("popstate",this.handlePopState);
if (!this.stateWasPopped)
{
// eslint-disable-next-line no-restricted-globals
history.back();
}
}
}
}
onWindowSizeChanged(width: number, height: number): void
{
this.setState({fullScreen: height < 200})
}
componentDidMount()
{
if (super.componentDidMount)
{
super.componentDidMount();
}
this.mounted = true;
this.updateHooks();
}
componentWillUnmount()
{
if (super.componentWillUnmount)
{
super.componentWillUnmount();
}
this.mounted = false;
this.updateHooks();
}
componentDidUpdate()
{
this.updateHooks();
}
render() {
let { tag, ...extra} = this.props;
return (
<Dialog {...extra}>
{this.props.children}
</Dialog>
);
}
};
export default DialogEx;
+1 -1
View File
@@ -187,7 +187,7 @@ export const MidiBindingDialog =
this.updateHooks();
}
componentWillUnmount() {
super.componentDidMount();
super.componentWillUnmount();
this.mounted = false;
this.updateHooks();
+118
View File
@@ -0,0 +1,118 @@
import React from 'react';
import InputAdornment from '@material-ui/core/InputAdornment';
import IconButton from '@material-ui/core/IconButton';
import FormControl from '@material-ui/core/FormControl';
import FormHelperText from '@material-ui/core/FormHelperText';
import InputLabel from '@material-ui/core/InputLabel';
import Input from '@material-ui/core/Input';
import Visibility from '@material-ui/icons/Visibility';
import VisibilityOff from '@material-ui/icons/VisibilityOff';
interface NoChangePasswordProps {
hasPassword: boolean;
label: string;
defaultValue: string;
onPasswordChange: (text: string) => void;
disabled?: boolean;
helperText?: string;
error?: boolean;
};
interface NoChangePasswordState {
focused: boolean;
passwordText: string;
showPassword: boolean;
};
class NoChangePassword extends React.Component<NoChangePasswordProps, NoChangePasswordState> {
refText: React.Ref<HTMLInputElement | HTMLTextAreaElement>;
constructor(props: NoChangePasswordProps) {
super(props);
this.refText = React.createRef();
this.state = {
focused: false,
showPassword: false,
passwordText: this.props.defaultValue as string
};
}
handleShowPassword() {
this.setState({
showPassword: !this.state.showPassword
});
}
handleChange(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) {
this.textChanged = true;
this.props.onPasswordChange(e.target.value);
}
textChanged: boolean = false;
handleFocus(e: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) {
console.log("onFocus");
if (!this.state.focused) {
this.textChanged = false;
e.target.value = this.state.passwordText;
}
e.currentTarget.removeAttribute("readonly");
this.setState({ focused: true });
}
handleBlur(e: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) {
console.log("onBlur");
let text = e.target.value as string;
this.setState({ focused: false, passwordText: text });
this.props.onPasswordChange(e.target.value);
this.textChanged = false;
if (text.length === 0 && this.props.hasPassword)
{
e.target.value = "(Unchanged)";
}
}
render() {
console.log("Render focus: " + this.state.focused)
let showUnchanged = (this.state.passwordText.length === 0) && this.props.hasPassword && (!this.state.focused);
let thisDefaultValue = showUnchanged ? "(Unchanged)" : this.props.defaultValue;
let isText = showUnchanged || this.state.showPassword;
return (
<FormControl disabled={this.props.disabled} fullWidth error={this.props.error} >
<InputLabel htmlFor="standard-adornment-password">{this.props.label}</InputLabel>
<Input
fullWidth
color="primary"
spellCheck="false"
autoComplete="off"
readOnly
id="standard-adornment-password"
onChange={(e) => this.handleChange(e)}
value={thisDefaultValue}
onFocus={(e) => this.handleFocus(e)}
onBlur={(e) => this.handleBlur(e)}
type={isText ? 'text' : 'password'}
style={ { color: showUnchanged? '#888': '#000'} }
endAdornment={
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={(e) => this.handleShowPassword()}
onMouseDown={(e) => e.preventDefault()}
>
{this.state.showPassword ? <Visibility /> : <VisibilityOff />}
</IconButton>
</InputAdornment>
}
/>
<FormHelperText >{ this.props.helperText}</FormHelperText>
</FormControl>
);
}
};
export default NoChangePassword;
+1 -1
View File
@@ -117,7 +117,7 @@ const pedalBoardStyles = (theme: Theme) => createStyles({
marginBottom: (CELL_HEIGHT - FRAME_SIZE) / 2,
width: FRAME_SIZE,
height: FRAME_SIZE,
border: "1pt black solid",
border: "1pt #666 solid",
borderRadius: 6
},
pedalIcon: {
+108 -6
View File
@@ -13,6 +13,8 @@ import JackHostStatus from './JackHostStatus';
import JackServerSettings from './JackServerSettings';
import MidiBinding from './MidiBinding';
import PluginPreset from './PluginPreset';
import WifiConfigSettings from './WifiConfigSettings';
import WifiChannel from './WifiChannel';
export enum State {
@@ -235,6 +237,7 @@ interface ControlChangedBody {
export interface PiPedalModel {
clientId: number;
countryCodes: Object;
serverVersion?: PiPedalVersion;
errorMessage: ObservableProperty<string>;
alertMessage: ObservableProperty<string>;
@@ -245,6 +248,7 @@ export interface PiPedalModel {
jackSettings: ObservableProperty<JackChannelSelection>;
banks: ObservableProperty<BankIndex>;
jackServerSettings: ObservableProperty<JackServerSettings>;
wifiConfigSettings: ObservableProperty<WifiConfigSettings>;
presets: ObservableProperty<PresetIndex>;
@@ -320,13 +324,17 @@ export interface PiPedalModel {
uploadPreset(file: File, uploadAfter: number): Promise<number>;
setWifiConfigSettings(wifiConfigSettings: WifiConfigSettings): Promise<void>;
getWifiChannels(countryIso3661: string): Promise<WifiChannel[]>;
};
class PiPedalModelImpl implements PiPedalModel {
clientId: number = -1;
serverVersion?: PiPedalVersion;
countryCodes: Object = {};
socketServerUrl: string = "";
varServerUrl: string = "";
lv2Path: string = "";
@@ -346,6 +354,8 @@ class PiPedalModelImpl implements PiPedalModel {
jackServerSettings: ObservableProperty<JackServerSettings>
= new ObservableProperty<JackServerSettings>(new JackServerSettings());
wifiConfigSettings: ObservableProperty<WifiConfigSettings> = new ObservableProperty<WifiConfigSettings>(new WifiConfigSettings());
presets: ObservableProperty<PresetIndex> = new ObservableProperty<PresetIndex>
(
@@ -459,6 +469,9 @@ class PiPedalModelImpl implements PiPedalModel {
} else if (message === "onJackServerSettingsChanged") {
let jackServerSettings = new JackServerSettings().deserialize(body);
this.jackServerSettings.set(jackServerSettings);
} else if (message === "onWifiConfigSettingsChanged") {
let wifiConfigSettings = new WifiConfigSettings().deserialize(body);
this.wifiConfigSettings.set(wifiConfigSettings);
} else if (message === "onBanksChanged") {
let banks = new BankIndex().deserialize(body);
this.banks.set(banks);
@@ -537,6 +550,11 @@ class PiPedalModelImpl implements PiPedalModel {
.then(data => {
this.pedalBoard.set(new PedalBoard().deserialize(data));
return this.getWebSocket().request<any>("getWifiConfigSettings");
})
.then(data => {
this.wifiConfigSettings.set(new WifiConfigSettings().deserialize(data));
return this.getWebSocket().request<any>("getJackServerSettings");
})
.then(data => {
@@ -573,7 +591,7 @@ class PiPedalModelImpl implements PiPedalModel {
})
}
makeSocketServerUrl(hostName: string, port: number): string {
return "ws://" + hostName + ":" + port + "/piddle";
return "ws://" + hostName + ":" + port + "/pipedal";
}
makeVarServerUrl(protocol: string, hostName: string, port: number): string {
@@ -587,11 +605,12 @@ class PiPedalModelImpl implements PiPedalModel {
const myRequest = new Request(this.varRequest('config.json'));
return fetch(myRequest)
.then(
response => response.json()
(response) =>
{
return response.json();
}
)
.then(data => {
console.log("Got config");
if (data.max_upload_size)
{
this.maxUploadSize = data.max_upload_size;
@@ -617,6 +636,15 @@ class PiPedalModelImpl implements PiPedalModel {
return this.webSocket.connect();
})
.then(() => {
const isoRequest = new Request('iso_codes.json');
return fetch(isoRequest);
})
.then((response) => {
return response.json();
})
.then((countryCodes) => {
this.countryCodes = countryCodes as Object;
return this.getWebSocket().request<number>("hello");
})
.then((clientId) => {
@@ -652,7 +680,12 @@ class PiPedalModelImpl implements PiPedalModel {
})
.then((data) => {
this.presets.set(new PresetIndex().deserialize(data));
return this.getWebSocket().request<any>("getWifiConfigSettings");
})
.then(data => {
this.wifiConfigSettings.set(new WifiConfigSettings().deserialize(data));
return this.getWebSocket().request<any>("getJackServerSettings");
})
.then(data => {
@@ -1427,6 +1460,75 @@ class PiPedalModelImpl implements PiPedalModel {
});
return result;
}
setWifiConfigSettings(wifiConfigSettings: WifiConfigSettings): Promise<void>
{
let result = new Promise<void>((resolve,reject) =>{
let oldSettings = this.wifiConfigSettings.get();
wifiConfigSettings = wifiConfigSettings.clone();
if ((!oldSettings.enable) && (!wifiConfigSettings.enable))
{
// no effective change.
resolve();
return;
}
let countryCodeChanged = wifiConfigSettings.countryCode !== oldSettings.countryCode;
if (!wifiConfigSettings.enable)
{
wifiConfigSettings.hasPassword = false;
wifiConfigSettings.hotspotName = oldSettings.hotspotName;
} else {
if (!wifiConfigSettings.hasPassword) {
if (wifiConfigSettings.hotspotName === oldSettings.hotspotName && !countryCodeChanged) {
// no effective change.
resolve();
return;
}
}
}
// save a version for the server (potentially carrying a password)
let serverConfigSettings = wifiConfigSettings.clone();
// notify the server.
let ws = this.webSocket;
if (!ws)
{
reject("Not connected.");
return;
}
ws.request<void>(
"setWifiConfigSettings",
serverConfigSettings
)
.then(()=> {
resolve();
})
.catch((err) =>{
reject(err);
});
});
return result;
}
getWifiChannels(countryIso3661: string): Promise<WifiChannel[]>
{
let result = new Promise<WifiChannel[]>((resolve,reject) => {
if (!this.webSocket) {
reject("Connection closed.");
return;
}
this.webSocket.request<WifiChannel[]>("getWifiChannels",countryIso3661)
.then((data) => {
resolve(WifiChannel.deserialize_array(data));
})
.catch((err)=> reject(err));
});
return result;
}
};
+2 -2
View File
@@ -100,12 +100,12 @@ const styles = (theme: Theme) => createStyles({
marginRight: 8,
marginBottom: 12,
position: "relative",
paddingLeft: 0,
paddingLeft: 3,
paddingRight: 0,
paddingTop: 0,
paddingBottom: 0,
border: "2pt #AAA solid",
borderRadius: 10,
borderRadius: 4,
elevation: 12,
display: "flex",
flexDirection: "row", flexWrap: "wrap",
+1 -1
View File
@@ -95,7 +95,7 @@ export default class RenameDialog extends ResizeResponsiveComponent<RenameDialog
}
componentWillUnmount()
{
super.componentDidMount();
super.componentWillUnmount();
this.mounted = false;
this.updateHooks();
}
+60 -7
View File
@@ -5,7 +5,6 @@ import { PiPedalModel, PiPedalModelFactory } 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 Dialog from '@material-ui/core/Dialog';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import { Theme, withStyles, WithStyles, createStyles } from '@material-ui/core/styles';
@@ -18,6 +17,9 @@ 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'
@@ -34,6 +36,9 @@ interface SettingsDialogState {
jackServerSettings: JackServerSettings;
jackStatus?: JackHostStatus;
wifiConfigSettings: WifiConfigSettings;
showWifiConfigDialog: boolean;
showInputSelectDialog: boolean;
showOutputSelectDialog: boolean;
showMidiSelectDialog: boolean;
@@ -120,6 +125,8 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
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,
@@ -132,10 +139,28 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
this.handleJackConfigurationChanged = this.handleJackConfigurationChanged.bind(this);
this.handleJackSettingsChanged = this.handleJackSettingsChanged.bind(this);
this.handleJackServerSettingsChanged = this.handleJackServerSettingsChanged.bind(this);
this.handleWifiConfigSettingsChanged = this.handleWifiConfigSettingsChanged.bind(this);
}
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()
@@ -177,6 +202,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
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(
@@ -193,7 +219,8 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
this.handleJackConfigurationChanged();
this.handleJackSettingsChanged();
this.handleJackServerSettingsChanged();
this.timerHandle = setInterval(() => this.tick(), 1000);
this.handleWifiConfigSettingsChanged();
// xxx UNCOMMENT ME! this.timerHandle = setInterval(() => this.tick(), 1000);
} else {
if (this.timerHandle) {
clearInterval(this.timerHandle);
@@ -201,6 +228,8 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
this.model.jackConfiguration.removeOnChangedHandler(this.handleJackConfigurationChanged);
this.model.jackSettings.removeOnChangedHandler(this.handleJackSettingsChanged);
this.model.jackServerSettings.removeOnChangedHandler(this.handleJackServerSettingsChanged);
this.model.wifiConfigSettings.removeOnChangedHandler(this.handleWifiConfigSettingsChanged);
}
}
@@ -292,25 +321,33 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
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 });
// 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 });
});
}
@@ -322,7 +359,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
let disableShutdown = this.state.shuttingDown || this.state.restarting;
return (
<Dialog fullScreen open={this.props.open}
<DialogEx tag="SettingsDialog" fullScreen open={this.props.open}
onClose={() => { this.props.onClose() }} TransitionComponent={Transition}>
<div style={{ display: "flex", flexDirection: "column", flexWrap: "nowrap", width: "100%", height: "100%", overflow: "hidden" }}>
@@ -428,6 +465,18 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
<Divider />
<div >
<Typography className={classes.sectionHead} display="block" variant="caption" color="secondary">SYSTEM</Typography>
<ButtonBase className={classes.setting} disabled={!this.state.wifiConfigSettings.valid}
onClick={() => this.handleShowWifiConfigDialog()} >
<SelectHoverBackground selected={false} showHover={true} />
<div style={{ width: "100%" }}>
<Typography className={classes.primaryItem} display="block" variant="body2" color="textPrimary" noWrap>
Configure Wi-fi Hotspot</Typography>
<Typography display="block" variant="caption" noWrap color="textSecondary">
{this.state.wifiConfigSettings.getSummaryText()}
</Typography>
</div>
</ButtonBase>
<ButtonBase className={classes.setting} disabled={!isConfigValid || disableShutdown}
onClick={() => this.handleRestart()} >
<SelectHoverBackground selected={false} showHover={true} />
@@ -452,7 +501,7 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
) : (
<Typography className={classes.primaryItem} display="block" variant="body2" noWrap>Shut down</Typography>
)
}
}
</div >
</ButtonBase>
</div>
@@ -483,8 +532,12 @@ const SettingsDialog = withStyles(styles, { withTheme: true })(
)
}
<WifiConfigDialog wifiConfigSettings={this.state.wifiConfigSettings} open={this.state.showWifiConfigDialog}
onClose={()=> this.setState({showWifiConfigDialog: false})}
onOk={(wifiConfigSettings) => this.handleApplyWifiConfig(wifiConfigSettings)}
/>
</Dialog >
</DialogEx >
);
+1 -1
View File
@@ -93,7 +93,7 @@ export default class UploadDialog extends ResizeResponsiveComponent<UploadDialog
this.updateHooks();
}
componentWillUnmount() {
super.componentDidMount();
super.componentWillUnmount();
this.mounted = false;
this.updateHooks();
}
+20
View File
@@ -0,0 +1,20 @@
export default class WifiChannel {
deserialize(input: any): WifiChannel {
this.channelId = input.channelId;
this.channelName = input.channelName;
return this;
}
static deserialize_array(input: any): WifiChannel[]
{
let result: WifiChannel[] = [];
for (let i = 0; i < input.length; ++i)
{
result.push(new WifiChannel().deserialize(input[i]));
}
return result;
}
channelId: string = "";
channelName: string = "";
};
+374
View File
@@ -0,0 +1,374 @@
import React from 'react';
import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogTitle from '@material-ui/core/DialogTitle';
import Switch from '@material-ui/core/Switch';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
import WifiConfigSettings from './WifiConfigSettings';
import NoChangePassword from './NoChangePassword';
import DialogEx from './DialogEx'
import Typography from '@material-ui/core/Typography';
import { Theme, withStyles, WithStyles,createStyles } from '@material-ui/core/styles';
import Select from '@material-ui/core/Select';
import FormControl from '@material-ui/core/FormControl';
import InputLabel from '@material-ui/core/InputLabel';
import MenuItem from '@material-ui/core/MenuItem';
import WifiChannel from './WifiChannel';
import {PiPedalModel, PiPedalModelFactory} from './PiPedalModel';
const styles = (theme: Theme) => createStyles({
pgraph: {
paddingBottom: 16
}
});
const NBSP = "\u00A0";
export interface WifiConfigProps extends WithStyles<typeof styles> {
open: boolean,
wifiConfigSettings: WifiConfigSettings,
onOk: (wifiConfigSettings: WifiConfigSettings) => void,
onClose: () => void
};
export interface WifiConfigState {
fullScreen: boolean;
showWifiWarningDialog: boolean;
wifiWarningGiven: boolean;
enabled: boolean;
name: string;
newPassword: string;
nameError: boolean;
nameErrorMessage: string;
passwordError: boolean;
passwordErrorMessage: string;
countryCode: string;
channel: string;
wifiChannels: WifiChannel[];
};
let validNameRegex = /^[a-zA-Z][a-zA-Z0-9_-]*$/
function isValidName(text: string)
{
return validNameRegex.test(text);
}
const WifiConfigDialog = withStyles(styles, { withTheme: true })(
class extends ResizeResponsiveComponent<WifiConfigProps, WifiConfigState> {
refName: React.RefObject<HTMLInputElement>;
refPassword: React.RefObject<HTMLInputElement>;
model: PiPedalModel;
constructor(props: WifiConfigProps) {
super(props);
this.model = PiPedalModelFactory.getInstance();
this.state = {
fullScreen: false,
enabled: this.props.wifiConfigSettings.enable,
name: this.props.wifiConfigSettings.hotspotName,
newPassword: "",
nameError: false,
nameErrorMessage: NBSP,
passwordError: false,
showWifiWarningDialog: false,
wifiWarningGiven: this.props.wifiConfigSettings.wifiWarningGiven,
passwordErrorMessage: NBSP,
countryCode: this.props.wifiConfigSettings.countryCode,
channel: this.props.wifiConfigSettings.channel,
wifiChannels: []
};
this.model.getWifiChannels(this.props.wifiConfigSettings.countryCode)
.then((wifiChannels_) => {
this.setState({wifiChannels: wifiChannels_});
}).catch((err)=> {});
this.refName = React.createRef<HTMLInputElement>();
this.refPassword = React.createRef<HTMLInputElement>();
this.handlePopState = this.handlePopState.bind(this);
}
mounted: boolean = false;
hasHooks: boolean = false;
stateWasPopped: boolean = false;
handleEnableChanged(e: any) {
this.setState({ enabled: e.target.checked });
}
handlePopState(e: any): any {
this.stateWasPopped = true;
let shouldClose = (!e.state || !e.state.WifiConfig);
if (shouldClose) {
this.props.onClose();
}
}
updateHooks(): void {
let wantHooks = this.mounted && this.props.open;
if (wantHooks !== this.hasHooks) {
this.hasHooks = wantHooks;
if (this.hasHooks) {
this.stateWasPopped = false;
window.addEventListener("popstate", this.handlePopState);
// eslint-disable-next-line no-restricted-globals
let state = history.state;
if (!state) {
state = {};
}
state.WifiConfig = true;
// eslint-disable-next-line no-restricted-globals
history.pushState(
state,
"Wi-Fi Configuration",
"#WifiConfig"
);
} else {
window.removeEventListener("popstate", this.handlePopState);
if (!this.stateWasPopped) {
// eslint-disable-next-line no-restricted-globals
history.back();
}
}
}
}
onWindowSizeChanged(width: number, height: number): void {
this.setState({ fullScreen: height < 200 })
}
componentDidMount() {
super.componentDidMount();
this.mounted = true;
this.updateHooks();
}
componentWillUnmount() {
super.componentWillUnmount();
this.mounted = false;
this.updateHooks();
}
componentDidUpdate(prevProps: WifiConfigProps) {
this.updateHooks();
if (this.props.open && !prevProps.open)
{
this.setState({
enabled: this.props.wifiConfigSettings.enable,
name: this.props.wifiConfigSettings.hotspotName,
newPassword: "",
wifiWarningGiven: this.props.wifiConfigSettings.wifiWarningGiven,
countryCode: this.props.wifiConfigSettings.countryCode,
channel: this.props.wifiConfigSettings.channel
});
if (this.props.wifiConfigSettings.countryCode !== this.state.countryCode)
{
this.model.getWifiChannels(this.props.wifiConfigSettings.countryCode)
.then((wifiChannels) => {
this.setState({wifiChannels: wifiChannels});
})
.catch((error) => {});
}
}
}
handleOk(wifiWarningGiven: boolean) {
let hasError = false;
if (this.state.enabled)
{
let name = this.state.name;
if (name.length === 0) {
this.setState({nameError: true, nameErrorMessage: "* Required"});
hasError = true;
} else {
if (!isValidName(name))
{
this.setState({nameError: true, nameErrorMessage: "Invalid name"});
hasError = true;
} else if (name.length > 15)
{
this.setState({nameError: true, nameErrorMessage: "> 15 characters"});
hasError = true;
}
}
}
if (this.state.enabled && (!this.props.wifiConfigSettings.hasPassword) && this.state.newPassword.length === 0)
{
this.setState({passwordError: true, passwordErrorMessage: "* Required"});
hasError = true;
}
if (!hasError)
{
let wifiConfigSettings = new WifiConfigSettings();
wifiConfigSettings.valid = true;
wifiConfigSettings.enable = this.state.enabled;
wifiConfigSettings.hotspotName = this.state.name;
if (this.state.newPassword.length === 0 || !this.state.enabled)
{
wifiConfigSettings.hasPassword = false;
} else {
wifiConfigSettings.hasPassword = true;
wifiConfigSettings.password = this.state.newPassword;
}
if (!this.props.wifiConfigSettings.wifiWarningGiven && !wifiWarningGiven) {
this.setState({showWifiWarningDialog: true});
} else {
wifiConfigSettings.wifiWarningGiven = true;
this.props.onOk(wifiConfigSettings);
}
}
}
handleChannelChange(e: any)
{
let value = e.target.value as string;
this.setState({channel: value });
}
handleCountryChanged(e: any)
{
let value = e.target.value as string;
this.setState({countryCode: value});
this.model.getWifiChannels(value)
.then(wifiChannels => {
this.setState({wifiChannels: wifiChannels});
})
.catch((error)=> { });
}
render() {
let props = this.props;
let classes = this.props.classes;
let { open, onClose} = props;
const handleClose = () => {
onClose();
};
return (
<Dialog open={open} fullWidth onClose={handleClose} style={{}}
fullScreen={this.state.fullScreen}
>
{ this.state.fullScreen && (
<DialogTitle>Wi-fi Hotspot</DialogTitle>
)}
<DialogContent>
<FormControlLabel
control={(
<Switch
checked={this.state.enabled}
onChange={(e: any) => this.handleEnableChanged(e)}
color="secondary"
/>
)}
label="Enable"
/>
<TextField style={{ marginBottom: 16, marginTop: 16 }}
autoComplete="off"
id="name"
spellCheck="false"
label="SSID"
type="text"
fullWidth
error={this.state.nameError}
helperText={this.state.nameErrorMessage}
value={this.state.name}
onChange={(e) => this.setState({name: e.target.value, nameError: false, nameErrorMessage: NBSP})}
inputRef={this.refName}
disabled={!this.state.enabled}
/>
<div style={{ marginBottom: 16 }}>
<NoChangePassword
onPasswordChange={(text): void => { this.setState({ newPassword: text, passwordError: false, passwordErrorMessage: NBSP }); }}
hasPassword={this.props.wifiConfigSettings.hasPassword}
label="WEP Passphrase"
error={this.state.passwordError}
helperText={this.state.passwordErrorMessage}
defaultValue={this.state.newPassword}
disabled={!this.state.enabled}
/>
</div>
<div style={{ marginBottom: 16}}>
<FormControl>
<InputLabel htmlFor="countryCodeSelect">Country</InputLabel>
<Select id="countryCodeSelect" value={this.state.countryCode} style={{width: 220}}
onChange={(event)=>this.handleCountryChanged(event)} >
{Object.entries(this.model.countryCodes).map(([key,value])=> {
return (
<MenuItem value={key}>{value}</MenuItem>
);
})}
</Select>
</FormControl>
</div>
<div style={{ marginBottom: 24}}>
<FormControl>
<InputLabel htmlFor="channelSelect">Channel</InputLabel>
<Select id="channelSelect" value={this.state.channel} style={{width: 220}} onChange={(e)=>{
this.handleChannelChange(e);
}}>
{this.state.wifiChannels.map((channel)=> {
return (
<MenuItem value={channel.channelId}>{channel.channelName}</MenuItem>
)
})}
</Select>
</FormControl>
</div>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary" style={{ width: 120 }}>
Cancel
</Button>
<Button onClick={()=> this.handleOk(false)} color="secondary" style={{ width: 120 }} >
OK
</Button>
</DialogActions>
<DialogEx open={this.state.showWifiWarningDialog} tag="wifiConfirm" >
<DialogContent>
<Typography className={classes.pgraph} variant="body2" color="textPrimary">
Enabling the Wi-Fi hotspot will disable regular Wi-Fi network access on the PiPedal device. Once
enabled, connect to the hotspot and launch http://172.24.1.1 or http://{this.state.name}.local to access the PiPedal web app again.
</Typography>
<Typography className={classes.pgraph} variant="body2" color="textPrimary" gutterBottom>
Are you sure you want to proceed?
</Typography>
</DialogContent>
<DialogActions>
<Button onClick={()=> this.setState({showWifiWarningDialog: false})} color="primary" style={{ width: 120 }}>
Cancel
</Button>
<Button onClick={()=> {
this.setState({showWifiWarningDialog: false});
this.handleOk(true);
}} color="secondary" style={{ width: 120 }} >
PROCEED
</Button>
</DialogActions>
</DialogEx>
</Dialog>
);
}
});
export default WifiConfigDialog;
+45
View File
@@ -0,0 +1,45 @@
export default class WifiConfigSettings {
deserialize(input: any) : WifiConfigSettings{
this.valid = input.valid;
this.wifiWarningGiven = input.wifiWarningGiven;
this.rebootRequired = input.rebootRequired;
this.enable = input.enable;
this.hotspotName = input.hotspotName;
this.hasPassword = input.hasPassword;
this.password = input.password;
this.countryCode = input.countryCode;
this.channel = input.channel;
return this;
}
clone() : WifiConfigSettings {
return this.deserialize(this);
}
valid: boolean = true;
wifiWarningGiven: boolean = false;
enable: boolean = true;
hasPassword: boolean = false;
rebootRequired = false;
hotspotName: string = "pipedal";
password: string = "";
countryCode: string = "US";
channel: string = "g6";
getSummaryText() {
let result: string;
if (!this.valid) {
result = "Not available.";
} else if (!this.enable) {
result = "Disabled.";
} else {
result = this.hotspotName;
}
if (this.rebootRequired)
{
result += " (Restart required)";
}
return result;
}
}
+36
View File
@@ -0,0 +1,36 @@
import React from 'react';
import { Theme, withStyles, WithStyles,createStyles } from '@material-ui/core/styles';
const styles = (theme: Theme) => createStyles({
});
interface XxxProps extends WithStyles<typeof styles> {
theme: Theme,
};
interface XxxState {
};
const Xxx = withStyles(styles, { withTheme: true })(
class extends React.Component<XxxProps, XxxState> {
constructor(props: XxxProps)
{
super(props);
this.state = {
};
}
render() {
return (
<div/>
);
}
}
);
export default Xxx;