Clean shutdown, stability

This commit is contained in:
Robin Davies
2024-09-21 13:18:00 -04:00
parent 951a7a73de
commit 2b13dc1c96
30 changed files with 820 additions and 358 deletions
+46 -22
View File
@@ -6,7 +6,7 @@ import { PiPedalModel, PiPedalModelFactory, State } from './PiPedalModel';
import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar';
import Divider from '@mui/material/Divider';
import ArrowBackIcon from '@mui/icons-material//ArrowBack';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import JackHostStatus from './JackHostStatus';
import { PiPedalError } from './PiPedalError';
@@ -16,12 +16,9 @@ import { Theme, createStyles } from '@mui/material/styles';
import { withStyles, WithStyles } from '@mui/styles';
import Slide, { SlideProps } from '@mui/material/Slide';
interface AboutDialogProps extends WithStyles<typeof styles> {
open: boolean;
onClose: () => void;
};
interface AboutDialogState {
@@ -76,6 +73,15 @@ const styles = (theme: Theme) => createStyles({
},
secondaryItem: {
},
can_select: {
userSelect: "text",
cursor: "text"
},
no_select: {
userSelect: "none",
cursor: "text"
}
@@ -173,7 +179,7 @@ const AboutDialog = withStyles(styles, { withTheme: true })(
this.updateNotifications();
}
componentDidUpdate(prevProps: Readonly<AboutDialogProps>, prevState: Readonly<AboutDialogState>, snapshot: any): void {
super.componentDidUpdate?.(prevProps,prevState,snapshot);
super.componentDidUpdate?.(prevProps, prevState, snapshot);
this.updateNotifications();
}
@@ -185,6 +191,11 @@ const AboutDialog = withStyles(styles, { withTheme: true })(
render() {
let classes = this.props.classes;
let addressKey = 0;
let serverVersion = this.model.serverVersion?.serverVersion ?? "";
let nPos = serverVersion.indexOf(' ');
if (nPos !== -1) {
serverVersion = serverVersion.substring(nPos + 1);
}
return (
<DialogEx tag="about" fullScreen open={this.props.open}
onClose={() => { this.props.onClose() }} TransitionComponent={Transition}
@@ -210,13 +221,16 @@ const AboutDialog = withStyles(styles, { withTheme: true })(
overflowX: "hidden", overflowY: "auto", userSelect: "text"
}}
>
<div style={{ margin: 24 }}>
<Typography noWrap display="block" variant="h6" color="textPrimary">
PiPedal <span style={{ fontSize: "0.7em" }}>
{(this.model.serverVersion ? this.model.serverVersion.serverVersion : "")
+ (this.model.serverVersion?.debug ? " (Debug)" : "")}
</span>
</Typography>
<div id="debug_info" className={classes.can_select} style={{ margin: 24 }}>
<div style={{ display: "flex", flexFlow: "row nowrap" }}>
<Typography noWrap display="block" variant="h6" color="textPrimary" style={{flexGrow: 1, flexShrink: 1}}>
PiPedal <span style={{ fontSize: "0.7em" }}>
{serverVersion
+ (this.model.serverVersion?.debug ? " (Debug)" : "")}
</span>
</Typography>
</div>
<Typography noWrap display="block" variant="body2" style={{ marginBottom: 12 }} >
Copyright &#169; 2022-2024 Robin Davies.
</Typography>
@@ -235,24 +249,34 @@ const AboutDialog = withStyles(styles, { withTheme: true })(
<Typography noWrap display="block" variant="caption" >
ADDRESSES
</Typography>
<div style={{marginBottom: 16}}>
{
this.model.serverVersion?.webAddresses.map((address) =>
(
<Typography key={addressKey++} display="block" variant="body2" style={{ marginBottom: 0, marginLeft: 24 }} >
{address}
</Typography>
))
<div style={{ marginBottom: 16 }}>
{
this.model.serverVersion?.webAddresses.map((address) =>
(
<Typography key={addressKey++} display="block" variant="body2" style={{ marginBottom: 0, marginLeft: 24 }} >
{address}
</Typography>
))
}
}
</div>
<Divider />
<Typography noWrap display="block" variant="caption" >
SERVER OS
</Typography>
<div style={{ marginBottom: 16 }}>
<Typography display="block" variant="body2" style={{ marginBottom: 0, marginLeft: 24 }} >
{this.model.serverVersion?.osVersion ?? ""}
</Typography>
</div>
</div><div>
<Divider />
<Typography display="block" variant="caption" >
LEGAL NOTICES
</Typography>
<div dangerouslySetInnerHTML={{ __html: this.state.openSourceNotices }} style={{ fontSize: "0.8em",maxWidth: 400 }}>
<div dangerouslySetInnerHTML={{ __html: this.state.openSourceNotices }} style={{ fontSize: "0.8em", maxWidth: 400 }}>
</div>
</div>
+8 -4
View File
@@ -59,11 +59,13 @@ const theme = createTheme(
styleOverrides: {
containedPrimary: {
borderRadius: '9999px',
paddingLeft: "14px", paddingRight: "16px"
paddingLeft: "16px", paddingRight: "16px",
textTransform: "none"
},
containedSecondary: {
borderRadius: '9999px',
paddingLeft: "14px", paddingRight: "16px"
paddingLeft: "16px", paddingRight: "16px",
textTransform: "none"
}
},
@@ -103,11 +105,13 @@ const theme = createTheme(
styleOverrides: {
containedPrimary: {
borderRadius: '9999px',
paddingLeft: "14px", paddingRight: "16px"
paddingLeft: "16px", paddingRight: "16px",
textTransform: "none"
},
containedSecondary: {
borderRadius: '9999px',
paddingLeft: "14px", paddingRight: "16px"
paddingLeft: "16px", paddingRight: "16px",
textTransform: "none"
}
},
+92 -31
View File
@@ -48,7 +48,7 @@ import SettingsDialog from './SettingsDialog';
import AboutDialog from './AboutDialog';
import BankDialog from './BankDialog';
import { PiPedalModelFactory, PiPedalModel, State, ZoomedControlInfo,wantsLoadingScreen } from './PiPedalModel';
import { PiPedalModelFactory, PiPedalModel, State, ZoomedControlInfo, wantsLoadingScreen } from './PiPedalModel';
import ZoomedUiControl from './ZoomedUiControl'
import MainPage from './MainPage';
import DialogContent from '@mui/material/DialogContent';
@@ -67,7 +67,7 @@ import { ReactComponent as SaveBankAsIcon } from './svg/ic_save_bank_as.svg';
import { ReactComponent as EditBanksIcon } from './svg/ic_edit_banks.svg';
import { ReactComponent as SettingsIcon } from './svg/ic_settings.svg';
import { ReactComponent as HelpOutlineIcon } from './svg/ic_help_outline.svg';
import DialogEx from './DialogEx';
import DialogEx, { DialogStackState } from './DialogEx';
const appStyles = (theme: Theme) => createStyles({
@@ -139,10 +139,11 @@ const appStyles = (theme: Theme) => createStyles({
marginTop: 0,
fontWeight: 500,
fontSize: "13pt",
maxWidth: 350,
opacity: 1,
zIndex: 2010,
paddingTop: 12,
gravity: "center",
textAlign: "center"
},
errorMessageBox: {
@@ -164,8 +165,6 @@ const appStyles = (theme: Theme) => createStyles({
position: "relative",
top: "20%",
color: isDarkMode() ? theme.palette.text.secondary : "#888",
marginLeft: "auto",
marginRight: "auto",
// border: "3px solid #888",
borderRadius: "12px",
padding: "12px",
@@ -351,9 +350,40 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
this.handleCloseAlert = this.handleCloseAlert.bind(this);
this.banksChangedHandler = this.banksChangedHandler.bind(this);
this.showStatusMonitorHandler = this.showStatusMonitorHandler.bind(this);
this.onPopStateHandler = this.onPopStateHandler.bind(this);
}
showDrawer() {
if (!this.state.isDrawerOpen) {
this.setState({ isDrawerOpen: true })
this.pushOpenMenuState();
}
}
hideDrawer(loadingDialog: boolean = false) {
if (!loadingDialog && window.location.hash === "#menu") {
window.history.back();
} else {
this.setState({ isDrawerOpen: false })
}
}
pushOpenMenuState() {
let stackState = { tag: "OpenMenu", previousState: null };
window.history.replaceState(stackState, "", window.location.href);
window.history.pushState({}, "", "#menu");
}
onPopStateHandler(ev: PopStateEvent) {
let stackState: DialogStackState | null = ev.state as (DialogStackState | null);
if (stackState && stackState.tag === "OpenMenu") {
if (this.state.isDrawerOpen) {
this.setState({ isDrawerOpen: false });
}
ev.stopPropagation();
window.history.replaceState(stackState.previousState, "", window.location.href);
return true;
}
return false;
}
onOpenBank(bankId: number) {
this.model_.openBank(bankId)
@@ -524,11 +554,9 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
return undefined;
}
promptForUpdateHandler(newValue: boolean) {
if (this.model_.enableAutoUpdate)
{
if (this.state.updateDialogOpen !== newValue)
{
this.setState({updateDialogOpen: newValue});
if (this.model_.enableAutoUpdate) {
if (this.state.updateDialogOpen !== newValue) {
this.setState({ updateDialogOpen: newValue });
}
}
}
@@ -537,6 +565,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
super.componentDidMount();
window.addEventListener("beforeunload", this.beforeUnloadListener);
window.addEventListener("unload", this.unloadListener);
window.addEventListener("popstate", this.onPopStateHandler);
this.model_.errorMessage.addOnChangedHandler(this.errorChangeHandler_);
this.model_.state.addOnChangedHandler(this.stateChangeHandler_);
@@ -546,6 +575,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
this.model_.showStatusMonitor.addOnChangedHandler(this.showStatusMonitorHandler);
this.model_.promptForUpdate.addOnChangedHandler(this.promptForUpdateHandler);
this.alertMessageChangedHandler();
}
@@ -568,6 +598,9 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
super.componentWillUnmount();
window.removeEventListener("beforeunload", this.beforeUnloadListener);
window.removeEventListener("popstate", this.onPopStateHandler);
this.model_.promptForUpdate.removeOnChangedHandler(this.promptForUpdateHandler);
this.model_.errorMessage.removeOnChangedHandler(this.errorChangeHandler_);
this.model_.state.removeOnChangedHandler(this.stateChangeHandler_);
@@ -634,12 +667,6 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
}
}
showDrawer() {
this.setState({ isDrawerOpen: true })
}
hideDrawer() {
this.setState({ isDrawerOpen: false })
}
shortBankList(banks: BankIndex): BankIndexEntry[] {
let n = this.state.bankDisplayItems;
let entries = banks.entries;
@@ -796,7 +823,11 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
{
showBankSelectDialog && (
<ListItem button key={'bankDOTDOTDOT'} selected={false}
onClick={() => this.handleDrawerSelectBank()}
onClick={(ev) => {
ev.stopPropagation();
this.hideDrawer(true);
this.handleDrawerSelectBank();
}}
>
<ListItemText primary={"..."} />
@@ -808,19 +839,34 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
</List>
<Divider />
<List>
<ListItem button key='RenameBank' onClick={() => { this.handleDrawerRenameBank() }}>
<ListItem button key='RenameBank'
onClick={(ev) => {
ev.stopPropagation();
this.hideDrawer(true);
this.handleDrawerRenameBank()
}}>
<ListItemIcon >
<RenameOutlineIcon color='inherit' className={classes.menuIcon} />
</ListItemIcon>
<ListItemText primary='Rename bank' />
</ListItem>
<ListItem button key='SaveBank' onClick={() => { this.handleDrawerSaveBankAs() }} >
<ListItem button key='SaveBank'
onClick={(ev) => {
ev.stopPropagation();
this.hideDrawer(true);
this.handleDrawerSaveBankAs();
}} >
<ListItemIcon>
<SaveBankAsIcon color="inherit" className={classes.menuIcon} />
</ListItemIcon>
<ListItemText primary='Save as new bank' />
</ListItem>
<ListItem button key='EditBanks' onClick={() => { this.handleDrawerManageBanks(); }}>
<ListItem button key='EditBanks'
onClick={(ev) => {
ev.stopPropagation();
this.hideDrawer(true);
this.handleDrawerManageBanks();
}}>
<ListItemIcon>
<EditBanksIcon color="inherit" className={classes.menuIcon} />
</ListItemIcon>
@@ -829,19 +875,34 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
</List>
<Divider />
<List>
<ListItem button key='Settings' onClick={() => { this.handleDrawerSettingsClick() }}>
<ListItem button key='Settings'
onClick={(ev) => {
ev.stopPropagation();
this.hideDrawer(true);
this.handleDrawerSettingsClick()
}}>
<ListItemIcon>
<SettingsIcon color="inherit" className={classes.menuIcon} />
</ListItemIcon>
<ListItemText primary='Settings' />
</ListItem>
<ListItem button key='About' onClick={() => { this.handleDrawerAboutClick() }}>
<ListItem button key='About'
onClick={(ev) => {
ev.stopPropagation();
this.hideDrawer(true);
this.handleDrawerAboutClick();
}}>
<ListItemIcon>
<HelpOutlineIcon color="inherit" className={classes.menuIcon} />
</ListItemIcon>
<ListItemText primary='About' />
</ListItem>
<ListItem button key='Donations' onClick={() => { this.handleDrawerDonationClick() }}>
<ListItem button key='Donations'
onClick={(ev) => {
ev.stopPropagation();
this.hideDrawer(true);
this.handleDrawerDonationClick();
}}>
<ListItemIcon >
<VolunteerActivismIcon className={classes.menuIcon} color="inherit" />
</ListItemIcon>
@@ -865,10 +926,10 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
</div>
</main>
<BankDialog show={this.state.bankDialogOpen} isEditDialog={this.state.editBankDialogOpen} onDialogClose={() => this.setState({ bankDialogOpen: false })} />
{ (this.state.aboutDialogOpen)&&
(
<AboutDialog open={this.state.aboutDialogOpen} onClose={() => this.setState({ aboutDialogOpen: false })} />
)}
{(this.state.aboutDialogOpen) &&
(
<AboutDialog open={this.state.aboutDialogOpen} onClose={() => this.setState({ aboutDialogOpen: false })} />
)}
<SettingsDialog
open={this.state.isSettingsDialogOpen}
onboarding={this.state.onboarding}
@@ -931,7 +992,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
<div className={classes.errorContent} style={{
display: (
wantsLoadingScreen(this.state.displayState)
? "block" : "none"
? "block" : "none"
)
}}
>
@@ -941,7 +1002,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
<div className={classes.loadingBoxItem}>
<CircularProgress color="inherit" className={classes.loadingBoxItem} />
</div>
<Typography noWrap variant="body2" className={classes.progressText}>
<Typography display="block" noWrap variant="body2" className={classes.progressText}>
{this.getReloadingMessage()}
</Typography>
</div>
@@ -961,7 +1022,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
</p>
</div>
<div style={{ paddingTop: 50, paddingLeft: 36, textAlign: "left" }}>
<Button variant='contained' color="primary"
<Button variant='contained' color="primary"
onClick={() => this.handleReload()} >
Reload
</Button>
+45 -23
View File
@@ -31,21 +31,17 @@ interface DialogExState {
}
class DialogStackEntry {
constructor(tag: string)
constructor(dialog: DialogEx)
{
this.tag = tag;
this.tag = dialog.props.tag;
this.dialog = dialog;
}
tag: string;
dialog: DialogEx;
};
let dialogStack: DialogStackEntry[] = [];
function peekDialogStack(): string {
if (dialogStack.length !== 0) {
return dialogStack[dialogStack.length-1].tag;
}
return "";
}
function popDialogStack(): void {
if (dialogStack.length !== 0)
@@ -53,12 +49,33 @@ function popDialogStack(): void {
dialogStack.splice(dialogStack.length-1,1);
}
}
function pushDialogStack(tag: string): void {
dialogStack.push(new DialogStackEntry(tag));
function pushDialogStack(dialog: DialogEx): void {
dialogStack.push(new DialogStackEntry(dialog));
}
export interface DialogStackState{
tag: String;
previousState: DialogStackState | null;
};
// Close all dialogs higher in the dialog stack than "tag".
export function removeDialogStackEntriesAbove(tag: string)
{
for (let i = dialogStack.length-1; i >= 0; --i)
{
let entry = dialogStack[i];
if (entry.tag === tag)
{
return;
}
popDialogStack();
if (entry.dialog.props.open && entry.dialog.props.onClose)
{
entry.dialog.props.onClose({}, "backdropClick");
}
}
}
class DialogEx extends React.Component<DialogExProps,DialogExState> {
constructor(props: DialogExProps)
{
@@ -75,22 +92,28 @@ class DialogEx extends React.Component<DialogExProps,DialogExState> {
hasHooks: boolean = false;
stateWasPopped: boolean = false;
handlePopState(e: any): any
handlePopState(ev: PopStateEvent): any
{
let shouldClose = (peekDialogStack() === this.props.tag);
if (shouldClose)
{
let evTag: DialogStackState | null = ev.state as DialogStackState | null;
if (evTag && evTag.tag === this.props.tag) {
removeDialogStackEntriesAbove(this.props.tag);
if (!this.stateWasPopped)
{
this.stateWasPopped = true;
popDialogStack();
if (this.props.open) {
this.props.onClose?.(e,"backdropClick");
this.props.onClose?.(ev,"backdropClick");
}
}
window.history.replaceState(evTag.previousState,"",window.location.href);
window.removeEventListener("popstate",this.handlePopState);
e.stopPropagation();
if (window.location.hash === "#menu") // The menu popstate is next?
{
window.history.back(); // then clear off the menu popstate too.
}
ev.stopPropagation();
}
}
@@ -105,19 +128,18 @@ class DialogEx extends React.Component<DialogExProps,DialogExState> {
this.stateWasPopped = false;
window.addEventListener("popstate",this.handlePopState);
pushDialogStack(this.props.tag);
let state: {tag: string} = {tag: this.props.tag};
// eslint-disable-next-line no-restricted-globals
history.pushState(
state,
pushDialogStack(this);
let dialogStackState: DialogStackState = {tag: this.props.tag,previousState: window.history.state as DialogStackState | null};
window.history.replaceState(dialogStackState,"",window.location.href);
window.history.pushState(
null,
"",
"#" + this.props.tag
);
} else {
if (!this.stateWasPopped)
{
// eslint-disable-next-line no-restricted-globals
history.back();
window.history.back();
}
}
}
+31 -1
View File
@@ -793,17 +793,47 @@ export class PiPedalModel //implements PiPedalModel
return this.webSocket;
}
androidReconnectTimeout?: NodeJS.Timeout = undefined;
cancelAndroidReconnectTimer()
{
if (this.androidReconnectTimeout) {
clearTimeout(this.androidReconnectTimeout);
this.androidReconnectTimeout = undefined;
}
}
startAndroidReconnectTimer()
{
this.cancelAndroidReconnectTimer();
this.androidReconnectTimeout = setTimeout(()=>{
this.androidReconnectTimeout = undefined;
this.androidHost?.setDisconnected(true);
},20*1000);
}
onSocketConnectionLost() {
// remove all the events and subscriptions we have.
if (this.isClosed)
{
return; // page unloading. do NOT change the UI.
}
this.vuSubscriptions = [];
this.monitorPatchPropertyListeners = [];
if (this.isAndroidHosted()) {
this.androidHost?.setDisconnected(true);
// if unexpected, go back to the device browser immediately.
if (this.reconnectReason === ReconnectReason.Disconnected)
{
this.androidHost?.setDisconnected(true);
} else {
this.startAndroidReconnectTimer();
}
}
}
onSocketReconnected() {
this.cancelOnNetworkChanging();
this.cancelAndroidReconnectTimer();
if (this.isAndroidHosted()) {
this.androidHost?.setDisconnected(false);
}
+1 -1
View File
@@ -278,7 +278,7 @@ class PiPedalSocket {
ws.onerror = null;
reject("Connection closed unexpectedly.");
};
ws.onerror = (evWheent: Event) => {
ws.onerror = (evt: Event) => {
ws.onclose = null;
ws.onerror = null;
reject("Failed to connect.");
+3 -2
View File
@@ -451,7 +451,6 @@ const WifiConfigDialog = withStyles(styles, { withTheme: true })(
InputLabelProps={{
shrink: true
}}
disabled={!enabled}
/>
<div style={{ marginBottom: 8, flexGrow: 1, flexBasis: 1 }}>
<form autoComplete='off' onSubmit={() => false}> {/*Prevents chrome from saving passwords */}
@@ -477,7 +476,9 @@ const WifiConfigDialog = withStyles(styles, { withTheme: true })(
}}
InputProps={{
startAdornment:
this.props.wifiConfigSettings.hasSavedPassword && !this.state.hasPassword && !this.state.showPassword ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" : ""
!this.state.hasPassword && !this.state.showPassword ?
(this.props.wifiConfigSettings.hasSavedPassword? "(Unchanged)" : "(Required)")
: ""
,
endAdornment: (
<IconButton size="small"