Snapshots
@@ -93,7 +93,7 @@ const theme = createTheme(
|
||||
},
|
||||
secondary: {
|
||||
main: "#FF6060"
|
||||
},
|
||||
}
|
||||
},
|
||||
mainBackground: "#222",
|
||||
toolbarColor: '#222'
|
||||
|
||||
@@ -21,6 +21,7 @@ import { SyntheticEvent } from 'react';
|
||||
import { WithStyles } from '@mui/styles';
|
||||
|
||||
import './AppThemed.css';
|
||||
|
||||
import AppBar from '@mui/material/AppBar';
|
||||
import Toolbar from '@mui/material/Toolbar';
|
||||
import CssBaseline from '@mui/material/CssBaseline';
|
||||
@@ -48,7 +49,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, wantsReloadingScreen } from './PiPedalModel';
|
||||
import ZoomedUiControl from './ZoomedUiControl'
|
||||
import MainPage from './MainPage';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
@@ -67,6 +68,9 @@ 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 { ReactComponent as FxAmplifierIcon } from './svg/fx_amplifier.svg';
|
||||
import { PerformanceView } from './PerformanceView';
|
||||
|
||||
import DialogEx, { DialogStackState } from './DialogEx';
|
||||
|
||||
|
||||
@@ -264,6 +268,8 @@ type AppState = {
|
||||
errorMessage: string;
|
||||
displayState: State;
|
||||
|
||||
performanceView: boolean;
|
||||
|
||||
canFullScreen: boolean;
|
||||
isFullScreen: boolean;
|
||||
tinyToolBar: boolean;
|
||||
@@ -313,6 +319,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
||||
);
|
||||
|
||||
this.state = {
|
||||
performanceView: false,
|
||||
zoomedControlInfo: this.model_.zoomedUiControl.get(),
|
||||
isDrawerOpen: false,
|
||||
errorMessage: this.model_.errorMessage.get(),
|
||||
@@ -729,8 +736,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
||||
<div style={{
|
||||
colorScheme: isDarkMode() ? "dark" : "light", // affects scrollbar color
|
||||
minHeight: 345, minWidth: 390,
|
||||
position: "absolute", width: "100%", height: "100%", background: "#F88", userSelect: "none",
|
||||
display: "flex", flexDirection: "column", flexWrap: "nowrap",
|
||||
position: "absolute", left:0, top: 0, right:0,bottom:0,
|
||||
overscrollBehavior: this.state.isDebug ? "auto" : "none"
|
||||
}}
|
||||
onContextMenu={(e) => {
|
||||
@@ -740,234 +746,267 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
||||
}}
|
||||
>
|
||||
<CssBaseline />
|
||||
{(!this.state.tinyToolBar) ?
|
||||
(
|
||||
<AppBar position="absolute" >
|
||||
<Toolbar variant="dense" className={classes.toolBar} >
|
||||
<IconButton
|
||||
edge="start"
|
||||
aria-label="menu"
|
||||
color="inherit"
|
||||
onClick={() => { this.showDrawer() }}
|
||||
size="large">
|
||||
<MenuButton style={{ opacity: 0.75 }} />
|
||||
</IconButton>
|
||||
<div style={{ flex: "0 1 400px", minWidth: 100 }}>
|
||||
<PresetSelector />
|
||||
</div>
|
||||
<div style={{ flex: "2 2 30px" }} />
|
||||
{this.state.canFullScreen &&
|
||||
{this.state.performanceView ? (
|
||||
<PerformanceView
|
||||
onClose={() => { this.setState({ performanceView: false }); }}
|
||||
/>
|
||||
) : (
|
||||
<div style={{
|
||||
position: "absolute", width: "100%", height: "100%", userSelect: "none",
|
||||
display: "flex", flexDirection: "column", flexWrap: "nowrap"
|
||||
}}
|
||||
>
|
||||
|
||||
|
||||
{(!this.state.tinyToolBar) && !this.state.performanceView ?
|
||||
(
|
||||
<AppBar position="absolute" >
|
||||
<Toolbar variant="dense" className={classes.toolBar} >
|
||||
<IconButton
|
||||
edge="start"
|
||||
aria-label="menu"
|
||||
color="inherit"
|
||||
onClick={() => { this.showDrawer() }}
|
||||
size="large">
|
||||
<MenuButton style={{ opacity: 0.75 }} />
|
||||
</IconButton>
|
||||
<div style={{ flex: "0 1 400px", minWidth: 100 }}>
|
||||
<PresetSelector />
|
||||
</div>
|
||||
<div style={{ flex: "2 2 30px" }} />
|
||||
{this.state.canFullScreen &&
|
||||
<IconButton
|
||||
aria-label="menu"
|
||||
onClick={() => { this.toggleFullScreen(); }}
|
||||
color="inherit"
|
||||
size="large">
|
||||
{this.state.isFullScreen ? (
|
||||
<FullscreenExitIcon style={{ opacity: 0.75 }} />
|
||||
) : (
|
||||
<FullscreenIcon style={{ opacity: 0.75 }} />
|
||||
|
||||
)}
|
||||
|
||||
</IconButton>
|
||||
}
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
) : (
|
||||
<div className={classes.toolBarContent} >
|
||||
<IconButton
|
||||
style={{ position: "absolute", left: 12, top: 8, zIndex: 2 }}
|
||||
aria-label="menu"
|
||||
onClick={() => { this.toggleFullScreen(); }}
|
||||
onClick={() => { this.showDrawer() }}
|
||||
color="inherit"
|
||||
size="large">
|
||||
{this.state.isFullScreen ? (
|
||||
<FullscreenExitIcon style={{ opacity: 0.75 }} />
|
||||
) : (
|
||||
<FullscreenIcon style={{ opacity: 0.75 }} />
|
||||
|
||||
)}
|
||||
|
||||
<MenuButton />
|
||||
</IconButton>
|
||||
}
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
) : (
|
||||
<div className={classes.toolBarContent} >
|
||||
<IconButton
|
||||
style={{ position: "absolute", left: 12, top: 8, zIndex: 2 }}
|
||||
aria-label="menu"
|
||||
onClick={() => { this.showDrawer() }}
|
||||
color="inherit"
|
||||
size="large">
|
||||
<MenuButton />
|
||||
</IconButton>
|
||||
{this.state.canFullScreen && (
|
||||
<IconButton
|
||||
style={{ position: "absolute", right: 8, top: 8, zIndex: 2 }}
|
||||
aria-label="menu"
|
||||
color="inherit"
|
||||
onClick={() => { this.toggleFullScreen(); }}
|
||||
size="large">
|
||||
{this.state.isFullScreen ? (
|
||||
<FullscreenExitIcon />
|
||||
) : (
|
||||
<FullscreenIcon />
|
||||
{this.state.canFullScreen && (
|
||||
<IconButton
|
||||
style={{ position: "absolute", right: 8, top: 8, zIndex: 2 }}
|
||||
aria-label="menu"
|
||||
color="inherit"
|
||||
onClick={() => { this.toggleFullScreen(); }}
|
||||
size="large">
|
||||
{this.state.isFullScreen ? (
|
||||
<FullscreenExitIcon />
|
||||
) : (
|
||||
<FullscreenIcon />
|
||||
|
||||
)}
|
||||
|
||||
</IconButton>
|
||||
)}
|
||||
|
||||
</IconButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<TemporaryDrawer position='left' title="PiPedal"
|
||||
is_open={this.state.isDrawerOpen} onClose={() => { this.hideDrawer(); }} >
|
||||
<ListSubheader className="listSubheader" component="div" id="xnested-list-subheader" style={{ background: "rgba(12,12,12,0.0)" }}>
|
||||
<Typography variant="caption" style={{ position: "relative", top: 15 }}>Banks</Typography></ListSubheader>
|
||||
<TemporaryDrawer position='left' title="PiPedal"
|
||||
is_open={this.state.isDrawerOpen} onClose={() => { this.hideDrawer(); }} >
|
||||
|
||||
<List >
|
||||
{
|
||||
shortBankList.map((bank) => {
|
||||
return (
|
||||
<ListItem button key={'bank' + bank.instanceId} selected={bank.instanceId === this.state.banks.selectedBank}
|
||||
onClick={() => this.onOpenBank(bank.instanceId)}
|
||||
>
|
||||
|
||||
<ListItemText primary={bank.name} />
|
||||
</ListItem>
|
||||
|
||||
);
|
||||
})
|
||||
}
|
||||
{
|
||||
showBankSelectDialog && (
|
||||
<ListItem button key={'bankDOTDOTDOT'} selected={false}
|
||||
<List>
|
||||
<ListItem button key='PerformanceView'
|
||||
onClick={(ev) => {
|
||||
ev.stopPropagation();
|
||||
this.hideDrawer(true);
|
||||
this.handleDrawerSelectBank();
|
||||
}}
|
||||
>
|
||||
|
||||
<ListItemText primary={"..."} />
|
||||
this.setState({ performanceView: true });
|
||||
}}>
|
||||
<ListItemIcon >
|
||||
<FxAmplifierIcon color='inherit' className={classes.menuIcon} style={{ width: 24, height: 24 }} />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary='Performance View' />
|
||||
</ListItem>
|
||||
</List>
|
||||
<Divider />
|
||||
<ListSubheader className="listSubheader" component="div" id="xnested-list-subheader" style={{ lineHeight: "24px", height: 24, background: "rgba(12,12,12,0.0)" }}
|
||||
disableSticky={true}
|
||||
>
|
||||
<Typography variant="caption" style={{}}>Banks</Typography></ListSubheader>
|
||||
|
||||
<List >
|
||||
{
|
||||
shortBankList.map((bank) => {
|
||||
return (
|
||||
<ListItem button key={'bank' + bank.instanceId} selected={bank.instanceId === this.state.banks.selectedBank}
|
||||
onClick={() => this.onOpenBank(bank.instanceId)}
|
||||
>
|
||||
|
||||
<ListItemText primary={bank.name} />
|
||||
</ListItem>
|
||||
|
||||
);
|
||||
})
|
||||
}
|
||||
{
|
||||
showBankSelectDialog && (
|
||||
<ListItem button key={'bankDOTDOTDOT'} selected={false}
|
||||
onClick={(ev) => {
|
||||
ev.stopPropagation();
|
||||
this.hideDrawer(true);
|
||||
this.handleDrawerSelectBank();
|
||||
}}
|
||||
>
|
||||
|
||||
<ListItemText primary={"..."} />
|
||||
</ListItem>
|
||||
|
||||
|
||||
)
|
||||
}
|
||||
</List>
|
||||
<Divider />
|
||||
<List>
|
||||
<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={(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={(ev) => {
|
||||
ev.stopPropagation();
|
||||
this.hideDrawer(true);
|
||||
this.handleDrawerManageBanks();
|
||||
}}>
|
||||
<ListItemIcon>
|
||||
<EditBanksIcon color="inherit" className={classes.menuIcon} />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary='Manage banks...' />
|
||||
</ListItem>
|
||||
</List>
|
||||
<Divider />
|
||||
<List>
|
||||
<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={(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={(ev) => {
|
||||
ev.stopPropagation();
|
||||
this.hideDrawer(true);
|
||||
this.handleDrawerDonationClick();
|
||||
}}>
|
||||
<ListItemIcon >
|
||||
<VolunteerActivismIcon className={classes.menuIcon} color="inherit" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary='Donations' />
|
||||
</ListItem>
|
||||
</List>
|
||||
)
|
||||
}
|
||||
</List>
|
||||
<Divider />
|
||||
<List>
|
||||
<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={(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={(ev) => {
|
||||
ev.stopPropagation();
|
||||
this.hideDrawer(true);
|
||||
this.handleDrawerManageBanks();
|
||||
}}>
|
||||
<ListItemIcon>
|
||||
<EditBanksIcon color="inherit" className={classes.menuIcon} />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary='Manage banks...' />
|
||||
</ListItem>
|
||||
</List>
|
||||
<Divider />
|
||||
<List>
|
||||
<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={(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={(ev) => {
|
||||
ev.stopPropagation();
|
||||
this.hideDrawer(true);
|
||||
this.handleDrawerDonationClick();
|
||||
}}>
|
||||
<ListItemIcon >
|
||||
<VolunteerActivismIcon className={classes.menuIcon} color="inherit" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary='Donations' />
|
||||
</ListItem>
|
||||
</List>
|
||||
|
||||
</TemporaryDrawer>
|
||||
{!this.state.tinyToolBar && (
|
||||
<Toolbar className={classes.toolBarSpacer} variant="dense"
|
||||
/>
|
||||
)}
|
||||
<main className={classes.mainFrame} >
|
||||
<div className={classes.mainSizingPosition}>
|
||||
<div className={classes.heroContent}>
|
||||
{(this.state.displayState !== State.Loading) && (
|
||||
<MainPage hasTinyToolBar={this.state.tinyToolBar} />
|
||||
</TemporaryDrawer>
|
||||
{!this.state.tinyToolBar && (
|
||||
<Toolbar className={classes.toolBarSpacer} variant="dense"
|
||||
/>
|
||||
)}
|
||||
<main className={classes.mainFrame} >
|
||||
<div className={classes.mainSizingPosition}>
|
||||
<div className={classes.heroContent}>
|
||||
{(this.state.displayState !== State.Loading) &&
|
||||
(
|
||||
<MainPage hasTinyToolBar={this.state.tinyToolBar} enableStructureEditing={true} />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
</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 })} />
|
||||
)}
|
||||
</div>
|
||||
<SettingsDialog
|
||||
open={this.state.isSettingsDialogOpen}
|
||||
onboarding={this.state.onboarding}
|
||||
onClose={() => this.handleSettingsDialogClose()} />
|
||||
<RenameDialog
|
||||
open={this.state.renameBankDialogOpen || this.state.saveBankAsDialogOpen}
|
||||
defaultName={this.model_.banks.get().getSelectedEntryName()}
|
||||
acceptActionName={this.state.renameBankDialogOpen ? "Rename" : "Save as"}
|
||||
onClose={() => {
|
||||
this.setState({
|
||||
renameBankDialogOpen: false,
|
||||
saveBankAsDialogOpen: false
|
||||
})
|
||||
}}
|
||||
onOk={(text: string) => {
|
||||
if (this.state.renameBankDialogOpen) {
|
||||
this.handleBankRenameOk(text);
|
||||
} else if (this.state.saveBankAsDialogOpen) {
|
||||
this.handleSaveBankAsOk(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
||||
<ZoomedUiControl
|
||||
dialogOpen={this.state.zoomedControlOpen}
|
||||
controlInfo={this.state.zoomedControlInfo}
|
||||
onDialogClose={() => { this.setState({ zoomedControlOpen: false }); }}
|
||||
onDialogClosed={() => { this.model_.zoomedUiControl.set(undefined); }
|
||||
}
|
||||
/>
|
||||
<UpdateDialog open={this.state.updateDialogOpen} />
|
||||
{this.state.showStatusMonitor && (<JackStatusView />)}
|
||||
</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 })} />
|
||||
)}
|
||||
<SettingsDialog
|
||||
open={this.state.isSettingsDialogOpen}
|
||||
onboarding={this.state.onboarding}
|
||||
onClose={() => this.handleSettingsDialogClose()} />
|
||||
<RenameDialog
|
||||
open={this.state.renameBankDialogOpen || this.state.saveBankAsDialogOpen}
|
||||
defaultName={this.model_.banks.get().getSelectedEntryName()}
|
||||
acceptActionName={this.state.renameBankDialogOpen ? "Rename" : "Save as"}
|
||||
onClose={() => {
|
||||
this.setState({
|
||||
renameBankDialogOpen: false,
|
||||
saveBankAsDialogOpen: false
|
||||
})
|
||||
}}
|
||||
onOk={(text: string) => {
|
||||
if (this.state.renameBankDialogOpen) {
|
||||
this.handleBankRenameOk(text);
|
||||
} else if (this.state.saveBankAsDialogOpen) {
|
||||
this.handleSaveBankAsOk(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
||||
<ZoomedUiControl
|
||||
dialogOpen={this.state.zoomedControlOpen}
|
||||
controlInfo={this.state.zoomedControlInfo}
|
||||
onDialogClose={() => { this.setState({ zoomedControlOpen: false }); }}
|
||||
onDialogClosed={() => { this.model_.zoomedUiControl.set(undefined); }
|
||||
}
|
||||
/>
|
||||
<UpdateDialog open={this.state.updateDialogOpen} />
|
||||
{this.state.showStatusMonitor && (<JackStatusView />)}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
<DialogEx
|
||||
tag="Alert"
|
||||
<DialogEx tag="Alert"
|
||||
open={this.state.alertDialogOpen}
|
||||
onClose={this.handleCloseAlert}
|
||||
aria-describedby="alert-dialog-description"
|
||||
@@ -989,24 +1028,6 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
||||
</DialogEx>
|
||||
|
||||
|
||||
<div className={classes.errorContent} style={{
|
||||
display: (
|
||||
wantsLoadingScreen(this.state.displayState)
|
||||
? "block" : "none"
|
||||
)
|
||||
}}
|
||||
>
|
||||
<div className={classes.errorContentMask} />
|
||||
|
||||
<div className={classes.loadingBox}>
|
||||
<div className={classes.loadingBoxItem}>
|
||||
<CircularProgress color="inherit" className={classes.loadingBoxItem} />
|
||||
</div>
|
||||
<Typography display="block" noWrap variant="body2" className={classes.progressText}>
|
||||
{this.getReloadingMessage()}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.errorContent} style={{ display: this.state.displayState === State.Error ? "flex" : "none" }}
|
||||
onMouseDown={preventDefault} onKeyDown={preventDefault}
|
||||
>
|
||||
@@ -1034,6 +1055,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
||||
<div style={{ flex: "5 5 auto", height: 20 }} > </div>
|
||||
|
||||
</div>
|
||||
{/* initial load mask*/}
|
||||
<div className={classes.errorContent} style={{ display: this.state.displayState === State.Loading ? "block" : "none" }}>
|
||||
<div className={classes.errorContentMask} />
|
||||
<div className={classes.loadingBox}>
|
||||
@@ -1045,11 +1067,30 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
{/* Reloading mask */}
|
||||
<div className={classes.errorContent} style={{
|
||||
zIndex: 501,
|
||||
display: (
|
||||
wantsReloadingScreen(this.state.displayState)
|
||||
? "block" : "none"
|
||||
)
|
||||
}}
|
||||
>
|
||||
<div className={classes.errorContentMask} />
|
||||
|
||||
<div className={classes.loadingBox}>
|
||||
<div className={classes.loadingBoxItem}>
|
||||
<CircularProgress color="inherit" className={classes.loadingBoxItem} />
|
||||
</div>
|
||||
<Typography display="block" noWrap variant="body2" className={classes.progressText}>
|
||||
{this.getReloadingMessage()}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div >
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
export default AppThemed;
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import ButtonBase from '@mui/material/ButtonBase';
|
||||
import Menu from '@mui/material/Menu';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import { colorKeys, getBackgroundColor } from './MaterialColors';
|
||||
import { useTheme } from '@mui/styles';
|
||||
|
||||
const colors: string[] = colorKeys;
|
||||
|
||||
interface ColorDropdownButtonProps {
|
||||
currentColor?: string;
|
||||
onColorChange: (color: string) => void;
|
||||
}
|
||||
|
||||
const ColorDropdownButton: React.FC<ColorDropdownButtonProps> = ({
|
||||
currentColor = colors[0],
|
||||
onColorChange,
|
||||
}) => {
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
const [selectedColor, setSelectedColor] = useState<string>(currentColor);
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedColor(currentColor);
|
||||
}, [currentColor]);
|
||||
|
||||
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const handleColorSelect = (color: string) => {
|
||||
handleClose();
|
||||
setSelectedColor(color);
|
||||
onColorChange(color);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ButtonBase
|
||||
onClick={handleClick}
|
||||
style={{
|
||||
width: 48, height: 48, padding: 12, borderRadius: 18
|
||||
}}
|
||||
>
|
||||
<div style={{
|
||||
width: 24, height: 24,
|
||||
background: getBackgroundColor(selectedColor),
|
||||
borderRadius: 6,
|
||||
borderColor: theme.palette.text.secondary,
|
||||
borderWidth: 1,
|
||||
borderStyle: "solid"
|
||||
}} />
|
||||
|
||||
</ButtonBase>
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
open={anchorEl !== null}
|
||||
onClose={handleClose}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", flexFlow: "row wrap", width: 56 * 4 + 2 }}>
|
||||
{colors.map((color) => {
|
||||
let selected = color === selectedColor;
|
||||
return (
|
||||
<MenuItem key={color} onClick={() => handleColorSelect(color)}
|
||||
style={{
|
||||
width: 48, height: 48, borderRadius: 6, margin: 4,
|
||||
borderStyle: "solid",
|
||||
borderWidth: selected ? 2 : 0.25,
|
||||
borderColor: color === selectedColor ?
|
||||
theme.palette.text.primary
|
||||
: theme.palette.text.secondary,
|
||||
backgroundColor: getBackgroundColor(color)
|
||||
}}
|
||||
></MenuItem>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ColorDropdownButton;
|
||||
@@ -24,6 +24,7 @@ import Dialog, {DialogProps} from '@mui/material/Dialog';
|
||||
|
||||
interface DialogExProps extends DialogProps {
|
||||
tag: string;
|
||||
fullwidth?: boolean;
|
||||
}
|
||||
|
||||
interface DialogExState {
|
||||
@@ -171,7 +172,7 @@ class DialogEx extends React.Component<DialogExProps,DialogExState> {
|
||||
render() {
|
||||
let { tag,onClose, ...extra} = this.props;
|
||||
return (
|
||||
<Dialog {...extra} onClose={(event,reason)=>{ this.myOnClose(event,reason);}}>
|
||||
<Dialog fullWidth={this.props.fullWidth??false} maxWidth={this.props.fullWidth ? false: undefined} {...extra} onClose={(event,reason)=>{ this.myOnClose(event,reason);}}>
|
||||
{this.props.children}
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@@ -319,6 +319,7 @@ const GxTunerControl =
|
||||
return this.makeTick(pitchInfo,cents,r0,r1,"#666", width);
|
||||
}
|
||||
|
||||
nextKey() { return "key" + (this.keyCounter++)}
|
||||
makeTick(pitchInfo: PitchInfo, cents: number, r0: number, r1: number, stroke: string,width: number): React.ReactNode
|
||||
{
|
||||
let range = pitchInfo.semitoneCents;
|
||||
@@ -335,7 +336,7 @@ const GxTunerControl =
|
||||
let cy = NEEDLE_CY;
|
||||
let path = new SvgPathBuilder().moveTo(r0*sin_+cx,r0*cos_+cy).lineTo(r1*sin_+cx,r1*cos_+cy).toString();
|
||||
|
||||
return (<path d={path} stroke={stroke} strokeWidth={width+""} />);
|
||||
return (<path key={this.nextKey()} d={path} stroke={stroke} strokeWidth={width+""} />);
|
||||
}
|
||||
|
||||
lastValidTime: number = 0;
|
||||
@@ -406,7 +407,10 @@ const GxTunerControl =
|
||||
)
|
||||
}
|
||||
|
||||
keyCounter: number = 0;
|
||||
|
||||
render() {
|
||||
this.keyCounter = 0;
|
||||
let textColor = isDarkMode() ? "#999": "#444";
|
||||
return (<div ref={this.refRoot} style={{width: DIAL_WIDTH, height: DIAL_HEIGHT, fontSize: "2em", fontWeight: 700, position: "relative",
|
||||
boxShadow: isDarkMode() ?
|
||||
|
||||
@@ -292,8 +292,11 @@ export const LoadPluginDialog =
|
||||
}
|
||||
);
|
||||
}
|
||||
mounted: boolean = false;
|
||||
|
||||
componentDidMount() {
|
||||
super.componentDidMount();
|
||||
this.mounted = true;
|
||||
this.updateWindowSize();
|
||||
window.addEventListener('resize', this.updateWindowSize);
|
||||
|
||||
@@ -307,10 +310,12 @@ export const LoadPluginDialog =
|
||||
});
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.cancelSearchTimeout();
|
||||
this.model.ui_plugins.removeOnChangedHandler(this.handlePluginsChanged);
|
||||
this.model.favorites.removeOnChangedHandler(this.handleFavoritesChanged);
|
||||
super.componentWillUnmount();
|
||||
window.removeEventListener('resize', this.updateWindowSize);
|
||||
this.mounted = false;
|
||||
super.componentWillUnmount();
|
||||
}
|
||||
|
||||
componentDidUpdate(oldProps: PluginGridProps) {
|
||||
@@ -574,6 +579,10 @@ export const LoadPluginDialog =
|
||||
|
||||
|
||||
handleSearchStringReady() {
|
||||
if (!this.mounted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (this.changedSearchString !== undefined) {
|
||||
this.requestScrollTo();
|
||||
this.setState({
|
||||
@@ -584,12 +593,17 @@ export const LoadPluginDialog =
|
||||
this.hSearchTimeout = undefined;
|
||||
}
|
||||
|
||||
|
||||
handleSearchStringChanged(text: string): void {
|
||||
cancelSearchTimeout()
|
||||
{
|
||||
if (this.hSearchTimeout) {
|
||||
clearTimeout(this.hSearchTimeout);
|
||||
this.hSearchTimeout = undefined;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
handleSearchStringChanged(text: string): void {
|
||||
this.cancelSearchTimeout();
|
||||
this.changedSearchString = text;
|
||||
this.hSearchTimeout = setTimeout(
|
||||
this.handleSearchStringReady,
|
||||
|
||||
@@ -45,13 +45,25 @@ import PluginInfoDialog from './PluginInfoDialog';
|
||||
import { GetControlView } from './ControlViewFactory';
|
||||
import MidiBindingsDialog from './MidiBindingsDialog';
|
||||
import PluginPresetSelector from './PluginPresetSelector';
|
||||
import {ReactComponent as OldDeleteIcon} from "./svg/old_delete_outline_24dp.svg";
|
||||
import {ReactComponent as MidiIcon} from "./svg/ic_midi.svg";
|
||||
import {isDarkMode} from './DarkMode';
|
||||
import { ReactComponent as OldDeleteIcon } from "./svg/old_delete_outline_24dp.svg";
|
||||
import { ReactComponent as MidiIcon } from "./svg/ic_midi.svg";
|
||||
import { isDarkMode } from './DarkMode';
|
||||
import { ReactComponent as Snapshot0Icon } from "./svg/snapshot_0.svg";
|
||||
import { ReactComponent as Snapshot1Icon } from "./svg/snapshot_1.svg";
|
||||
import { ReactComponent as Snapshot2Icon } from "./svg/snapshot_2.svg";
|
||||
import { ReactComponent as Snapshot3Icon } from "./svg/snapshot_3.svg";
|
||||
import { ReactComponent as Snapshot4Icon } from "./svg/snapshot_4.svg";
|
||||
import { ReactComponent as Snapshot5Icon } from "./svg/snapshot_5.svg";
|
||||
import { ReactComponent as Snapshot6Icon } from "./svg/snapshot_6.svg";
|
||||
|
||||
const SPLIT_CONTROLBAR_THRESHHOLD = 650;
|
||||
import SnapshotDialog from './SnapshotDialog';
|
||||
|
||||
|
||||
const SPLIT_CONTROLBAR_THRESHHOLD = 750;
|
||||
const DISPLAY_AUTHOR_THRESHHOLD = 750;
|
||||
const DISPLAY_AUTHOR_SPLIT_THRESHOLD = 500;
|
||||
const HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK = 500;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const HORIZONTAL_LAYOUT_MQ = "@media (max-height: " + HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK + "px)";
|
||||
|
||||
@@ -86,7 +98,7 @@ const styles = ({ palette }: Theme) => createStyles({
|
||||
flex: "0 0 162px", width: "100%", height: 162, overflowY: "hidden",
|
||||
},
|
||||
title: { fontSize: "1.1em", fontWeight: 700, marginRight: 8, textOverflow: "ellipsis", whiteSpace: "nowrap", opacity: 0.75 },
|
||||
author: { fontWeight: 500, marginRight: 8, textOverflow: "ellipsis", whiteSpace: "nowrap",opacity: 0.75 }
|
||||
author: { fontWeight: 500, marginRight: 8, textOverflow: "ellipsis", whiteSpace: "nowrap", opacity: 0.75 }
|
||||
});
|
||||
|
||||
|
||||
@@ -94,14 +106,18 @@ const styles = ({ palette }: Theme) => createStyles({
|
||||
interface MainProps extends WithStyles<typeof styles> {
|
||||
hasTinyToolBar: boolean;
|
||||
theme: Theme;
|
||||
enableStructureEditing: boolean;
|
||||
}
|
||||
|
||||
interface MainState {
|
||||
selectedPedal: number;
|
||||
selectedSnapshot: number;
|
||||
loadDialogOpen: boolean;
|
||||
snapshotDialogOpen: boolean;
|
||||
pedalboard: Pedalboard;
|
||||
addMenuAnchorEl: HTMLElement | null;
|
||||
splitControlBar: boolean;
|
||||
displayAuthor: boolean;
|
||||
horizontalScrollLayout: boolean;
|
||||
showMidiBindingsDialog: boolean;
|
||||
screenHeight: number;
|
||||
@@ -111,10 +127,21 @@ interface MainState {
|
||||
|
||||
export const MainPage =
|
||||
withStyles(styles, { withTheme: true })(
|
||||
class extends ResizeResponsiveComponent<MainProps, MainState>
|
||||
{
|
||||
class extends ResizeResponsiveComponent<MainProps, MainState> {
|
||||
model: PiPedalModel;
|
||||
|
||||
getSplitToolbar() {
|
||||
return this.windowSize.width < SPLIT_CONTROLBAR_THRESHHOLD;
|
||||
}
|
||||
getDisplayAuthor()
|
||||
{
|
||||
if (this.getSplitToolbar())
|
||||
{
|
||||
return this.windowSize.width >= DISPLAY_AUTHOR_SPLIT_THRESHOLD;
|
||||
} else {
|
||||
return this.windowSize.width >= DISPLAY_AUTHOR_THRESHHOLD;
|
||||
}
|
||||
}
|
||||
constructor(props: MainProps) {
|
||||
super(props);
|
||||
this.model = PiPedalModelFactory.getInstance();
|
||||
@@ -123,14 +150,18 @@ export const MainPage =
|
||||
|
||||
this.state = {
|
||||
selectedPedal: selectedPedal,
|
||||
selectedSnapshot: -1,
|
||||
loadDialogOpen: false,
|
||||
snapshotDialogOpen: false,
|
||||
pedalboard: pedalboard,
|
||||
addMenuAnchorEl: null,
|
||||
splitControlBar: this.windowSize.width < SPLIT_CONTROLBAR_THRESHHOLD,
|
||||
splitControlBar: this.getSplitToolbar(),
|
||||
displayAuthor: this.getDisplayAuthor(),
|
||||
horizontalScrollLayout: this.windowSize.height < HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK,
|
||||
showMidiBindingsDialog: false,
|
||||
screenHeight: this.windowSize.height
|
||||
|
||||
|
||||
};
|
||||
this.onSelectionChanged = this.onSelectionChanged.bind(this);
|
||||
this.onPedalDoubleClick = this.onPedalDoubleClick.bind(this);
|
||||
@@ -138,6 +169,7 @@ export const MainPage =
|
||||
this.onLoadOk = this.onLoadOk.bind(this);
|
||||
this.onLoadCancel = this.onLoadCancel.bind(this);
|
||||
this.onPedalboardChanged = this.onPedalboardChanged.bind(this);
|
||||
this.onSelectedSnapshotChanged = this.onSelectedSnapshotChanged.bind(this);
|
||||
this.handleEnableCurrentItemChanged = this.handleEnableCurrentItemChanged.bind(this);
|
||||
}
|
||||
|
||||
@@ -204,9 +236,16 @@ export const MainPage =
|
||||
}
|
||||
this.setState({
|
||||
pedalboard: value,
|
||||
selectedPedal: selectedItem
|
||||
selectedPedal: selectedItem,
|
||||
selectedSnapshot: value.selectedSnapshot
|
||||
});
|
||||
}
|
||||
onSelectedSnapshotChanged(selectedSnapshot: number) {
|
||||
this.setState({
|
||||
selectedSnapshot: selectedSnapshot
|
||||
});
|
||||
|
||||
}
|
||||
onDeletePedal(instanceId: number): void {
|
||||
let result = this.model.deletePedalboardPedal(instanceId);
|
||||
if (result != null) {
|
||||
@@ -217,14 +256,17 @@ export const MainPage =
|
||||
componentDidMount() {
|
||||
super.componentDidMount();
|
||||
this.model.pedalboard.addOnChangedHandler(this.onPedalboardChanged);
|
||||
this.model.selectedSnapshot.addOnChangedHandler(this.onSelectedSnapshotChanged);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.model.selectedSnapshot.removeOnChangedHandler(this.onSelectedSnapshotChanged);
|
||||
this.model.pedalboard.removeOnChangedHandler(this.onPedalboardChanged);
|
||||
super.componentWillUnmount();
|
||||
}
|
||||
updateResponsive() {
|
||||
this.setState({
|
||||
splitControlBar: this.windowSize.width < SPLIT_CONTROLBAR_THRESHHOLD,
|
||||
splitControlBar: this.getSplitToolbar(),
|
||||
displayAuthor: this.getDisplayAuthor(),
|
||||
horizontalScrollLayout: this.windowSize.height < HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK,
|
||||
screenHeight: this.windowSize.height
|
||||
});
|
||||
@@ -267,10 +309,16 @@ export const MainPage =
|
||||
let newSelectedItem = this.model.loadPedalboardPlugin(itemId, selectedUri);
|
||||
this.setState({ selectedPedal: newSelectedItem });
|
||||
}
|
||||
onSnapshotDialogOk(): void {
|
||||
this.setState({ snapshotDialogOpen: false });
|
||||
}
|
||||
|
||||
onLoadClick(e: SyntheticEvent) {
|
||||
this.setState({ loadDialogOpen: true });
|
||||
}
|
||||
onSnapshotClick() {
|
||||
this.setState({ snapshotDialogOpen: true });
|
||||
}
|
||||
|
||||
getPedalboardItem(selectedId?: number): PedalboardItem | null {
|
||||
if (selectedId === undefined) return null;
|
||||
@@ -352,8 +400,8 @@ export const MainPage =
|
||||
display: "flex", flexDirection: "row", height: 48, flexWrap: "nowrap",
|
||||
alignItems: "center"
|
||||
}}>
|
||||
<div style={{ flex: "0 1 auto",minWidth: 0 }}>
|
||||
<span style={{ color: isDarkMode()? "#F02020": "#800000" }}>
|
||||
<div style={{ flex: "0 1 auto", minWidth: 0 }}>
|
||||
<span style={{ color: isDarkMode() ? "#F02020" : "#800000" }}>
|
||||
<span className={classes.title}>{title}</span>
|
||||
</span>
|
||||
</div>
|
||||
@@ -363,13 +411,15 @@ export const MainPage =
|
||||
} else {
|
||||
return (
|
||||
<div style={{
|
||||
flex: "1 1 auto", minWidth: 0,overflow: "hidden", marginRight: 8,
|
||||
flex: "1 1 auto", minWidth: 0, overflow: "hidden", marginRight: 8,
|
||||
display: "flex", flexDirection: "row", height: 48, flexWrap: "nowrap",
|
||||
alignItems: "center"
|
||||
}}>
|
||||
<div style={{ flex: "0 1 auto", minWidth: 0,overflow: "hidden",textOverflow: "ellipsis" }}>
|
||||
<div style={{ flex: "0 1 auto", minWidth: 0, overflow: "hidden", textOverflow: "ellipsis" }}>
|
||||
<span className={classes.title}>{title}</span>
|
||||
<span className={classes.author}>{author}</span>
|
||||
{this.state.displayAuthor&&(
|
||||
<span className={classes.author}>{author}</span>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ flex: "0 0 auto", verticalAlign: "center" }}>
|
||||
<PluginInfoDialog plugin_uri={pluginUri} />
|
||||
@@ -383,6 +433,25 @@ export const MainPage =
|
||||
}
|
||||
}
|
||||
|
||||
snapshotIcon(theme: Theme, snapshotNumber: number) {
|
||||
switch (snapshotNumber + 1) {
|
||||
case 0:
|
||||
default:
|
||||
return (<Snapshot0Icon style={{ height: 24, width: 24, fill: theme.palette.text.primary, opacity: 0.6 }} />);
|
||||
case 1:
|
||||
return (<Snapshot1Icon style={{ height: 24, width: 24, fill: theme.palette.text.primary, opacity: 0.6 }} />);
|
||||
case 2:
|
||||
return (<Snapshot2Icon style={{ height: 24, width: 24, fill: theme.palette.text.primary, opacity: 0.6 }} />);
|
||||
case 3:
|
||||
return (<Snapshot3Icon style={{ height: 24, width: 24, fill: theme.palette.text.primary, opacity: 0.6 }} />);
|
||||
case 4:
|
||||
return (<Snapshot4Icon style={{ height: 24, width: 24, fill: theme.palette.text.primary, opacity: 0.6 }} />);
|
||||
case 5:
|
||||
return (<Snapshot5Icon style={{ height: 24, width: 24, fill: theme.palette.text.primary, opacity: 0.6 }} />);
|
||||
case 6:
|
||||
return (<Snapshot6Icon style={{ height: 24, width: 24, fill: theme.palette.text.primary, opacity: 0.6 }} />);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let classes = this.props.classes;
|
||||
@@ -438,6 +507,7 @@ export const MainPage =
|
||||
<div id="pedalboardScroll" className={horizontalScrollLayout ? classes.pedalboardScrollSmall : classes.pedalboardScroll}
|
||||
style={{ maxHeight: horizontalScrollLayout ? undefined : this.state.screenHeight / 2 }}>
|
||||
<PedalboardView key={pluginUri} selectedId={this.state.selectedPedal}
|
||||
enableStructureEditing={this.props.enableStructureEditing}
|
||||
onSelectionChanged={this.onSelectionChanged}
|
||||
onDoubleClick={this.onPedalDoubleClick}
|
||||
hasTinyToolBar={this.props.hasTinyToolBar}
|
||||
@@ -455,63 +525,75 @@ export const MainPage =
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
(!this.state.splitControlBar) && this.titleBar(pedalboardItem)
|
||||
(!this.state.splitControlBar || !this.props.enableStructureEditing) && this.titleBar(pedalboardItem)
|
||||
}
|
||||
<div style={{ flex: "1 1 1px" }}>
|
||||
|
||||
</div>
|
||||
<div style={{ flex: "0 0 auto", display: (canInsert || canAppend) ? "block" : "none", paddingRight: 8 }}>
|
||||
<IconButton onClick={(e) => { this.onAddClick(e) }} size="large">
|
||||
<AddIcon style={{height: 24, width: 24,fill: this.props.theme.palette.text.primary, opacity: 0.6}} />
|
||||
</IconButton>
|
||||
<Menu
|
||||
id="add-menu"
|
||||
anchorEl={this.state.addMenuAnchorEl}
|
||||
keepMounted
|
||||
open={Boolean(this.state.addMenuAnchorEl)}
|
||||
onClose={() => this.handleAddClose()}
|
||||
TransitionComponent={Fade}
|
||||
>
|
||||
{canInsert && (<MenuItem onClick={() => this.onInsertPedal(instanceId)}>Insert pedal</MenuItem>)}
|
||||
{canAppend && (<MenuItem onClick={() => this.onAppendPedal(instanceId)}>Append pedal</MenuItem>)}
|
||||
<Divider />
|
||||
{canInsert && (<MenuItem onClick={() => this.onInsertSplit(instanceId)}>Insert split</MenuItem>)}
|
||||
{canAppend && (<MenuItem onClick={() => this.onAppendSplit(instanceId)}>Append split</MenuItem>)}
|
||||
</Menu>
|
||||
</div>
|
||||
<div style={{ flex: "0 0 auto", display: canDelete ? "block" : "none", paddingRight: 8 }}>
|
||||
<IconButton
|
||||
onClick={() => { this.onDeletePedal(pedalboardItem?.instanceId ?? -1) }}
|
||||
size="large">
|
||||
<OldDeleteIcon style={{height: 24, width: 24,fill: this.props.theme.palette.text.primary, opacity: 0.6}} />
|
||||
</IconButton>
|
||||
</div>
|
||||
<div style={{ flex: "0 0 auto" }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
size="small"
|
||||
onClick={this.onLoadClick}
|
||||
disabled={this.state.selectedPedal === -1 || (!canLoad) || (this.getSelectedPedalboardItem()?.isSplit() ?? true)}
|
||||
startIcon={<InputIcon />}
|
||||
style={{ textTransform: "none",
|
||||
background: (isDarkMode() ? "#6750A4": undefined) }}
|
||||
>
|
||||
Load
|
||||
</Button>
|
||||
</div>
|
||||
<div style={{ flex: "0 0 auto" }}>
|
||||
<IconButton
|
||||
onClick={(e) => { this.handleMidiConfiguration(instanceId); }}
|
||||
size="large">
|
||||
<MidiIcon style={{height: 24, width: 24,fill: this.props.theme.palette.text.primary, opacity: 0.6}} />
|
||||
</IconButton>
|
||||
|
||||
</div>
|
||||
{this.props.enableStructureEditing && (
|
||||
<div style={{ flex: "0 0 auto", display: "flex", flexFlow: "row nowrap",alignItems: "center" }}>
|
||||
<div style={{ flex: "0 0 auto", display: (canInsert || canAppend) ? "block" : "none", paddingRight: 8 }}>
|
||||
<IconButton onClick={(e) => { this.onAddClick(e) }} size="large">
|
||||
<AddIcon style={{ height: 24, width: 24, fill: this.props.theme.palette.text.primary, opacity: 0.6 }} />
|
||||
</IconButton>
|
||||
<Menu
|
||||
id="add-menu"
|
||||
anchorEl={this.state.addMenuAnchorEl}
|
||||
keepMounted
|
||||
open={Boolean(this.state.addMenuAnchorEl)}
|
||||
onClose={() => this.handleAddClose()}
|
||||
TransitionComponent={Fade}
|
||||
>
|
||||
{canInsert && (<MenuItem onClick={() => this.onInsertPedal(instanceId)}>Insert pedal</MenuItem>)}
|
||||
{canAppend && (<MenuItem onClick={() => this.onAppendPedal(instanceId)}>Append pedal</MenuItem>)}
|
||||
<Divider />
|
||||
{canInsert && (<MenuItem onClick={() => this.onInsertSplit(instanceId)}>Insert split</MenuItem>)}
|
||||
{canAppend && (<MenuItem onClick={() => this.onAppendSplit(instanceId)}>Append split</MenuItem>)}
|
||||
</Menu>
|
||||
</div>
|
||||
<div style={{ flex: "0 0 auto", display: canDelete ? "block" : "none", paddingRight: 8 }}>
|
||||
<IconButton
|
||||
onClick={() => { this.onDeletePedal(pedalboardItem?.instanceId ?? -1) }}
|
||||
size="large">
|
||||
<OldDeleteIcon style={{ height: 24, width: 24, fill: this.props.theme.palette.text.primary, opacity: 0.6 }} />
|
||||
</IconButton>
|
||||
</div>
|
||||
<div style={{ flex: "0 0 auto" }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
size="small"
|
||||
onClick={this.onLoadClick}
|
||||
disabled={this.state.selectedPedal === -1 || (!canLoad) || (this.getSelectedPedalboardItem()?.isSplit() ?? true)}
|
||||
startIcon={<InputIcon />}
|
||||
style={{
|
||||
textTransform: "none",
|
||||
background: (isDarkMode() ? "#6750A4" : undefined)
|
||||
}}
|
||||
>
|
||||
Load
|
||||
</Button>
|
||||
</div>
|
||||
<div style={{ flex: "0 0 auto" }}>
|
||||
<IconButton
|
||||
onClick={(e) => { this.handleMidiConfiguration(instanceId); }}
|
||||
size="large">
|
||||
<MidiIcon style={{ height: 24, width: 24, fill: this.props.theme.palette.text.primary, opacity: 0.6 }} />
|
||||
</IconButton>
|
||||
</div>
|
||||
<div style={{ flex: "0 0 auto" }}>
|
||||
<IconButton
|
||||
onClick={(e) => { this.setState({ snapshotDialogOpen: true }); }}
|
||||
size="large">
|
||||
{this.snapshotIcon(this.props.theme, this.state.selectedSnapshot)}
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
this.state.splitControlBar && (
|
||||
this.state.splitControlBar && this.props.enableStructureEditing && (
|
||||
<div className={classes.splitControlBar}>
|
||||
{
|
||||
this.titleBar(pedalboardItem)
|
||||
@@ -544,6 +626,9 @@ export const MainPage =
|
||||
|
||||
)
|
||||
}
|
||||
{(this.state.snapshotDialogOpen) && (
|
||||
<SnapshotDialog open={this.state.snapshotDialogOpen} onOk={() => this.onSnapshotDialogOk()} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,356 @@
|
||||
import { isDarkMode } from "./DarkMode";
|
||||
|
||||
export const materialColors: { [Name: string]: { [Name: number]: string } } = {
|
||||
"grey": {
|
||||
50: '#FAFAFA',
|
||||
100: '#F5F5F5',
|
||||
200: '#EEEEEE',
|
||||
300: '#E0E0E0',
|
||||
400: '#BDBDBD',
|
||||
500: '#9E9E9E',
|
||||
600: '#757575',
|
||||
700: '#616161',
|
||||
800: '#424242',
|
||||
900: '#212121'
|
||||
},
|
||||
"blueGrey": {
|
||||
50: '#ECEFF1',
|
||||
100: '#CFD8DC',
|
||||
200: '#B0BEC5',
|
||||
300: '#90A4AE',
|
||||
400: '#78909C',
|
||||
500: '#607D8B',
|
||||
600: '#546E7A',
|
||||
700: '#455A64',
|
||||
800: '#37474F',
|
||||
900: '#263238'
|
||||
},
|
||||
|
||||
"red": {
|
||||
50: '#FFEBEE',
|
||||
100: '#FFCDD2',
|
||||
200: '#EF9A9A',
|
||||
300: '#E57373',
|
||||
400: '#EF5350',
|
||||
500: '#F44336',
|
||||
600: '#E53935',
|
||||
700: '#D32F2F',
|
||||
800: '#C62828',
|
||||
900: '#B71C1C',
|
||||
// A100: '#FF8A80',
|
||||
// A200: '#FF5252',
|
||||
// A400: '#FF1744',
|
||||
// A700: '#D50000'
|
||||
},
|
||||
"pink": {
|
||||
50: '#FCE4EC',
|
||||
100: '#F8BBD0',
|
||||
200: '#F48FB1',
|
||||
300: '#F06292',
|
||||
400: '#EC407A',
|
||||
500: '#E91E63',
|
||||
600: '#D81B60',
|
||||
700: '#C2185B',
|
||||
800: '#AD1457',
|
||||
900: '#880E4F',
|
||||
// A100: '#FF80AB',
|
||||
// A200: '#FF4081',
|
||||
// A400: '#F50057',
|
||||
// A700: '#C51162'
|
||||
},
|
||||
"purple": {
|
||||
50: '#F3E5F5',
|
||||
100: '#E1BEE7',
|
||||
200: '#CE93D8',
|
||||
300: '#BA68C8',
|
||||
400: '#AB47BC',
|
||||
500: '#9C27B0',
|
||||
600: '#8E24AA',
|
||||
700: '#7B1FA2',
|
||||
800: '#6A1B9A',
|
||||
900: '#4A148C',
|
||||
// A100: '#EA80FC',
|
||||
// A200: '#E040FB',
|
||||
// A400: '#D500F9',
|
||||
// A700: '#AA00FF'
|
||||
},
|
||||
"deepPurple": {
|
||||
50: '#EDE7F6',
|
||||
100: '#D1C4E9',
|
||||
200: '#B39DDB',
|
||||
300: '#9575CD',
|
||||
400: '#7E57C2',
|
||||
500: '#673AB7',
|
||||
600: '#5E35B1',
|
||||
700: '#512DA8',
|
||||
800: '#4527A0',
|
||||
900: '#311B92',
|
||||
// A100: '#B388FF',
|
||||
// A200: '#7C4DFF',
|
||||
// A400: '#651FFF',
|
||||
// A700: '#6200EA'
|
||||
},
|
||||
"indigo": {
|
||||
50: '#E8EAF6',
|
||||
100: '#C5CAE9',
|
||||
200: '#9FA8DA',
|
||||
300: '#7986CB',
|
||||
400: '#5C6BC0',
|
||||
500: '#3F51B5',
|
||||
600: '#3949AB',
|
||||
700: '#303F9F',
|
||||
800: '#283593',
|
||||
900: '#1A237E',
|
||||
// A100: '#8C9EFF',
|
||||
// A200: '#536DFE',
|
||||
// A400: '#3D5AFE',
|
||||
// A700: '#304FFE'
|
||||
},
|
||||
"blue": {
|
||||
50: '#E3F2FD',
|
||||
100: '#BBDEFB',
|
||||
200: '#90CAF9',
|
||||
300: '#64B5F6',
|
||||
400: '#42A5F5',
|
||||
500: '#2196F3',
|
||||
600: '#1E88E5',
|
||||
700: '#1976D2',
|
||||
800: '#1565C0',
|
||||
900: '#0D47A1',
|
||||
// A100: '#82B1FF',
|
||||
// A200: '#448AFF',
|
||||
// A400: '#2979FF',
|
||||
// A700: '#2962FF'
|
||||
},
|
||||
"lightBlue": {
|
||||
50: '#E1F5FE',
|
||||
100: '#B3E5FC',
|
||||
200: '#81D4FA',
|
||||
300: '#4FC3F7',
|
||||
400: '#29B6F6',
|
||||
500: '#03A9F4',
|
||||
600: '#039BE5',
|
||||
700: '#0288D1',
|
||||
800: '#0277BD',
|
||||
900: '#01579B',
|
||||
// A100: '#80D8FF',
|
||||
// A200: '#40C4FF',
|
||||
// A400: '#00B0FF',
|
||||
// A700: '#0091EA'
|
||||
},
|
||||
"cyan": {
|
||||
50: '#E0F7FA',
|
||||
100: '#B2EBF2',
|
||||
200: '#80DEEA',
|
||||
300: '#4DD0E1',
|
||||
400: '#26C6DA',
|
||||
500: '#00BCD4',
|
||||
600: '#00ACC1',
|
||||
700: '#0097A7',
|
||||
800: '#00838F',
|
||||
900: '#006064',
|
||||
// A100: '#84FFFF',
|
||||
// A200: '#18FFFF',
|
||||
// A400: '#00E5FF',
|
||||
// A700: '#00B8D4'
|
||||
},
|
||||
"teal": {
|
||||
50: '#E0F2F1',
|
||||
100: '#B2DFDB',
|
||||
200: '#80CBC4',
|
||||
300: '#4DB6AC',
|
||||
400: '#26A69A',
|
||||
500: '#009688',
|
||||
600: '#00897B',
|
||||
700: '#00796B',
|
||||
800: '#00695C',
|
||||
900: '#004D40',
|
||||
// A100: '#A7FFEB',
|
||||
// A200: '#64FFDA',
|
||||
// A400: '#1DE9B6',
|
||||
// A700: '#00BFA5'
|
||||
},
|
||||
"green": {
|
||||
50: '#E8F5E9',
|
||||
100: '#C8E6C9',
|
||||
200: '#A5D6A7',
|
||||
300: '#81C784',
|
||||
400: '#66BB6A',
|
||||
500: '#4CAF50',
|
||||
600: '#43A047',
|
||||
700: '#388E3C',
|
||||
800: '#2E7D32',
|
||||
900: '#1B5E20',
|
||||
// A100: '#B9F6CA',
|
||||
// A200: '#69F0AE',
|
||||
// A400: '#00E676',
|
||||
// A700: '#00C853'
|
||||
},
|
||||
"lightGreen": {
|
||||
50: '#F1F8E9',
|
||||
100: '#DCEDC8',
|
||||
200: '#C5E1A5',
|
||||
300: '#AED581',
|
||||
400: '#9CCC65',
|
||||
500: '#8BC34A',
|
||||
600: '#7CB342',
|
||||
700: '#689F38',
|
||||
800: '#558B2F',
|
||||
900: '#33691E',
|
||||
// A100: '#CCFF90',
|
||||
// A200: '#B2FF59',
|
||||
// A400: '#76FF03',
|
||||
// A700: '#64DD17'
|
||||
},
|
||||
"lime": {
|
||||
50: '#F9FBE7',
|
||||
100: '#F0F4C3',
|
||||
200: '#E6EE9C',
|
||||
300: '#DCE775',
|
||||
400: '#D4E157',
|
||||
500: '#CDDC39',
|
||||
600: '#C0CA33',
|
||||
700: '#AFB42B',
|
||||
800: '#9E9D24',
|
||||
900: '#827717',
|
||||
// A100: '#F4FF81',
|
||||
// A200: '#EEFF41',
|
||||
// A400: '#C6FF00',
|
||||
// A700: '#AEEA00'
|
||||
},
|
||||
"yellow": {
|
||||
50: '#FFFDE7',
|
||||
100: '#FFF9C4',
|
||||
200: '#FFF59D',
|
||||
300: '#FFF176',
|
||||
400: '#FFEE58',
|
||||
500: '#FFEB3B',
|
||||
600: '#FDD835',
|
||||
700: '#FBC02D',
|
||||
800: '#F9A825',
|
||||
900: '#F57F17',
|
||||
// A100: '#FFFF8D',
|
||||
// A200: '#FFFF00',
|
||||
// A400: '#FFEA00',
|
||||
// A700: '#FFD600'
|
||||
},
|
||||
"amber": {
|
||||
50: '#FFF8E1',
|
||||
100: '#FFECB3',
|
||||
200: '#FFE082',
|
||||
300: '#FFD54F',
|
||||
400: '#FFCA28',
|
||||
500: '#FFC107',
|
||||
600: '#FFB300',
|
||||
700: '#FFA000',
|
||||
800: '#FF8F00',
|
||||
900: '#FF6F00',
|
||||
// A100: '#FFE57F',
|
||||
// A200: '#FFD740',
|
||||
// A400: '#FFC400',
|
||||
// A700: '#FFAB00'
|
||||
},
|
||||
"orange": {
|
||||
50: '#FFF3E0',
|
||||
100: '#FFE0B2',
|
||||
200: '#FFCC80',
|
||||
300: '#FFB74D',
|
||||
400: '#FFA726',
|
||||
500: '#FF9800',
|
||||
600: '#FB8C00',
|
||||
700: '#F57C00',
|
||||
800: '#EF6C00',
|
||||
900: '#E65100',
|
||||
// A100: '#FFD180',
|
||||
// A200: '#FFAB40',
|
||||
// A400: '#FF9100',
|
||||
// A700: '#FF6D00'
|
||||
},
|
||||
"deepOrange": {
|
||||
50: '#FBE9E7',
|
||||
100: '#FFCCBC',
|
||||
200: '#FFAB91',
|
||||
300: '#FF8A65',
|
||||
400: '#FF7043',
|
||||
500: '#FF5722',
|
||||
600: '#F4511E',
|
||||
700: '#E64A19',
|
||||
800: '#D84315',
|
||||
900: '#BF360C',
|
||||
// A100: '#FF9E80',
|
||||
// A200: '#FF6E40',
|
||||
// A400: '#FF3D00',
|
||||
// A700: '#DD2C00'
|
||||
},
|
||||
"brown": {
|
||||
50: '#EFEBE9',
|
||||
100: '#D7CCC8',
|
||||
200: '#BCAAA4',
|
||||
300: '#A1887F',
|
||||
400: '#8D6E63',
|
||||
500: '#795548',
|
||||
600: '#6D4C41',
|
||||
700: '#5D4037',
|
||||
800: '#4E342E',
|
||||
900: '#3E2723'
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function makeLabels() {
|
||||
let result: string[] = [];
|
||||
for (let [key] of Object.entries(materialColors)) {
|
||||
result.push(key.toString());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export const colorKeys: string[] = makeLabels();
|
||||
|
||||
const darkMode: boolean = isDarkMode();
|
||||
|
||||
export function getBackgroundColor(colorKey: string): string {
|
||||
let list = materialColors[colorKey];
|
||||
if (!list) {
|
||||
list = materialColors["grey"];
|
||||
}
|
||||
if (darkMode) {
|
||||
if (colorKey === "grey" || colorKey === "yellow")
|
||||
{
|
||||
return list[900];
|
||||
}
|
||||
else {
|
||||
return list[800];
|
||||
}
|
||||
} else {
|
||||
if (colorKey === "grey")
|
||||
{
|
||||
return list[200];
|
||||
}
|
||||
else {
|
||||
return list[100];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getBorderColor(colorKey: string): string {
|
||||
let list = materialColors[colorKey];
|
||||
if (!list)
|
||||
{
|
||||
list = materialColors["grey"];
|
||||
}
|
||||
if (darkMode) {
|
||||
if (colorKey === "grey" || colorKey === "yellow")
|
||||
{
|
||||
return list[700];
|
||||
}
|
||||
return list[500];
|
||||
} else {
|
||||
if (colorKey === "grey")
|
||||
{
|
||||
return list[300];
|
||||
}
|
||||
return list[200];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -70,6 +70,7 @@ export class PedalboardItem implements Deserializable<PedalboardItem> {
|
||||
this.stateUpdateCount = input.stateUpdateCount;
|
||||
this.lv2State = input.lv2State;
|
||||
this.lilvPresetUri = input.lilvPresetUri;
|
||||
this.pathProperties = input.pathProperties;
|
||||
return this;
|
||||
}
|
||||
deserialize(input: any): PedalboardItem {
|
||||
@@ -200,8 +201,81 @@ export class PedalboardItem implements Deserializable<PedalboardItem> {
|
||||
stateUpdateCount: number = 0;
|
||||
lv2State: [boolean,any] = [false,{}];
|
||||
lilvPresetUri: string = "";
|
||||
pathProperties: {[Name: string]: string} = {};
|
||||
};
|
||||
|
||||
export class SnapshotValue {
|
||||
deserialize(input: any): SnapshotValue {
|
||||
this.instanceId = input.instanceId;
|
||||
this.controlValues = ControlValue.deserializeArray(input.controlValues);
|
||||
this.lv2State = input.lv2state;
|
||||
this.pathProperties = input.pathProperties;
|
||||
return this;
|
||||
}
|
||||
static deserializeArray(input: any): SnapshotValue[] {
|
||||
let result: SnapshotValue[] = [];
|
||||
for (let i = 0; i < input.length; ++i) {
|
||||
let inputItem: any = input[i];
|
||||
let outputItem = new SnapshotValue().deserialize(inputItem);
|
||||
result[i] = outputItem;
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
static createFromPedalboardItem(item: PedalboardItem)
|
||||
{
|
||||
let result = new SnapshotValue();
|
||||
result.instanceId = item.instanceId;
|
||||
result.controlValues = ControlValue.deserializeArray(item.controlValues);
|
||||
result.lv2State = item.lv2State; // we can do this, because lv2State is immutable.
|
||||
result.pathProperties = {... item.pathProperties}; // clone the dictionary.
|
||||
return result;
|
||||
}
|
||||
instanceId: number = -1;
|
||||
controlValues: ControlValue[] = ControlValue.EmptyArray;
|
||||
lv2State: [boolean,any] = [false,{}];
|
||||
pathProperties: {[Name: string]: string} = {};
|
||||
}
|
||||
export class Snapshot {
|
||||
deserialize(input: any): Snapshot {
|
||||
this.values = SnapshotValue.deserializeArray(input.values);
|
||||
this.name = input.name;
|
||||
this.color = input.color;
|
||||
return this;
|
||||
}
|
||||
static deserializeArray(input: any): (Snapshot| null)[] {
|
||||
let result: (Snapshot|null)[] = [];
|
||||
for (let i = 0; i < input.length; ++i) {
|
||||
let inputItem: any = input[i];
|
||||
let outputItem: (Snapshot|null) = null;
|
||||
if (inputItem !== null)
|
||||
{
|
||||
outputItem = new Snapshot().deserialize(inputItem);
|
||||
}
|
||||
result[i] = outputItem;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
static readonly MAX_SNAPSHOTS: number = 6;
|
||||
|
||||
static cloneSnapshots(snapshots: (Snapshot|null)[]): (Snapshot|null)[]
|
||||
{
|
||||
let result: (Snapshot|null)[] = [];
|
||||
for (let i = 0; i < Snapshot.MAX_SNAPSHOTS; ++i)
|
||||
{
|
||||
if (i >= snapshots.length)
|
||||
{
|
||||
result.push(null);
|
||||
} else {
|
||||
result.push(snapshots[i]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
name: string = "";
|
||||
color: string = "";
|
||||
values: SnapshotValue[] = [];
|
||||
};
|
||||
|
||||
export enum SplitType {
|
||||
Ab = 0,
|
||||
@@ -277,6 +351,9 @@ export class Pedalboard implements Deserializable<Pedalboard> {
|
||||
this.output_volume_db = input.output_volume_db;
|
||||
this.items = PedalboardItem.deserializeArray(input.items);
|
||||
this.nextInstanceId = input.nextInstanceId ?? -1;
|
||||
this.snapshots = input.snapshots ? Snapshot.deserializeArray(input.snapshots): [];
|
||||
this.selectedSnapshot = input.selectedSnapshot;
|
||||
this.pathProperties = input.pathProperties;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -289,6 +366,10 @@ export class Pedalboard implements Deserializable<Pedalboard> {
|
||||
items: PedalboardItem[] = [];
|
||||
nextInstanceId: number = -1;
|
||||
|
||||
snapshots: (Snapshot | null)[] = [];
|
||||
selectedSnapshot: number = -1;
|
||||
pathProperties: {[Name: string]: string} = {};
|
||||
|
||||
*itemsGenerator(): Generator<PedalboardItem, void, undefined> {
|
||||
let it = itemGenerator_(this.items);
|
||||
while (true)
|
||||
@@ -299,6 +380,19 @@ export class Pedalboard implements Deserializable<Pedalboard> {
|
||||
}
|
||||
}
|
||||
|
||||
makeSnapshot(): Snapshot {
|
||||
let result = new Snapshot();
|
||||
let it = this.itemsGenerator();
|
||||
while (true)
|
||||
{
|
||||
let v = it.next();
|
||||
if (v.done) break;
|
||||
let pedalboardItem = v.value;
|
||||
let snapshotValue = SnapshotValue.createFromPedalboardItem(pedalboardItem);
|
||||
result.values.push(snapshotValue);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
hasItem(instanceId: number): boolean
|
||||
{
|
||||
let it = this.itemsGenerator();
|
||||
@@ -570,6 +664,8 @@ export class Pedalboard implements Deserializable<Pedalboard> {
|
||||
item.pluginName = "";
|
||||
item.isEnabled = true;
|
||||
item.controlValues = [];
|
||||
item.vstState = "";
|
||||
item.lv2State = [false,{}];
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -191,6 +191,7 @@ interface PedalboardProps extends WithStyles<typeof pedalboardStyles> {
|
||||
onSelectionChanged?: OnSelectHandler;
|
||||
onDoubleClick?: OnSelectHandler;
|
||||
hasTinyToolBar: boolean;
|
||||
enableStructureEditing: boolean;
|
||||
|
||||
}
|
||||
interface LayoutSize {
|
||||
@@ -392,6 +393,10 @@ const PedalboardView =
|
||||
}
|
||||
|
||||
onDragEnd(instanceId: number, clientX: number, clientY: number) {
|
||||
if (!this.props.enableStructureEditing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!this.currentLayout) return;
|
||||
|
||||
if (!this.frameRef.current) return;
|
||||
@@ -670,18 +675,27 @@ const PedalboardView =
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if (this.props.onDoubleClick && instanceId) {
|
||||
if (this.props.onDoubleClick && instanceId && this.props.enableStructureEditing) {
|
||||
this.props.onDoubleClick(instanceId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
onItemLongClick(event: SyntheticEvent, instanceId?: number): void {
|
||||
if (!instanceId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if (!this.props.enableStructureEditing)
|
||||
{
|
||||
this.setSelection(instanceId);
|
||||
return;
|
||||
}
|
||||
if (!Utility.needsZoomedControls()) {
|
||||
if (this.props.onDoubleClick && instanceId) {
|
||||
if (this.props.onDoubleClick && this.props.enableStructureEditing && instanceId) {
|
||||
this.props.onDoubleClick(instanceId);
|
||||
}
|
||||
}
|
||||
@@ -927,7 +941,7 @@ const PedalboardView =
|
||||
onContextMenu={(e: SyntheticEvent) => { this.onItemLongClick(e, instanceId); }}
|
||||
>
|
||||
<SelectHoverBackground selected={instanceId === this.props.selectedId} showHover={true} borderRadius={6} >
|
||||
<Draggable draggable={draggable} getScrollContainer={() => this.getScrollContainer()}
|
||||
<Draggable draggable={draggable && (this.props.enableStructureEditing)} getScrollContainer={() => this.getScrollContainer()}
|
||||
onDragEnd={(x, y) => { this.onDragEnd(instanceId, x, y) }}
|
||||
>
|
||||
<PluginIcon pluginType={iconType} size={24} pluginMissing={pluginNotFound} opacity={enabled? 0.99:0.6} />
|
||||
|
||||
@@ -0,0 +1,318 @@
|
||||
// Copyright (c) 2024 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 { Theme } from '@mui/material/styles';
|
||||
import { WithStyles } from '@mui/styles';
|
||||
import createStyles from '@mui/styles/createStyles';
|
||||
import withStyles from '@mui/styles/withStyles';
|
||||
import { PiPedalModel, PiPedalModelFactory, PresetIndex } from './PiPedalModel';
|
||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import AppBar from '@mui/material/AppBar';
|
||||
import SnapshotPanel from './SnapshotPanel';
|
||||
import ArrowLeftOutlined from '@mui/icons-material/ArrowLeft';
|
||||
import ArrowRightOutlined from '@mui/icons-material/ArrowRight';
|
||||
import Select from '@mui/material/Select';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import { isDarkMode } from './DarkMode';
|
||||
import { BankIndex } from './Banks';
|
||||
import SnapshotEditor from './SnapshotEditor';
|
||||
import { Snapshot } from './Pedalboard';
|
||||
|
||||
|
||||
const selectColor = isDarkMode() ? "#888" : "#FFFFFF";
|
||||
|
||||
const styles = (theme: Theme) => createStyles({
|
||||
frame: {
|
||||
position: "absolute", display: "flex", flexDirection: "column", flexWrap: "nowrap",
|
||||
justifyContent: "flex-start", left: "0px", top: "0px", bottom: "0px", right: "0px", overflow: "hidden"
|
||||
},
|
||||
select: { // fu fu fu.Overrides for white selector on dark background.
|
||||
'&:before': {
|
||||
borderColor: selectColor,
|
||||
},
|
||||
'&:after': {
|
||||
borderColor: selectColor,
|
||||
},
|
||||
'&:hover:not(.Mui-disabled):before': {
|
||||
borderColor: selectColor,
|
||||
}
|
||||
},
|
||||
select_icon: {
|
||||
fill: selectColor,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
interface PerformanceViewProps extends WithStyles<typeof styles> {
|
||||
onClose: () => void,
|
||||
theme: Theme
|
||||
}
|
||||
|
||||
interface PerformanceViewState {
|
||||
wrapSelects: boolean
|
||||
presets: PresetIndex;
|
||||
banks: BankIndex;
|
||||
showSnapshotEditor: boolean;
|
||||
snapshotEditorIndex: number;
|
||||
}
|
||||
|
||||
|
||||
export const PerformanceView =
|
||||
withStyles(styles, { withTheme: true })(
|
||||
class extends ResizeResponsiveComponent<PerformanceViewProps, PerformanceViewState> {
|
||||
model: PiPedalModel;
|
||||
|
||||
constructor(props: PerformanceViewProps) {
|
||||
super(props);
|
||||
this.model = PiPedalModelFactory.getInstance();
|
||||
// let pedalboard = this.model.pedalboard.get();
|
||||
// let selectedPedal = pedalboard.getFirstSelectableItem();
|
||||
|
||||
this.state = {
|
||||
presets: this.model.presets.get(),
|
||||
banks: this.model.banks.get(),
|
||||
wrapSelects: false,
|
||||
showSnapshotEditor: false,
|
||||
snapshotEditorIndex: 0
|
||||
};
|
||||
this.onPresetsChanged = this.onPresetsChanged.bind(this);
|
||||
this.onBanksChanged = this.onBanksChanged.bind(this);
|
||||
}
|
||||
onWindowSizeChanged(width: number, height: number): void {
|
||||
this.setState({ wrapSelects: width < 700 });
|
||||
}
|
||||
|
||||
onPresetsChanged(newValue: PresetIndex) {
|
||||
this.setState({ presets: this.model.presets.get() })
|
||||
}
|
||||
onBanksChanged(newValue: BankIndex) {
|
||||
this.setState({ banks: this.model.banks.get() })
|
||||
}
|
||||
componentDidMount(): void {
|
||||
super.componentDidMount();
|
||||
this.model.presets.addOnChangedHandler(this.onPresetsChanged);
|
||||
this.model.banks.addOnChangedHandler(this.onBanksChanged);
|
||||
this.setState({
|
||||
presets: this.model.presets.get(),
|
||||
banks: this.model.banks.get()
|
||||
});
|
||||
}
|
||||
componentWillUnmount(): void {
|
||||
this.model.presets.removeOnChangedHandler(this.onPresetsChanged);
|
||||
this.model.banks.removeOnChangedHandler(this.onBanksChanged);
|
||||
super.componentWillUnmount();
|
||||
}
|
||||
handlePresetSelectClose(event: any): void {
|
||||
let value = event.currentTarget.getAttribute("data-value");
|
||||
if (value && value.length > 0) {
|
||||
this.model.loadPreset(parseInt(value));
|
||||
}
|
||||
}
|
||||
|
||||
handleBankSelectClose(event: any): void {
|
||||
let value = event.currentTarget.getAttribute("data-value");
|
||||
if (value && value.length > 0) {
|
||||
this.model.openBank(parseInt(value));
|
||||
}
|
||||
}
|
||||
handleOnEdit(index: number): boolean {
|
||||
// load it so we can edit it.
|
||||
this.model.selectSnapshot(index);
|
||||
this.setState({
|
||||
showSnapshotEditor: true,
|
||||
snapshotEditorIndex: index
|
||||
});
|
||||
return true;
|
||||
}
|
||||
handleSnapshotEditOk(index: number, name: string, color: string,newSnapshots: (Snapshot|null)[])
|
||||
{
|
||||
// results have been sent to the server. we would perform a short-cut commit to our local state, to avoid laggy display updates.
|
||||
// but we don't actually have that in our state.
|
||||
|
||||
}
|
||||
|
||||
handlePreviousBank()
|
||||
{
|
||||
this.model.previousBank();
|
||||
}
|
||||
|
||||
handleNextBank()
|
||||
{
|
||||
this.model.nextBank();
|
||||
}
|
||||
handlePreviousPreset()
|
||||
{
|
||||
this.model.previousPreset();
|
||||
}
|
||||
handleNextPreset()
|
||||
{
|
||||
this.model.nextPreset();
|
||||
}
|
||||
|
||||
render() {
|
||||
let classes = this.props.classes;
|
||||
let wrapSelects = this.state.wrapSelects;
|
||||
let presets = this.state.presets;
|
||||
let banks = this.state.banks;
|
||||
return (
|
||||
<div className={this.props.classes.frame} style={{ overflow: "clip", height: "100%" }} >
|
||||
<div className={this.props.classes.frame} style={{ overflow: "clip", height: "100%" }} >
|
||||
<AppBar id="select-plugin-dialog-title"
|
||||
style={{
|
||||
position: "static", flex: "0 0 auto", height: wrapSelects ? 104 : 54,
|
||||
display: this.state.showSnapshotEditor ? "none" : undefined
|
||||
}}
|
||||
>
|
||||
<div style={{
|
||||
display: "flex", flexFlow: "row nowrap", alignContent: "center",
|
||||
paddingTop: 3, paddingBottom: 3, paddingRight: 8, paddingLeft: 8,
|
||||
alignItems: "start"
|
||||
}}>
|
||||
<IconButton aria-label="menu" color="inherit"
|
||||
onClick={() => { this.props.onClose(); }} style={{ flex: "0 0 auto" }} >
|
||||
<ArrowBackIcon />
|
||||
</IconButton>
|
||||
|
||||
<div style={{
|
||||
flex: "1 1 1px", display: "flex",
|
||||
flexFlow: wrapSelects ? "column nowrap" : "row nowrap",
|
||||
alignItems: wrapSelects ? "stretch" : undefined,
|
||||
justifyContent: wrapSelects ? undefined : "space-evenly",
|
||||
marginLeft: 8,
|
||||
gap: 8
|
||||
|
||||
}}>
|
||||
{/********* BANKS *******************/}
|
||||
<div style={{ flex: "1 1 1px", display: "flex", flexFlow: "row nowrap", maxWidth: 500 }}>
|
||||
<IconButton
|
||||
aria-label="previous-bank"
|
||||
onClick={() => { this.handlePreviousBank(); }}
|
||||
color="inherit"
|
||||
style={{
|
||||
borderTopRightRadius: "3px", borderBottomRightRadius: "3px", marginRight: 4
|
||||
}}
|
||||
>
|
||||
<ArrowLeftOutlined style={{ opacity: 0.75 }} />
|
||||
</IconButton>
|
||||
|
||||
<Select variant="standard"
|
||||
className={classes.select}
|
||||
style={{ flex: "1 1 1px", width: "100%", position: "relative", top: 0, color: "#FFFFFF" }} disabled={false}
|
||||
displayEmpty
|
||||
onClose={(e) => this.handleBankSelectClose(e)}
|
||||
value={banks.selectedBank === 0 ? undefined : banks.selectedBank}
|
||||
inputProps={{
|
||||
classes: { icon: classes.select_icon },
|
||||
'aria-label': "Select preset"
|
||||
}}
|
||||
>
|
||||
{
|
||||
banks.entries.map((entry) => {
|
||||
return (
|
||||
<MenuItem key={entry.instanceId} value={entry.instanceId} >
|
||||
{entry.name}
|
||||
</MenuItem>
|
||||
);
|
||||
})
|
||||
}
|
||||
</Select>
|
||||
|
||||
<IconButton
|
||||
aria-label="next-bank"
|
||||
onClick={() => { this.handleNextBank(); }}
|
||||
color="inherit"
|
||||
style={{ borderTopLeftRadius: "3px", borderBottomLeftRadius: "3px", marginLeft: 4 }}
|
||||
>
|
||||
<ArrowRightOutlined style={{ opacity: 0.75 }} />
|
||||
</IconButton>
|
||||
|
||||
</div>
|
||||
{/********* PRESETS *******************/}
|
||||
<div style={{ flex: "1 1 1px", display: "flex", flexFlow: "row nowrap", maxWidth: 500 }}>
|
||||
<IconButton
|
||||
aria-label="previous-presest"
|
||||
onClick={() => { this.handlePreviousPreset(); }}
|
||||
color="inherit"
|
||||
style={{ borderTopRightRadius: "3px", borderBottomRightRadius: "3px", marginRight: 4 }}
|
||||
>
|
||||
<ArrowLeftOutlined style={{ opacity: 0.75 }} />
|
||||
</IconButton>
|
||||
<Select variant="standard"
|
||||
className={classes.select}
|
||||
style={{
|
||||
flex: "1 1 1px", width: "100%", color: "#FFFFFF"
|
||||
}} disabled={false}
|
||||
displayEmpty
|
||||
onClose={(e) => this.handlePresetSelectClose(e)}
|
||||
value={presets.selectedInstanceId === 0 ? '' : presets.selectedInstanceId}
|
||||
|
||||
inputProps={{
|
||||
classes: { icon: classes.select_icon },
|
||||
'aria-label': "Select preset"
|
||||
}
|
||||
}
|
||||
>
|
||||
{
|
||||
presets.presets.map((preset) => {
|
||||
return (
|
||||
<MenuItem key={preset.instanceId} value={preset.instanceId} >
|
||||
{preset.name}
|
||||
</MenuItem>
|
||||
);
|
||||
})
|
||||
}
|
||||
</Select>
|
||||
|
||||
<IconButton
|
||||
aria-label="next-preset"
|
||||
onClick={() => { this.handleNextPreset(); }}
|
||||
color="inherit"
|
||||
style={{ borderTopLeftRadius: "3px", borderBottomLeftRadius: "3px", marginLeft: 4 }}
|
||||
>
|
||||
<ArrowRightOutlined style={{ opacity: 0.75 }} />
|
||||
</IconButton>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AppBar >
|
||||
<div style={{ flex: "1 0 auto", display: "flex", marginTop: 16 }}>
|
||||
<SnapshotPanel onEdit={(index) => { return this.handleOnEdit(index); }} />
|
||||
</div>
|
||||
</div>
|
||||
{this.state.showSnapshotEditor && (
|
||||
<SnapshotEditor snapshotIndex={this.state.snapshotEditorIndex}
|
||||
onClose={() => {
|
||||
this.setState({ showSnapshotEditor: false });
|
||||
}}
|
||||
onOk={(index,name,color,newSnapshots)=>{
|
||||
this.setState({ showSnapshotEditor: false});
|
||||
this.handleSnapshotEditOk(index,name,color,newSnapshots);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div >
|
||||
)
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -22,7 +22,7 @@ import { UiPlugin, UiControl, PluginType, UiFileProperty } from './Lv2Plugin';
|
||||
import { PiPedalArgumentError, PiPedalStateError } from './PiPedalError';
|
||||
import { UpdateStatus, UpdatePolicyT } from './Updater';
|
||||
import { ObservableProperty } from './ObservableProperty';
|
||||
import { Pedalboard, PedalboardItem, ControlValue } from './Pedalboard'
|
||||
import { Pedalboard, PedalboardItem, ControlValue, Snapshot } from './Pedalboard'
|
||||
import PluginClass from './PluginClass';
|
||||
import PiPedalSocket, { PiPedalMessageHeader } from './PiPedalSocket';
|
||||
import { nullCast } from './Utility'
|
||||
@@ -57,7 +57,7 @@ export enum State {
|
||||
|
||||
class UpdatedError extends Error {
|
||||
};
|
||||
export function wantsLoadingScreen(state: State) {
|
||||
export function wantsReloadingScreen(state: State) {
|
||||
return state >= State.Reconnecting;
|
||||
}
|
||||
|
||||
@@ -368,8 +368,8 @@ export class PiPedalModel //implements PiPedalModel
|
||||
clientId: number = -1;
|
||||
|
||||
serverVersion?: PiPedalVersion;
|
||||
countryCodes: {[Name: string]: string} = {};
|
||||
|
||||
countryCodes: { [Name: string]: string } = {};
|
||||
|
||||
socketServerUrl: string = "";
|
||||
varServerUrl: string = "";
|
||||
lv2Path: string = "";
|
||||
@@ -390,6 +390,8 @@ export class PiPedalModel //implements PiPedalModel
|
||||
showStatusMonitor: ObservableProperty<boolean> = new ObservableProperty<boolean>(true);
|
||||
|
||||
pedalboard: ObservableProperty<Pedalboard> = new ObservableProperty<Pedalboard>(new Pedalboard());
|
||||
presetChanged: ObservableProperty<boolean> = new ObservableProperty<boolean>(false);
|
||||
selectedSnapshot: ObservableProperty<number> = new ObservableProperty<number>(-1);
|
||||
plugin_classes: ObservableProperty<PluginClass> = new ObservableProperty<PluginClass>(new PluginClass());
|
||||
jackConfiguration: ObservableProperty<JackConfiguration> = new ObservableProperty<JackConfiguration>(new JackConfiguration());
|
||||
jackSettings: ObservableProperty<JackChannelSelection> = new ObservableProperty<JackChannelSelection>(new JackChannelSelection());
|
||||
@@ -498,6 +500,10 @@ export class PiPedalModel //implements PiPedalModel
|
||||
return true;
|
||||
|
||||
}
|
||||
private setModelPedalboard(pedalboard: Pedalboard) {
|
||||
this.pedalboard.set(pedalboard);
|
||||
this.selectedSnapshot.set(pedalboard.selectedSnapshot);
|
||||
}
|
||||
onSocketMessage(header: PiPedalMessageHeader, body?: any) {
|
||||
if (this.visibilityState.get() === VisibilityState.Hidden) return;
|
||||
|
||||
@@ -558,6 +564,19 @@ export class PiPedalModel //implements PiPedalModel
|
||||
let channelSelectionBody = body as ChannelSelectionChangedBody;
|
||||
let channelSelection = new JackChannelSelection().deserialize(channelSelectionBody.jackChannelSelection);
|
||||
this.jackSettings.set(channelSelection);
|
||||
} else if (message === "onSelectedSnapshotChanged") {
|
||||
let selectedSnapshot = body as number;
|
||||
this.pedalboard.get().selectedSnapshot = selectedSnapshot;
|
||||
this.selectedSnapshot.set(selectedSnapshot);
|
||||
} else if (message === "onPresetChanged") {
|
||||
let changed = body as boolean;
|
||||
|
||||
if (this.presets.get().presetChanged !== changed) {
|
||||
let newPresets = this.presets.get().clone(); // deep clone.
|
||||
newPresets.presetChanged = changed;
|
||||
this.presets.set(newPresets);
|
||||
}
|
||||
this.presetChanged.set(changed);
|
||||
} else if (message === "onPresetsChanged") {
|
||||
let presetsChangedBody = body as PresetsChangedBody;
|
||||
let presets = new PresetIndex().deserialize(presetsChangedBody.presets);
|
||||
@@ -580,7 +599,7 @@ export class PiPedalModel //implements PiPedalModel
|
||||
);
|
||||
} else if (message === "onPedalboardChanged") {
|
||||
let pedalChangedBody = body as PedalboardChangedBody;
|
||||
this.pedalboard.set(new Pedalboard().deserialize(pedalChangedBody.pedalboard));
|
||||
this.setModelPedalboard(new Pedalboard().deserialize(pedalChangedBody.pedalboard));
|
||||
|
||||
} else if (message === "onMidiValueChanged") {
|
||||
let controlChangedBody = body as ControlChangedBody;
|
||||
@@ -599,6 +618,14 @@ export class PiPedalModel //implements PiPedalModel
|
||||
let isNote = body.isNote as boolean;
|
||||
let noteOrControl = body.noteOrControl as number;
|
||||
this.handleNotifyMidiListener(clientHandle, isNote, noteOrControl);
|
||||
} else if (message === "onNotifyPathPatchPropertyChanged") {
|
||||
let instanceId = body.instanceId as number;
|
||||
let propertyUri = body.propertyUri as string;
|
||||
let atomJson = body.atomJson as any;
|
||||
this.handleNotifyPathPatchPropertyChanged(instanceId, propertyUri, atomJson);
|
||||
if (header.replyTo) {
|
||||
this.webSocket?.reply(header.replyTo, "onNotifyPatchProperty", true);
|
||||
}
|
||||
} else if (message === "onNotifyPatchProperty") {
|
||||
let clientHandle = body.clientHandle as number;
|
||||
let instanceId = body.instanceId as number;
|
||||
@@ -649,8 +676,7 @@ export class PiPedalModel //implements PiPedalModel
|
||||
} else if (message === "onUpdateStatusChanged") {
|
||||
let updateStatus = new UpdateStatus().deserialize(body);
|
||||
this.onUpdateStatusChanged(updateStatus);
|
||||
} else if (message === "onNetworkChanging")
|
||||
{
|
||||
} else if (message === "onNetworkChanging") {
|
||||
this.onNetworkChanging(body as boolean);
|
||||
}
|
||||
}
|
||||
@@ -687,8 +713,7 @@ export class PiPedalModel //implements PiPedalModel
|
||||
private lastCanUpdateNow: boolean = false;
|
||||
private updatePromptForUpdate() {
|
||||
this.clearPromptForUpdateTimer();
|
||||
if (!this.enableAutoUpdate)
|
||||
{
|
||||
if (!this.enableAutoUpdate) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -702,7 +727,7 @@ export class PiPedalModel //implements PiPedalModel
|
||||
let nDate = Date.now();
|
||||
|
||||
let now: Date = new Date(nDate);
|
||||
let maxDate: Date = new Date(nDate + 86400000*2); // sanity check for systems with unstable system clock
|
||||
let maxDate: Date = new Date(nDate + 86400000 * 2); // sanity check for systems with unstable system clock
|
||||
|
||||
timeEnabled = (updateLaterTime < now || updateLaterTime >= maxDate)
|
||||
}
|
||||
@@ -710,16 +735,14 @@ export class PiPedalModel //implements PiPedalModel
|
||||
let statusEnabled = updateStatus.isOnline && updateStatus.isValid && updateStatus.getActiveRelease().updateAvailable;
|
||||
|
||||
let canUpdateNow: boolean = (stateEnabled && timeEnabled && statusEnabled);
|
||||
if (updateStatus.updatePolicy === UpdatePolicyT.Disable)
|
||||
{
|
||||
if (updateStatus.updatePolicy === UpdatePolicyT.Disable) {
|
||||
canUpdateNow = false;
|
||||
}
|
||||
|
||||
if (canUpdateNow && canUpdateNow !== this.lastCanUpdateNow)
|
||||
{
|
||||
if (canUpdateNow && canUpdateNow !== this.lastCanUpdateNow) {
|
||||
this.showUpdateDialogValue = true; // make the dialog sticky so it can show OK button
|
||||
}
|
||||
this.lastCanUpdateNow = canUpdateNow;
|
||||
this.lastCanUpdateNow = canUpdateNow;
|
||||
this.promptForUpdate.set(this.showUpdateDialogValue || canUpdateNow);
|
||||
|
||||
if (stateEnabled && statusEnabled && !timeEnabled && updateLaterTime) {
|
||||
@@ -795,26 +818,23 @@ export class PiPedalModel //implements PiPedalModel
|
||||
|
||||
androidReconnectTimeout?: NodeJS.Timeout = undefined;
|
||||
|
||||
cancelAndroidReconnectTimer()
|
||||
{
|
||||
cancelAndroidReconnectTimer() {
|
||||
if (this.androidReconnectTimeout) {
|
||||
clearTimeout(this.androidReconnectTimeout);
|
||||
this.androidReconnectTimeout = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
startAndroidReconnectTimer()
|
||||
{
|
||||
startAndroidReconnectTimer() {
|
||||
this.cancelAndroidReconnectTimer();
|
||||
this.androidReconnectTimeout = setTimeout(()=>{
|
||||
this.androidReconnectTimeout = setTimeout(() => {
|
||||
this.androidReconnectTimeout = undefined;
|
||||
this.androidHost?.setDisconnected(true);
|
||||
},20*1000);
|
||||
}, 20 * 1000);
|
||||
}
|
||||
onSocketConnectionLost() {
|
||||
// remove all the events and subscriptions we have.
|
||||
if (this.isClosed)
|
||||
{
|
||||
if (this.isClosed) {
|
||||
return; // page unloading. do NOT change the UI.
|
||||
}
|
||||
this.vuSubscriptions = [];
|
||||
@@ -822,8 +842,7 @@ export class PiPedalModel //implements PiPedalModel
|
||||
|
||||
if (this.isAndroidHosted()) {
|
||||
// if unexpected, go back to the device browser immediately.
|
||||
if (this.reconnectReason === ReconnectReason.Disconnected)
|
||||
{
|
||||
if (this.reconnectReason === ReconnectReason.Disconnected) {
|
||||
this.androidHost?.setDisconnected(true);
|
||||
} else {
|
||||
this.startAndroidReconnectTimer();
|
||||
@@ -848,7 +867,7 @@ export class PiPedalModel //implements PiPedalModel
|
||||
return this.getUpdateStatus(); // detects whether server has been upgraded.
|
||||
})
|
||||
.then((updateStatus) => {
|
||||
|
||||
|
||||
return this.getWebSocket().request<any>("plugins");
|
||||
})
|
||||
.then(data => {
|
||||
@@ -856,7 +875,7 @@ export class PiPedalModel //implements PiPedalModel
|
||||
return this.getWebSocket().request<Pedalboard>("currentPedalboard");
|
||||
})
|
||||
.then(data => {
|
||||
this.pedalboard.set(new Pedalboard().deserialize(data));
|
||||
this.setModelPedalboard(new Pedalboard().deserialize(data));
|
||||
|
||||
return this.getWebSocket().request<boolean>("getShowStatusMonitor");
|
||||
})
|
||||
@@ -923,8 +942,7 @@ export class PiPedalModel //implements PiPedalModel
|
||||
this.setState(State.Ready);
|
||||
})
|
||||
.catch((what) => {
|
||||
if (what instanceof UpdatedError)
|
||||
{
|
||||
if (what instanceof UpdatedError) {
|
||||
// do nothing. a page reload is imminent and unavoidable as soon as we return to the dispatcher.
|
||||
} else {
|
||||
this.onError(what.toString());
|
||||
@@ -1002,7 +1020,7 @@ export class PiPedalModel //implements PiPedalModel
|
||||
return response.json();
|
||||
})
|
||||
.then((countryCodes) => {
|
||||
this.countryCodes = countryCodes as {[Name: string]: string};
|
||||
this.countryCodes = countryCodes as { [Name: string]: string };
|
||||
|
||||
return this.getWebSocket().request<number>("hello");
|
||||
})
|
||||
@@ -1031,7 +1049,7 @@ export class PiPedalModel //implements PiPedalModel
|
||||
return this.getWebSocket().request<Pedalboard>("currentPedalboard");
|
||||
})
|
||||
.then(data => {
|
||||
this.pedalboard.set(new Pedalboard().deserialize(data));
|
||||
this.setModelPedalboard(new Pedalboard().deserialize(data));
|
||||
|
||||
return this.getWebSocket().request<any>("pluginClasses");
|
||||
})
|
||||
@@ -1120,17 +1138,16 @@ export class PiPedalModel //implements PiPedalModel
|
||||
}
|
||||
|
||||
|
||||
onError(message: string| Error): void {
|
||||
onError(message: string | Error): void {
|
||||
let m = message;
|
||||
if (message instanceof Error)
|
||||
{
|
||||
if (message instanceof Error) {
|
||||
let e = message as Error;
|
||||
if (e.message) {
|
||||
m = e.message as string;
|
||||
} else {
|
||||
m = e.toString();
|
||||
}
|
||||
} else{
|
||||
} else {
|
||||
m = message.toString();
|
||||
}
|
||||
this.errorMessage.set(m);
|
||||
@@ -1269,7 +1286,8 @@ export class PiPedalModel //implements PiPedalModel
|
||||
changed = item.setControlValue(controlValue.key, controlValue.value) || changed;
|
||||
}
|
||||
if (changed) {
|
||||
this.pedalboard.set(newPedalboard);
|
||||
newPedalboard.selectedSnapshot = -1;
|
||||
this.setModelPedalboard(newPedalboard);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1354,6 +1372,33 @@ export class PiPedalModel //implements PiPedalModel
|
||||
|
||||
}
|
||||
|
||||
nextBank() {
|
||||
this.webSocket?.send("nextBank");
|
||||
}
|
||||
previousBank() {
|
||||
this.webSocket?.send("previousBank");
|
||||
}
|
||||
nextPreset() {
|
||||
this.webSocket?.send("nextPreset");
|
||||
}
|
||||
previousPreset() {
|
||||
this.webSocket?.send("previousPreset");
|
||||
}
|
||||
|
||||
|
||||
selectSnapshot(index: number) {
|
||||
this.webSocket?.send("setSnapshot", index);
|
||||
}
|
||||
setSnapshots(snapshots: (Snapshot | null)[], selectedSnapshot: number) {
|
||||
let pedalboard = this.pedalboard.get().clone();
|
||||
pedalboard.snapshots = snapshots;
|
||||
if (selectedSnapshot !== -1) {
|
||||
pedalboard.selectedSnapshot = selectedSnapshot;
|
||||
}
|
||||
this.setModelPedalboard(pedalboard);
|
||||
|
||||
this.webSocket?.send("setSnapshots", { snapshots: snapshots, selectedSnapshot: selectedSnapshot });
|
||||
}
|
||||
private _setPedalboardPropertyValue(instanceId: number, propertyUri: string, value: any): void {
|
||||
let body: PatchPropertyChangedBody = {
|
||||
clientId: this.clientId,
|
||||
@@ -1383,7 +1428,8 @@ export class PiPedalModel //implements PiPedalModel
|
||||
if (pedalboard.input_volume_db !== volume_db) {
|
||||
let newPedalboard = pedalboard.clone();
|
||||
newPedalboard.input_volume_db = volume_db;
|
||||
this.pedalboard.set(newPedalboard);
|
||||
newPedalboard.selectedSnapshot = -1;
|
||||
this.setModelPedalboard(newPedalboard);
|
||||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
@@ -1411,7 +1457,8 @@ export class PiPedalModel //implements PiPedalModel
|
||||
if (pedalboard.output_volume_db !== volume_db) {
|
||||
let newPedalboard = pedalboard.clone();
|
||||
newPedalboard.output_volume_db = volume_db;
|
||||
this.pedalboard.set(newPedalboard);
|
||||
newPedalboard.selectedSnapshot = -1;
|
||||
this.setModelPedalboard(newPedalboard);
|
||||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
@@ -1447,7 +1494,8 @@ export class PiPedalModel //implements PiPedalModel
|
||||
if (notifyServer) {
|
||||
this._setServerControl("setControl", instanceId, key, value);
|
||||
}
|
||||
this.pedalboard.set(newPedalboard);
|
||||
newPedalboard.selectedSnapshot = -1;
|
||||
this.setModelPedalboard(newPedalboard);
|
||||
for (let i = 0; i < this._controlValueChangeItems.length; ++i) {
|
||||
let item = this._controlValueChangeItems[i];
|
||||
if (instanceId === item.instanceId) {
|
||||
@@ -1467,7 +1515,7 @@ export class PiPedalModel //implements PiPedalModel
|
||||
item.vstState = state;
|
||||
|
||||
if (changed) {
|
||||
this.pedalboard.set(newPedalboard);
|
||||
this.setModelPedalboard(newPedalboard);
|
||||
if (notifyServer) {
|
||||
this._setServerControl("setControl", instanceId, key, value);
|
||||
}
|
||||
@@ -1499,7 +1547,8 @@ export class PiPedalModel //implements PiPedalModel
|
||||
let changed = value !== item.isEnabled;
|
||||
if (changed) {
|
||||
item.isEnabled = value;
|
||||
this.pedalboard.set(newPedalboard);
|
||||
newPedalboard.selectedSnapshot = -1;
|
||||
this.setModelPedalboard(newPedalboard);
|
||||
if (notifyServer) {
|
||||
let body: PedalboardItemEnableBody = {
|
||||
clientId: this.clientId,
|
||||
@@ -1548,8 +1597,16 @@ export class PiPedalModel //implements PiPedalModel
|
||||
item.pluginName = plugin.name;
|
||||
item.controlValues = this.getDefaultValues(item.uri);
|
||||
item.isEnabled = true;
|
||||
// lv2State: not valid. vstState : not valid.
|
||||
this.pedalboard.set(newPedalboard);
|
||||
item.lv2State = [false, {}];
|
||||
item.vstState = "";
|
||||
item.pathProperties = {};
|
||||
for (let fileProperty of plugin.fileProperties) {
|
||||
// stringized json for an atom. see AtomConverter.hpp.
|
||||
// null -> we've never seen a value.
|
||||
item.pathProperties[fileProperty.patchProperty] = "null";
|
||||
}
|
||||
newPedalboard.selectedSnapshot = -1;
|
||||
this.setModelPedalboard(newPedalboard);
|
||||
this.updateServerPedalboard()
|
||||
return item.instanceId;
|
||||
}
|
||||
@@ -1589,7 +1646,8 @@ export class PiPedalModel //implements PiPedalModel
|
||||
|
||||
let result = newPedalboard.deleteItem(instanceId);
|
||||
if (result !== null) {
|
||||
this.pedalboard.set(newPedalboard);
|
||||
newPedalboard.selectedSnapshot = -1;
|
||||
this.setModelPedalboard(newPedalboard);
|
||||
this.updateServerPedalboard();
|
||||
}
|
||||
return result;
|
||||
@@ -1611,7 +1669,8 @@ export class PiPedalModel //implements PiPedalModel
|
||||
newPedalboard.deleteItem(fromInstanceId);
|
||||
|
||||
newPedalboard.addBefore(fromItem, toInstanceId);
|
||||
this.pedalboard.set(newPedalboard);
|
||||
newPedalboard.selectedSnapshot = -1;
|
||||
this.setModelPedalboard(newPedalboard);
|
||||
this.updateServerPedalboard();
|
||||
|
||||
}
|
||||
@@ -1631,7 +1690,8 @@ export class PiPedalModel //implements PiPedalModel
|
||||
|
||||
newPedalboard.addAfter(fromItem, toInstanceId);
|
||||
|
||||
this.pedalboard.set(newPedalboard);
|
||||
newPedalboard.selectedSnapshot = -1;
|
||||
this.setModelPedalboard(newPedalboard);
|
||||
this.updateServerPedalboard();
|
||||
}
|
||||
|
||||
@@ -1652,7 +1712,8 @@ export class PiPedalModel //implements PiPedalModel
|
||||
|
||||
newPedalboard.addToStart(fromItem);
|
||||
|
||||
this.pedalboard.set(newPedalboard);
|
||||
newPedalboard.selectedSnapshot = -1;
|
||||
this.setModelPedalboard(newPedalboard);
|
||||
this.updateServerPedalboard();
|
||||
}
|
||||
movePedalboardItemToEnd(instanceId: number): void {
|
||||
@@ -1671,7 +1732,9 @@ export class PiPedalModel //implements PiPedalModel
|
||||
|
||||
newPedalboard.addToEnd(fromItem);
|
||||
|
||||
this.pedalboard.set(newPedalboard);
|
||||
newPedalboard.selectedSnapshot = -1;
|
||||
this.setModelPedalboard(newPedalboard);
|
||||
|
||||
this.updateServerPedalboard();
|
||||
|
||||
|
||||
@@ -1697,7 +1760,9 @@ export class PiPedalModel //implements PiPedalModel
|
||||
let emptyItem = newPedalboard.createEmptyItem();
|
||||
newPedalboard.replaceItem(fromInstanceId, emptyItem);
|
||||
newPedalboard.replaceItem(toInstanceId, fromItem);
|
||||
this.pedalboard.set(newPedalboard);
|
||||
|
||||
newPedalboard.selectedSnapshot = -1;
|
||||
this.setModelPedalboard(newPedalboard);
|
||||
this.updateServerPedalboard();
|
||||
|
||||
}
|
||||
@@ -1720,7 +1785,8 @@ export class PiPedalModel //implements PiPedalModel
|
||||
}
|
||||
let newItem = newPedalboard.createEmptyItem();
|
||||
newPedalboard.addItem(newItem, instanceId, append);
|
||||
this.pedalboard.set(newPedalboard);
|
||||
newPedalboard.selectedSnapshot = -1;
|
||||
this.setModelPedalboard(newPedalboard);
|
||||
this.updateServerPedalboard();
|
||||
return newItem.instanceId;
|
||||
}
|
||||
@@ -1745,7 +1811,8 @@ export class PiPedalModel //implements PiPedalModel
|
||||
}
|
||||
let newItem = newPedalboard.createEmptySplit();
|
||||
newPedalboard.addItem(newItem, instanceId, append);
|
||||
this.pedalboard.set(newPedalboard);
|
||||
newPedalboard.selectedSnapshot = -1;
|
||||
this.setModelPedalboard(newPedalboard);
|
||||
this.updateServerPedalboard();
|
||||
return newItem.instanceId;
|
||||
|
||||
@@ -1762,7 +1829,8 @@ export class PiPedalModel //implements PiPedalModel
|
||||
}
|
||||
newPedalboard.setItemEmpty(item);
|
||||
|
||||
this.pedalboard.set(newPedalboard);
|
||||
newPedalboard.selectedSnapshot = -1;
|
||||
this.setModelPedalboard(newPedalboard);
|
||||
this.updateServerPedalboard();
|
||||
return item.instanceId;
|
||||
|
||||
@@ -1956,17 +2024,16 @@ export class PiPedalModel //implements PiPedalModel
|
||||
return this.copyPreset(instanceId, -1);
|
||||
}
|
||||
|
||||
showAlert(message: string| Error): void {
|
||||
showAlert(message: string | Error): void {
|
||||
let m = message;
|
||||
if (message instanceof Error)
|
||||
{
|
||||
if (message instanceof Error) {
|
||||
let e = message as Error;
|
||||
if (e.message) {
|
||||
m = e.message as string;
|
||||
} else {
|
||||
m = e.toString();
|
||||
}
|
||||
} else{
|
||||
} else {
|
||||
m = message.toString();
|
||||
}
|
||||
this.alertMessage.set(m);
|
||||
@@ -2265,7 +2332,8 @@ export class PiPedalModel //implements PiPedalModel
|
||||
let newPedalboard = pedalboard.clone();
|
||||
this.updateVst3State(newPedalboard);
|
||||
if (newPedalboard.setMidiBinding(instanceId, midiBinding)) {
|
||||
this.pedalboard.set(newPedalboard);
|
||||
newPedalboard.selectedSnapshot = -1;
|
||||
this.setModelPedalboard(newPedalboard);
|
||||
|
||||
|
||||
// notify the server.
|
||||
@@ -2329,8 +2397,31 @@ export class PiPedalModel //implements PiPedalModel
|
||||
}
|
||||
}
|
||||
|
||||
private handleNotifyPathPatchPropertyChanged(
|
||||
instanceId: number, propertyUri: string, jsonObject: any
|
||||
) {
|
||||
let pedalboard = this.pedalboard.get();
|
||||
let pedalboardItem = pedalboard.getItem(instanceId);
|
||||
if (pedalboardItem) {
|
||||
pedalboardItem.pathProperties[propertyUri] = jsonObject;
|
||||
}
|
||||
for (let i = 0; i < this.monitorPatchPropertyListeners.length; ++i) {
|
||||
let listener = this.monitorPatchPropertyListeners[i];
|
||||
if (listener.instanceId === instanceId) {
|
||||
listener.callback(instanceId, propertyUri, jsonObject);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
private handleNotifyPatchProperty(clientHandle: number, instanceId: number, propertyUri: string, jsonObject: any) {
|
||||
// yyy this whole path is obsolete. We now get property change notifications
|
||||
// always, since the entire pedalboard must track excactly.
|
||||
// Review carefully.
|
||||
let pedalboard = this.pedalboard.get();
|
||||
let pedalboardItem = pedalboard.getItem(instanceId);
|
||||
if (pedalboardItem) {
|
||||
pedalboardItem.pathProperties[propertyUri] = JSON.stringify(jsonObject);
|
||||
}
|
||||
for (let i = 0; i < this.monitorPatchPropertyListeners.length; ++i) {
|
||||
let listener = this.monitorPatchPropertyListeners[i];
|
||||
if (listener.handle === clientHandle && listener.instanceId === instanceId) {
|
||||
@@ -2367,7 +2458,13 @@ export class PiPedalModel //implements PiPedalModel
|
||||
download(targetType: string, instanceId: number | string): void {
|
||||
if (instanceId === -1) return;
|
||||
let url = this.varServerUrl + targetType + "?id=" + instanceId;
|
||||
window.open(url, "_blank");
|
||||
|
||||
// window.open(url, "_blank");
|
||||
// download with no flashing temporary tab.
|
||||
let link = window.document.createElement("A") as HTMLLinkElement;
|
||||
link.href = url;
|
||||
link.setAttribute("download", "");
|
||||
link.click();
|
||||
}
|
||||
|
||||
uploadFile(uploadPage: string, file: File, contentType: string = "application/octet-stream", abortController?: AbortController): Promise<string> {
|
||||
@@ -2611,10 +2708,9 @@ export class PiPedalModel //implements PiPedalModel
|
||||
wifiConfigSettings.hasPassword = false;
|
||||
wifiConfigSettings.password = "";
|
||||
} else {
|
||||
if (wifiConfigSettings.hasPassword)
|
||||
{
|
||||
wifiConfigSettings.hasSavedPassword = true;
|
||||
}
|
||||
if (wifiConfigSettings.hasPassword) {
|
||||
wifiConfigSettings.hasSavedPassword = true;
|
||||
}
|
||||
}
|
||||
// save a version for the server (potentially carrying a password)
|
||||
let serverConfigSettings: WifiConfigSettings;
|
||||
@@ -2622,7 +2718,7 @@ export class PiPedalModel //implements PiPedalModel
|
||||
serverConfigSettings = wifiConfigSettings.clone();
|
||||
} else {
|
||||
// avoid leaking edits to the server
|
||||
serverConfigSettings = oldSettings.clone();
|
||||
serverConfigSettings = oldSettings.clone();
|
||||
serverConfigSettings.autoStartMode = wifiConfigSettings.autoStartMode;
|
||||
wifiConfigSettings = oldSettings.clone();
|
||||
wifiConfigSettings.autoStartMode = 0;
|
||||
@@ -2714,7 +2810,7 @@ export class PiPedalModel //implements PiPedalModel
|
||||
}
|
||||
|
||||
|
||||
getKnownWifiNetworks() : Promise<string[]> {
|
||||
getKnownWifiNetworks(): Promise<string[]> {
|
||||
let result = new Promise<string[]>((resolve, reject) => {
|
||||
if (!this.webSocket) {
|
||||
reject("Connection closed.");
|
||||
@@ -2727,7 +2823,7 @@ export class PiPedalModel //implements PiPedalModel
|
||||
.catch((err) => reject(err));
|
||||
});
|
||||
return result;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -2945,81 +3041,67 @@ export class PiPedalModel //implements PiPedalModel
|
||||
private expectNetworkChangeTimeout?: NodeJS.Timeout = undefined;
|
||||
private hotspotReconnectTimer?: NodeJS.Timeout = undefined;
|
||||
|
||||
async detectServer(address: string)
|
||||
{
|
||||
async detectServer(address: string) {
|
||||
let port = window.location.port;
|
||||
let newUrl = new URL("http://" + address + ":" + port + "/manifest.json");
|
||||
|
||||
try {
|
||||
let response = await fetch(newUrl);
|
||||
if (response.ok)
|
||||
{
|
||||
return "http://" + address +":"+ port;
|
||||
if (response.ok) {
|
||||
return "http://" + address + ":" + port;
|
||||
}
|
||||
return "";
|
||||
} catch (error: any)
|
||||
{
|
||||
} catch (error: any) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
async pollForLiveServer() {
|
||||
let wifiConfigSettings = this.wifiConfigSettings.get();
|
||||
if (wifiConfigSettings.mdnsName.length !== 0)
|
||||
{
|
||||
if (wifiConfigSettings.mdnsName.length !== 0) {
|
||||
let newUrl = await this.detectServer(wifiConfigSettings.mdnsName);
|
||||
if (newUrl.length !== 0)
|
||||
{
|
||||
if (newUrl.length !== 0) {
|
||||
return newUrl;
|
||||
}
|
||||
}
|
||||
if (this.networkChanging_expectHotspot)
|
||||
{
|
||||
if (this.networkChanging_expectHotspot) {
|
||||
let newUrl = await this.detectServer("10.40.0.1");
|
||||
if (newUrl.length !== 0)
|
||||
{
|
||||
if (newUrl.length !== 0) {
|
||||
return newUrl;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
cancelHotspotReconnectTimer()
|
||||
{
|
||||
if (this.hotspotReconnectTimer)
|
||||
{
|
||||
cancelHotspotReconnectTimer() {
|
||||
if (this.hotspotReconnectTimer) {
|
||||
clearTimeout(this.hotspotReconnectTimer);
|
||||
}
|
||||
|
||||
}
|
||||
startHotspotReconnectTimer()
|
||||
{
|
||||
startHotspotReconnectTimer() {
|
||||
// poll for access to a running pipedal server
|
||||
this.hotspotReconnectTimer = setTimeout(
|
||||
async () => {
|
||||
let newUrl = await this.pollForLiveServer();
|
||||
if (newUrl.length === 0)
|
||||
{
|
||||
if (newUrl.length === 0) {
|
||||
this.startHotspotReconnectTimer();
|
||||
} else {
|
||||
this.cancelOnNetworkChanging();
|
||||
window.location.replace(newUrl);
|
||||
}
|
||||
},
|
||||
5*1000);
|
||||
|
||||
5 * 1000);
|
||||
|
||||
}
|
||||
cancelOnNetworkChanging()
|
||||
{
|
||||
cancelOnNetworkChanging() {
|
||||
this.cancelHotspotReconnectTimer();
|
||||
if (this.expectNetworkChangeTimeout)
|
||||
{
|
||||
if (this.expectNetworkChangeTimeout) {
|
||||
clearTimeout(this.expectNetworkChangeTimeout);
|
||||
this.expectNetworkChangeTimeout = undefined;
|
||||
}
|
||||
this.networkChanging = false;
|
||||
this.expectDisconnect(ReconnectReason.Disconnected);
|
||||
}
|
||||
onNetworkChanging(hotspotConnected: boolean)
|
||||
{
|
||||
onNetworkChanging(hotspotConnected: boolean) {
|
||||
this.cancelOnNetworkChanging();
|
||||
|
||||
this.networkChanging = true;
|
||||
@@ -3030,7 +3112,7 @@ export class PiPedalModel //implements PiPedalModel
|
||||
() => {
|
||||
this.cancelOnNetworkChanging();
|
||||
},
|
||||
30*1000);
|
||||
30 * 1000);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@@ -526,6 +526,7 @@ const PluginControlView =
|
||||
return false;
|
||||
}
|
||||
|
||||
controlKeyIndex : number = 0;
|
||||
controlNodesToNodes(nodes: (ReactNode | ControlGroup)[]): ReactNode[] {
|
||||
let classes = this.props.classes;
|
||||
let isLandscapeGrid = this.state.landscapeGrid;
|
||||
@@ -542,7 +543,7 @@ const PluginControlView =
|
||||
let item = controlGroup.controls[j];
|
||||
controls.push(
|
||||
(
|
||||
<div className={classes.controlPadding}>
|
||||
<div key={"ctl"+(this.controlKeyIndex++)} className={classes.controlPadding}>
|
||||
{item}
|
||||
</div>
|
||||
|
||||
@@ -551,7 +552,7 @@ const PluginControlView =
|
||||
}
|
||||
|
||||
result.push((
|
||||
<div className={!isLandscapeGrid ? classes.portGroup : classes.portGroupLandscape}>
|
||||
<div key={"ctl"+(this.controlKeyIndex++)} className={!isLandscapeGrid ? classes.portGroup : classes.portGroupLandscape}>
|
||||
<div className={classes.portGroupTitle}>
|
||||
<Typography noWrap variant="caption" >{controlGroup.name}</Typography>
|
||||
</div>
|
||||
@@ -565,7 +566,7 @@ const PluginControlView =
|
||||
|
||||
} else {
|
||||
result.push((
|
||||
<div className={hasGroups ? classes.portgroupControlPadding : classes.controlPadding} >
|
||||
<div key={"ctl"+(this.controlKeyIndex++)} className={hasGroups ? classes.portgroupControlPadding : classes.controlPadding} >
|
||||
{node as ReactNode}
|
||||
</div>
|
||||
));
|
||||
@@ -583,6 +584,7 @@ const PluginControlView =
|
||||
|
||||
|
||||
render(): ReactNode {
|
||||
this.controlKeyIndex = 0;
|
||||
let classes = this.props.classes;
|
||||
let pedalboardItem: PedalboardItem;
|
||||
let pedalboard = this.model.pedalboard.get();
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 Robin E. R. 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.
|
||||
*/
|
||||
|
||||
const darkcolors: string[] =
|
||||
[
|
||||
"#b71C1C","#880E4F","#4A148c","#311B92","#1A237E","#0D47A1","#01579B","#006064","#004D40"
|
||||
];
|
||||
|
||||
const lighcolors: string[] = [
|
||||
"#FFcDD2","#F8BBd0","#E1BEE7","#D1c4E9","#C5CAE9","#BBDEFB","#B3E5FC","#B2EbF2","B2DFDB"
|
||||
];
|
||||
export function getLightSnapshotcolors() {
|
||||
let result:string[] = [];
|
||||
for (let h = 0; h < 360; h += 30)
|
||||
{
|
||||
result.push("hsl(" + h + ",100%,79%)");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getDarkSnapshotColors() {
|
||||
let result:string[] = [];
|
||||
for (let h = 0; h < 360; h += 30)
|
||||
{
|
||||
result.push("hsl(" + h + ",90%,20%)");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getSnapshotColors() {
|
||||
return getDarkSnapshotColors();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 Robin E. R. 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 from 'react';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import DialogEx from './DialogEx';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||
import { TransitionProps } from '@mui/material/transitions';
|
||||
|
||||
import Fade from '@mui/material/Fade';
|
||||
import SnapshotPanel from './SnapshotPanel';
|
||||
|
||||
const styles = {
|
||||
dialogPaper: {
|
||||
minHeight: '80vh',
|
||||
maxHeight: '80vh',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
const Transition = React.forwardRef(function Transition(
|
||||
props: TransitionProps & {
|
||||
children: React.ReactElement<any, any>;
|
||||
},
|
||||
ref: React.Ref<unknown>,
|
||||
) {
|
||||
return <Fade in={true} ref={ref} {...props} />;
|
||||
});
|
||||
|
||||
|
||||
|
||||
export interface SnapshotDialogProps {
|
||||
open: boolean,
|
||||
onOk: () => void,
|
||||
};
|
||||
|
||||
export interface SnapshotDialogState {
|
||||
fullScreen: boolean,
|
||||
};
|
||||
|
||||
export default class SnapshotDialog extends ResizeResponsiveComponent<SnapshotDialogProps, SnapshotDialogState> {
|
||||
private model: PiPedalModel;
|
||||
|
||||
constructor(props: SnapshotDialogProps) {
|
||||
super(props);
|
||||
this.model = PiPedalModelFactory.getInstance();
|
||||
this.state = {
|
||||
fullScreen: this.getFullScreen(),
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
super.componentDidMount();
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
super.componentWillUnmount();
|
||||
|
||||
}
|
||||
|
||||
getFullScreen() {
|
||||
return window.innerHeight < 450;
|
||||
}
|
||||
onWindowSizeChanged(width: number, height: number): void {
|
||||
this.setState(
|
||||
{
|
||||
fullScreen: this.getFullScreen(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
return (
|
||||
<DialogEx fullWidth={true} tag="shapshot" open={this.props.open} onClose={() => this.props.onOk()} fullScreen={this.state.fullScreen}
|
||||
TransitionComponent={Transition} keepMounted
|
||||
style={{ userSelect: "none", }}
|
||||
PaperProps={{
|
||||
style: {
|
||||
maxWidth: '1000px'
|
||||
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogTitle>
|
||||
<div>
|
||||
<IconButton edge="start" color="inherit" onClick={() => { this.props.onOk(); }} aria-label="back"
|
||||
>
|
||||
<ArrowBackIcon fontSize="small" style={{ opacity: "0.6" }} />
|
||||
</IconButton>
|
||||
<Typography display="inline" variant="body1" color="textSecondary" >Snapshots</Typography>
|
||||
</div>
|
||||
|
||||
</DialogTitle>
|
||||
<DialogContent style={{
|
||||
overflow: "hidden", margin: 0, height: "80vh",
|
||||
paddingLeft: 16, paddingRight: 16, paddingTop: 0, paddingBottom: 24,
|
||||
}}>
|
||||
<SnapshotPanel panelHeight="80vh" />
|
||||
</DialogContent>
|
||||
</DialogEx>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,345 @@
|
||||
// Copyright (c) 2024 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 { WithStyles } from '@mui/styles';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import AppBar from '@mui/material/AppBar';
|
||||
import Toolbar from '@mui/material/Toolbar';
|
||||
import CssBaseline from '@mui/material/CssBaseline';
|
||||
import createStyles from '@mui/styles/createStyles';
|
||||
import withStyles from '@mui/styles/withStyles';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import FullscreenIcon from '@mui/icons-material/Fullscreen';
|
||||
import FullscreenExitIcon from '@mui/icons-material/FullscreenExit';
|
||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||
import { PiPedalModelFactory, PiPedalModel, State, ZoomedControlInfo } from './PiPedalModel';
|
||||
import ZoomedUiControl from './ZoomedUiControl'
|
||||
import MainPage from './MainPage';
|
||||
import JackStatusView from './JackStatusView';
|
||||
import { Theme } from '@mui/material/styles';
|
||||
import { isDarkMode } from './DarkMode';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import { Snapshot } from './Pedalboard';
|
||||
import ColorDropdownButton from './ColorDropdownButton';
|
||||
|
||||
|
||||
const selectColor = isDarkMode() ? "#888" : "#FFFFFF";
|
||||
|
||||
|
||||
const appStyles = (theme: Theme) => createStyles({
|
||||
"&": { // :root
|
||||
colorScheme: (isDarkMode() ? "dark" : "light")
|
||||
},
|
||||
|
||||
|
||||
mainFrame: {
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
marginTop: 8,
|
||||
flex: "1 1 100%"
|
||||
},
|
||||
heroContent: {
|
||||
backgroundColor: theme.mainBackground,
|
||||
position: "relative",
|
||||
height: "100%",
|
||||
width: "100%"
|
||||
},
|
||||
shadowCatcher: {
|
||||
backgroundColor: theme.mainBackground
|
||||
},
|
||||
|
||||
select: { // fu fu fu.Overrides for white selector on dark background.
|
||||
'&:before': {
|
||||
borderColor: selectColor,
|
||||
},
|
||||
'&:after': {
|
||||
borderColor: selectColor,
|
||||
},
|
||||
'&:hover:not(.Mui-disabled):before': {
|
||||
borderColor: selectColor,
|
||||
}
|
||||
},
|
||||
select_icon: {
|
||||
fill: selectColor,
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
function supportsFullScreen(): boolean {
|
||||
let doc: any = window.document;
|
||||
let docEl: any = doc.documentElement;
|
||||
var requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullScreen || docEl.msRequestFullscreen;
|
||||
return (!!requestFullScreen);
|
||||
|
||||
}
|
||||
|
||||
function setFullScreen(value: boolean) {
|
||||
let doc: any = window.document;
|
||||
let docEl: any = doc.documentElement;
|
||||
|
||||
var requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullScreen || docEl.msRequestFullscreen;
|
||||
var cancelFullScreen = doc.exitFullscreen || doc.mozCancelFullScreen || doc.webkitExitFullscreen || doc.msExitFullscreen;
|
||||
|
||||
if (!doc.fullscreenElement && !doc.mozFullScreenElement && !doc.webkitFullscreenElement && !doc.msFullscreenElement) {
|
||||
requestFullScreen.call(docEl);
|
||||
}
|
||||
else {
|
||||
cancelFullScreen.call(doc);
|
||||
}
|
||||
}
|
||||
|
||||
type SnapshotEditorState = {
|
||||
zoomedControlInfo: ZoomedControlInfo | undefined;
|
||||
zoomedControlOpen: boolean;
|
||||
|
||||
canFullScreen: boolean;
|
||||
isFullScreen: boolean;
|
||||
isDebug: boolean;
|
||||
collapseLabel: boolean;
|
||||
|
||||
showStatusMonitor: boolean;
|
||||
name: string,
|
||||
color: string
|
||||
|
||||
};
|
||||
interface SnapshotEditorProps extends WithStyles<typeof appStyles> {
|
||||
onClose: () => void;
|
||||
onOk: (snapshotIndex: number, name: string, color: string, newSnapshots: (Snapshot | null)[]) => void;
|
||||
snapshotIndex: number;
|
||||
}
|
||||
|
||||
const SnapshotEditor = withStyles(appStyles)(class extends ResizeResponsiveComponent<SnapshotEditorProps, SnapshotEditorState> {
|
||||
// Before the component mounts, we initialise our state
|
||||
|
||||
model_: PiPedalModel;
|
||||
|
||||
getCollapseLabel() {
|
||||
return this.windowSize.width < 500;
|
||||
}
|
||||
|
||||
constructor(props: SnapshotEditorProps) {
|
||||
super(props);
|
||||
this.model_ = PiPedalModelFactory.getInstance();
|
||||
|
||||
this.model_.zoomedUiControl.addOnChangedHandler(
|
||||
() => {
|
||||
this.setState({
|
||||
zoomedControlOpen: this.model_.zoomedUiControl.get() !== undefined,
|
||||
zoomedControlInfo: this.model_.zoomedUiControl.get()
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
let snapshot = this.model_.pedalboard.get().snapshots[this.props.snapshotIndex] ?? new Snapshot();
|
||||
this.state = {
|
||||
zoomedControlOpen: false,
|
||||
zoomedControlInfo: this.model_.zoomedUiControl.get(),
|
||||
canFullScreen: supportsFullScreen() && !this.model_.isAndroidHosted(),
|
||||
isFullScreen: !!document.fullscreenElement,
|
||||
showStatusMonitor: this.model_.showStatusMonitor.get(),
|
||||
isDebug: true,
|
||||
name: snapshot.name,
|
||||
color: snapshot.color,
|
||||
collapseLabel: this.getCollapseLabel()
|
||||
|
||||
};
|
||||
|
||||
this.showStatusMonitorHandler = this.showStatusMonitorHandler.bind(this);
|
||||
}
|
||||
|
||||
showStatusMonitorHandler() {
|
||||
this.setState({
|
||||
showStatusMonitor: this.model_.showStatusMonitor.get()
|
||||
});
|
||||
}
|
||||
toggleFullScreen(): void {
|
||||
setFullScreen(this.state.isFullScreen);
|
||||
this.setState({ isFullScreen: !this.state.isFullScreen });
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
super.componentDidMount();
|
||||
this.model_.showStatusMonitor.addOnChangedHandler(this.showStatusMonitorHandler);
|
||||
|
||||
}
|
||||
|
||||
|
||||
updateOverscroll(): void {
|
||||
if (this.model_.serverVersion) {
|
||||
// no pull-down refresh on android devices once we're ready (unless we're debug)
|
||||
let preventOverscroll =
|
||||
this.model_.state.get() === State.Ready
|
||||
&& !this.model_.debug;
|
||||
|
||||
let overscrollBehavior = preventOverscroll ? "none" : "auto";
|
||||
document.body.style.overscrollBehavior = overscrollBehavior;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
super.componentWillUnmount();
|
||||
this.model_.banks.removeOnChangedHandler(this.showStatusMonitorHandler);
|
||||
}
|
||||
|
||||
onWindowSizeChanged(width: number, height: number): void {
|
||||
super.onWindowSizeChanged(width, height);
|
||||
this.setState({
|
||||
collapseLabel: this.getCollapseLabel()
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
handleOk() {
|
||||
let selectedSnapshot = this.props.snapshotIndex;
|
||||
let currentPedalboard = this.model_.pedalboard.get();
|
||||
let newSnapshots = Snapshot.cloneSnapshots(currentPedalboard.snapshots);
|
||||
let newSnapshot = this.model_.pedalboard.get().makeSnapshot();
|
||||
newSnapshot.name = this.state.name;
|
||||
newSnapshot.color = this.state.color;
|
||||
newSnapshots[selectedSnapshot] = newSnapshot;
|
||||
this.model_.setSnapshots(newSnapshots, selectedSnapshot);
|
||||
|
||||
this.props.onOk(selectedSnapshot, this.state.name, this.state.color, newSnapshots);
|
||||
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
const { classes } = this.props;
|
||||
|
||||
return (
|
||||
|
||||
<div
|
||||
className={classes.shadowCatcher}
|
||||
style={{
|
||||
position: "absolute", top: 0, left: 0, right: 0, bottom: 0,
|
||||
colorScheme: isDarkMode() ? "dark" : "light", // affects scrollbar color
|
||||
minHeight: 345, minWidth: 390,
|
||||
userSelect: "none",
|
||||
display: "flex", flexDirection: "column", flexWrap: "nowrap",
|
||||
overscrollBehavior: this.state.isDebug ? "auto" : "none"
|
||||
}}
|
||||
onContextMenu={(e) => {
|
||||
if (!this.model_.debug) {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<CssBaseline />
|
||||
<AppBar position="static" sx={{ bgcolor: isDarkMode() ? "#100020" : "#200040" }} >
|
||||
<Toolbar variant="dense" >
|
||||
<IconButton
|
||||
edge="start"
|
||||
aria-label="menu"
|
||||
color="inherit"
|
||||
onClick={() => { this.handleOk(); }}
|
||||
size="large">
|
||||
<ArrowBackIcon />
|
||||
</IconButton>
|
||||
{!this.state.collapseLabel && (
|
||||
<Typography style={{ flex: "0 1 auto", opacity: 0.66 }}
|
||||
variant="body1" noWrap
|
||||
>
|
||||
{'Snapshot ' + (this.props.snapshotIndex + 1)}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
<div style={{ flex: "1 1 auto", display: "flex", flexFlow: "column nowrap", marginRight: 8 }}>
|
||||
<TextField
|
||||
sx={{
|
||||
input: { color: 'white' },
|
||||
'& .MuiInput-underline:before': { borderBottomColor: '#FFFFFFC0' },
|
||||
'& .MuiInput-underline:hover:before': { borderBottomColor: '#FFFFFFE0' },
|
||||
'& .MuiInput-underline:after': { borderBottomColor: '#FFFFFF' }
|
||||
}}
|
||||
variant="standard"
|
||||
style={{ flex: "1 1 auto", marginLeft: 16, maxWidth: 500, color: "white" }}
|
||||
value={this.state.name}
|
||||
onChange={(ev) => {
|
||||
this.setState({ name: ev.target.value });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<ColorDropdownButton
|
||||
currentColor={this.state.color}
|
||||
onColorChange={(newColor) => {
|
||||
this.setState({ color: newColor });
|
||||
}} />
|
||||
{this.state.canFullScreen &&
|
||||
<IconButton
|
||||
aria-label="full-screen"
|
||||
onClick={() => { this.toggleFullScreen(); }}
|
||||
color="inherit"
|
||||
size="medium">
|
||||
{this.state.isFullScreen ? (
|
||||
<FullscreenExitIcon style={{ opacity: 0.75 }} />
|
||||
) : (
|
||||
<FullscreenIcon style={{ opacity: 0.75 }} />
|
||||
|
||||
)}
|
||||
|
||||
</IconButton>
|
||||
}
|
||||
<IconButton
|
||||
aria-label="cancel"
|
||||
onClick={() => { this.props.onClose(); }}
|
||||
color="inherit"
|
||||
size="medium">
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<main className={classes.mainFrame} >
|
||||
<div style={{
|
||||
overflow: "hidden",
|
||||
flex: "1 1 auto"
|
||||
}} >
|
||||
<div className={classes.heroContent}>
|
||||
<MainPage hasTinyToolBar={false} enableStructureEditing={false} />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<ZoomedUiControl
|
||||
dialogOpen={this.state.zoomedControlOpen}
|
||||
controlInfo={this.state.zoomedControlInfo}
|
||||
onDialogClose={() => { this.setState({ zoomedControlOpen: false }); }}
|
||||
onDialogClosed={() => { this.model_.zoomedUiControl.set(undefined); }
|
||||
}
|
||||
/>
|
||||
{this.state.showStatusMonitor && (<JackStatusView />)}
|
||||
</div >
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export default SnapshotEditor;
|
||||
@@ -0,0 +1,401 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 Robin E. R. 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 Typography from '@mui/material/Typography';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import ButtonBase from '@mui/material/ButtonBase';
|
||||
import { Pedalboard, Snapshot } from './Pedalboard';
|
||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||
import SaveIconOutline from '@mui/icons-material/Save';
|
||||
import Button from "@mui/material/Button";
|
||||
import EditIconOutline from '@mui/icons-material/Edit';
|
||||
|
||||
import SnapshotPropertiesDialog from './SnapshotPropertiesDialog';
|
||||
import { colorKeys, getBackgroundColor, getBorderColor } from './MaterialColors';
|
||||
|
||||
|
||||
|
||||
export interface SnapshotPanelProps {
|
||||
panelHeight?: string,
|
||||
onEdit?: (snapshotIndex: number) => boolean
|
||||
};
|
||||
|
||||
export interface SnapshotPanelState {
|
||||
snapshots: (Snapshot | null)[],
|
||||
portraitOrientation: boolean,
|
||||
collapseButtons: boolean,
|
||||
largeText: boolean,
|
||||
|
||||
snapshotPropertiesDialogOpen: boolean,
|
||||
editTitle: string,
|
||||
editColor: string,
|
||||
editing: boolean,
|
||||
editId: number,
|
||||
selectedSnapshot: number
|
||||
|
||||
};
|
||||
|
||||
export default class SnapshotPanel extends ResizeResponsiveComponent<SnapshotPanelProps, SnapshotPanelState> {
|
||||
private model: PiPedalModel;
|
||||
|
||||
constructor(props: SnapshotPanelProps) {
|
||||
super(props);
|
||||
this.model = PiPedalModelFactory.getInstance();
|
||||
this.state = {
|
||||
snapshots: this.getCurrentSnapshots(),
|
||||
selectedSnapshot: this.getSelectedSnapshot(),
|
||||
portraitOrientation: this.getPortraitOrientation(),
|
||||
collapseButtons: this.getCollapseButtons(),
|
||||
largeText: this.getLargeText(),
|
||||
|
||||
snapshotPropertiesDialogOpen: false,
|
||||
editTitle: "",
|
||||
editColor: "",
|
||||
editing: false,
|
||||
editId: 0,
|
||||
|
||||
};
|
||||
this.onPedalboardChanged = this.onPedalboardChanged.bind(this);
|
||||
this.onSelectedSnapshotChanged = this.onSelectedSnapshotChanged.bind(this);
|
||||
}
|
||||
|
||||
getCurrentSnapshots() {
|
||||
let result: (Snapshot | null)[] = [];
|
||||
let currentSnapshots = this.model.pedalboard.get().snapshots;
|
||||
for (let i = 0; i < 6; ++i) {
|
||||
let mySnapshot = null;
|
||||
if (i < currentSnapshots.length) {
|
||||
if (currentSnapshots[i] !== null) {
|
||||
mySnapshot = new Snapshot().deserialize(currentSnapshots[i]); // a deep clone.
|
||||
}
|
||||
}
|
||||
result.push(mySnapshot);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
getSelectedSnapshot() {
|
||||
return this.model.selectedSnapshot.get();
|
||||
}
|
||||
|
||||
onSelectedSnapshotChanged(value: number) {
|
||||
this.setState({ selectedSnapshot: this.getSelectedSnapshot() })
|
||||
}
|
||||
onPedalboardChanged(value: Pedalboard) {
|
||||
// boofs our current edit, oh well. we can't track property across a structural change.
|
||||
this.setState({
|
||||
snapshots: this.getCurrentSnapshots(),
|
||||
selectedSnapshot: this.getSelectedSnapshot()
|
||||
});
|
||||
}
|
||||
componentDidMount(): void {
|
||||
super.componentDidMount();
|
||||
this.model.pedalboard.addOnChangedHandler(this.onPedalboardChanged);
|
||||
this.model.selectedSnapshot.addOnChangedHandler(this.onSelectedSnapshotChanged);
|
||||
this.setState({
|
||||
snapshots: this.getCurrentSnapshots(),
|
||||
selectedSnapshot: this.getSelectedSnapshot()
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
super.componentWillUnmount();
|
||||
this.model.selectedSnapshot.removeOnChangedHandler(this.onSelectedSnapshotChanged);
|
||||
this.model.pedalboard.removeOnChangedHandler(this.onPedalboardChanged)
|
||||
|
||||
}
|
||||
|
||||
getCollapseButtons() {
|
||||
if (this.getPortraitOrientation())
|
||||
{
|
||||
return window.innerWidth < 550;
|
||||
} else {
|
||||
return window.innerWidth < 800;
|
||||
}
|
||||
return window.innerWidth < 500;
|
||||
}
|
||||
getPortraitOrientation() {
|
||||
return window.innerWidth * 2 < window.innerHeight * 3;
|
||||
}
|
||||
|
||||
getLargeText() {
|
||||
return window.innerHeight > 700 && window.innerWidth > 1000;
|
||||
}
|
||||
onSaveSnapshot(index: number) {
|
||||
let snapshot = this.state.snapshots[index];
|
||||
if (snapshot) {
|
||||
// no dialog required. just save it.
|
||||
this.handleSnapshotPropertyOk(
|
||||
index,
|
||||
snapshot.name,
|
||||
snapshot.color,
|
||||
false);
|
||||
} else {
|
||||
this.setState({
|
||||
editTitle: "",
|
||||
editColor: "",
|
||||
editId: index,
|
||||
snapshotPropertiesDialogOpen: true,
|
||||
editing: false
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
onEditSnapshot(index: number) {
|
||||
if (this.props.onEdit) {
|
||||
if (this.props.onEdit(index)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
let snapshot = this.state.snapshots[index];
|
||||
if (snapshot) {
|
||||
this.setState({
|
||||
editTitle: snapshot.name,
|
||||
editColor: snapshot.color,
|
||||
editId: index,
|
||||
snapshotPropertiesDialogOpen: true,
|
||||
editing: true
|
||||
});
|
||||
}
|
||||
}
|
||||
onWindowSizeChanged(width: number, height: number): void {
|
||||
this.setState(
|
||||
{
|
||||
portraitOrientation: this.getPortraitOrientation(),
|
||||
collapseButtons: this.getCollapseButtons(),
|
||||
largeText: this.getLargeText()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
selectSnapshot(index: number) {
|
||||
this.model.selectSnapshot(index);
|
||||
}
|
||||
renderSnapshot(snapshot: Snapshot | null, index: number) {
|
||||
//let state = this.state;
|
||||
let color: string;
|
||||
let bordercolor: string;
|
||||
if (snapshot) {
|
||||
if (colorKeys.indexOf(snapshot.color) === -1) {
|
||||
bordercolor = color = snapshot.color;
|
||||
} else {
|
||||
color = getBackgroundColor(snapshot.color);
|
||||
bordercolor = getBorderColor(snapshot.color);
|
||||
}
|
||||
} else {
|
||||
bordercolor = color = getBackgroundColor("grey");
|
||||
}
|
||||
let title = snapshot ? snapshot.name : "<unassigned>";
|
||||
let disabled = snapshot === null;
|
||||
let selected = this.state.selectedSnapshot === index;
|
||||
return (
|
||||
<div style={{ display: "flex", position: "relative",flexGrow: 1, flexBasis: 1, flexFlow: "column nowrap", alignItems: "stretch", borderRadius: 16 }}>
|
||||
|
||||
<ButtonBase style={{
|
||||
display: "flex", flexGrow: 1, flexBasis: 1, flexFlow: "column nowrap", alignItems: "stretch", borderRadius: 16,
|
||||
background: color,
|
||||
boxShadow: "1px 3px 6px #00000090"
|
||||
}}
|
||||
onMouseDown={(ev) => {
|
||||
if (!selected) ev.stopPropagation();
|
||||
}}
|
||||
|
||||
onClick={() => { if (snapshot) this.selectSnapshot(index); }}
|
||||
>
|
||||
<div
|
||||
style={{ flexGrow: 1, flexShrink: 1, display: "flex", flexFlow: "column nowrap", alignItems: "stretch" }}
|
||||
onMouseDown={(ev) => {
|
||||
if (disabled) ev.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<div style={{
|
||||
flexGrow: 1, flexShrink: 1, display: "flex", flexFlow: "column nowrap", alignItems: "stretch", justifyContent: "center", margin: 6,
|
||||
borderColor: selected ? bordercolor : "transparent", borderStyle: "solid", borderWidth: 3, borderRadius: 10
|
||||
}}
|
||||
>
|
||||
<div style={{ flexGrow: 1, display: "flex", alignItems: "center", justifyContent: "center" }}>
|
||||
|
||||
<Typography display="block" color="textPrimary" align="center" variant="body1"
|
||||
style={{ fontSize: this.state.largeText ? "30px" : undefined }}
|
||||
>{title}</Typography>
|
||||
</div>
|
||||
<div style={{ height: 54,flexGrow: 0,flexShrink: 0 }}>
|
||||
{/* placeholder where we will float the buttons, which can't be witin another button (tons of DOM validation warning messages in chrome) */}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</ButtonBase >
|
||||
<div style={{ flexGrow: 0, position: "absolute", bottom:0,left:0,right:0,paddingBottom:10,paddingLeft: 16, paddingRight:12 }} >
|
||||
<Typography variant="h3" style={{opacity: 0.15,fontSize: "44px",fontWeight:900,fontFamily: "Arial Black",fontStyle: "italic"}} >{(index+1).toString()}</Typography>
|
||||
</div>
|
||||
|
||||
<div style={{ flexGrow: 0, position: "absolute", bottom:0,left:0,right:0,paddingBottom:12,paddingLeft: 12, paddingRight:12 }} >
|
||||
{!this.state.collapseButtons ? (
|
||||
<div style={{ marginRight: 8, display: "flex", flexFlow: "row nowrap" }}>
|
||||
<div style={{ flexGrow: 1, flexBasis: 1 }} > </div>
|
||||
<Button variant="dialogSecondary" startIcon={<SaveIconOutline />} color="inherit" style={{ textTransform: "none" }}
|
||||
onMouseDown={(ev) => { ev.stopPropagation(); /*don't prop to card*/ }}
|
||||
onMouseUp={(ev) => { ev.stopPropagation(); /*don't prop to card*/ }}
|
||||
disabled={false}
|
||||
onClick={(ev) => { ev.stopPropagation(); this.onSaveSnapshot(index); }}
|
||||
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button variant="dialogSecondary" color="inherit" startIcon={<EditIconOutline />} style={{ textTransform: "none" }}
|
||||
onMouseDown={(ev) => { ev.stopPropagation(); /*don't prop to card*/ }}
|
||||
onMouseUp={(ev) => { ev.stopPropagation(); /*don't prop to card*/ }}
|
||||
disabled={disabled}
|
||||
onClick={(ev) => { ev.stopPropagation(); this.onEditSnapshot(index); }}
|
||||
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
) : (
|
||||
<div style={{ marginLeft: "auto", marginRight: 8, display: "flex", flexFlow: "row nowrap" }}>
|
||||
<div style={{ flexGrow: 1, flexBasis: 1 }} > </div>
|
||||
<IconButton color="inherit" style={{ opacity: 0.66 }} disabled={false}
|
||||
onMouseDown={(ev) => { ev.stopPropagation(); /*don't prop to card*/ }}
|
||||
onClick={() => { this.onSaveSnapshot(index); }}
|
||||
>
|
||||
<SaveIconOutline />
|
||||
</IconButton>
|
||||
<IconButton color="inherit" disabled={disabled} style={{ opacity: 0.66 }}
|
||||
onMouseDown={(ev) => { ev.stopPropagation(); /*don't prop to card*/ }}
|
||||
onClick={() => { this.onEditSnapshot(index); }}
|
||||
|
||||
>
|
||||
<EditIconOutline />
|
||||
</IconButton>
|
||||
</div>
|
||||
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div >
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
handleSnapshotPropertyOk(index: number, name: string, color: string, editing: boolean) {
|
||||
if (editing) {
|
||||
let snapshots = Snapshot.cloneSnapshots(this.state.snapshots);
|
||||
let snapshot = snapshots[index];
|
||||
if (snapshot) {
|
||||
snapshot.name = name;
|
||||
snapshot.color = color;
|
||||
}
|
||||
this.setState({
|
||||
snapshots: snapshots,
|
||||
snapshotPropertiesDialogOpen: false
|
||||
});
|
||||
this.model.setSnapshots(snapshots, -1);
|
||||
|
||||
} else {
|
||||
let pedalboard = this.model.pedalboard.get();
|
||||
let newSnapshot = pedalboard.makeSnapshot();
|
||||
newSnapshot.name = name;
|
||||
newSnapshot.color = color;
|
||||
let snapshots = Snapshot.cloneSnapshots(this.state.snapshots);
|
||||
snapshots[index] = newSnapshot;
|
||||
this.setState({
|
||||
snapshots: snapshots,
|
||||
snapshotPropertiesDialogOpen: false
|
||||
});
|
||||
this.model.setSnapshots(snapshots, index);
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
bg: undefined;
|
||||
|
||||
render() {
|
||||
let snapshots = this.state.snapshots;
|
||||
return (
|
||||
<div style={{
|
||||
flex: "1 1 auto",
|
||||
height: this.props.panelHeight,
|
||||
overflow: "hidden", margin: 0,
|
||||
paddingLeft: 16, paddingRight: 16, paddingTop: 0, paddingBottom: 24,
|
||||
background: this.bg,
|
||||
display: "flex", flexFlow: "column nowrap", alignContent: "stretch", justifyContent: "stretch"
|
||||
}}>
|
||||
|
||||
<div style={{
|
||||
flexGrow: 1, flexShrink: 1, flexBasis: 1,
|
||||
|
||||
display: "flex",
|
||||
flexFlow: this.state.portraitOrientation ?
|
||||
"row nowrap" : "column nowrap",
|
||||
alignItems: "stretch", gap: 8
|
||||
}}>
|
||||
<div style={{
|
||||
flexGrow: 1, flexShrink: 1, flexBasis: 1,
|
||||
display: "flex",
|
||||
flexFlow: this.state.portraitOrientation ?
|
||||
"column nowrap"
|
||||
: "row nowrap",
|
||||
alignItems: "stretch", gap: 8
|
||||
}}>
|
||||
{this.renderSnapshot(snapshots[0], 0)}
|
||||
{this.renderSnapshot(snapshots[1], 1)}
|
||||
{this.renderSnapshot(snapshots[2], 2)}
|
||||
</div>
|
||||
<div style={{
|
||||
flexGrow: 1, flexShrink: 1, flexBasis: 1,
|
||||
display: "flex",
|
||||
flexFlow: this.state.portraitOrientation ? "column nowrap" : "row nowrap", alignItems: "stretch", gap: 8
|
||||
}}>
|
||||
{this.renderSnapshot(snapshots[3], 3)}
|
||||
{this.renderSnapshot(snapshots[4], 4)}
|
||||
{this.renderSnapshot(snapshots[5], 5)}
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
this.state.snapshotPropertiesDialogOpen && (
|
||||
<SnapshotPropertiesDialog
|
||||
open={this.state.snapshotPropertiesDialogOpen}
|
||||
name={this.state.editTitle}
|
||||
color={this.state.editColor}
|
||||
editing={this.state.editing}
|
||||
snapshotIndex={this.state.editId}
|
||||
onClose={() => { this.setState({ snapshotPropertiesDialogOpen: false }); }}
|
||||
onOk={(snapshotId, name, color, editing) => {
|
||||
this.handleSnapshotPropertyOk(snapshotId, name, color, editing);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2024 Robin E. R. 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 from 'react';
|
||||
import Button from '@mui/material/Button';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import ButtonBase from "@mui/material/ButtonBase"
|
||||
import Typography from '@mui/material/Typography';
|
||||
import DialogEx from './DialogEx';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import { colorKeys, getBackgroundColor } from "./MaterialColors";
|
||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||
|
||||
const snapshotColors = colorKeys.slice(0,100);
|
||||
const NBSP = "\u00A0";
|
||||
|
||||
export interface SnapshotPropertiesDialogProps {
|
||||
open: boolean,
|
||||
name: string,
|
||||
editing: boolean,
|
||||
snapshotIndex: number,
|
||||
color: string,
|
||||
onOk: (snapshotId: number, name: string, color: string, editing: boolean) => void,
|
||||
onClose: () => void
|
||||
};
|
||||
|
||||
export interface SnapshotPropertiesDialogState {
|
||||
name: string,
|
||||
color: string,
|
||||
nameErrorMessage: string,
|
||||
compactVertical: boolean,
|
||||
};
|
||||
|
||||
export default class SnapshotPropertiesDialog extends ResizeResponsiveComponent<SnapshotPropertiesDialogProps, SnapshotPropertiesDialogState> {
|
||||
|
||||
|
||||
constructor(props: SnapshotPropertiesDialogProps) {
|
||||
super(props);
|
||||
this.state = this.stateFromProps();
|
||||
}
|
||||
getCompactVertical() {
|
||||
return window.innerHeight < 450;
|
||||
}
|
||||
stateFromProps() {
|
||||
let color = this.props.color;
|
||||
if (color.length === 0) {
|
||||
color = snapshotColors[0];
|
||||
}
|
||||
let name = this.props.name;
|
||||
if (name.length === 0 && !this.props.editing)
|
||||
{
|
||||
name = "Snapshot " + (this.props.snapshotIndex+1);
|
||||
}
|
||||
return {
|
||||
name: name,
|
||||
color: color,
|
||||
nameErrorMessage: "",
|
||||
compactVertical: this.getCompactVertical()
|
||||
};
|
||||
}
|
||||
|
||||
onWindowSizeChanged(width: number, height: number): void {
|
||||
super.onWindowSizeChanged(width,height);
|
||||
this.setState({compactVertical: this.getCompactVertical() })
|
||||
}
|
||||
|
||||
handleOk() {
|
||||
if (this.state.name === "") {
|
||||
this.setState({ nameErrorMessage: "* Required" });
|
||||
} else {
|
||||
this.props.onOk(
|
||||
this.props.snapshotIndex,
|
||||
this.state.name,
|
||||
this.state.color,
|
||||
this.props.editing
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let props = this.props;
|
||||
|
||||
const handleClose = () => {
|
||||
props.onClose();
|
||||
};
|
||||
|
||||
let okButtonText = this.props.editing ? "OK" : "Save";
|
||||
return (
|
||||
<DialogEx maxWidth={false} tag="snapshotProps" open={this.props.open} onClose={handleClose}
|
||||
style={{ userSelect: "none" }} fullScreen={this.state.compactVertical}
|
||||
>
|
||||
<DialogTitle>
|
||||
<div>
|
||||
<IconButton edge="start" color="inherit" onClick={() => { this.props.onClose(); }} aria-label="back"
|
||||
>
|
||||
<ArrowBackIcon fontSize="small" style={{ opacity: "0.6" }} />
|
||||
</IconButton>
|
||||
<Typography display="inline" variant="body1" color="textSecondary" >
|
||||
{this.props.editing ? "Edit snapshot" : "Save snapshot"}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
|
||||
<TextField variant="standard" style={{ flexGrow: 1, flexBasis: 1 }}
|
||||
autoComplete="off"
|
||||
spellCheck="false"
|
||||
error={this.state.nameErrorMessage.length !== 0}
|
||||
id="name"
|
||||
label="Name"
|
||||
type="text"
|
||||
fullWidth
|
||||
helperText={this.state.nameErrorMessage.length === 0 ? NBSP : this.state.nameErrorMessage}
|
||||
value={this.state.name}
|
||||
onChange={(e) => this.setState({ name: e.target.value, nameErrorMessage: "" })}
|
||||
InputLabelProps={{
|
||||
shrink: true
|
||||
}}
|
||||
/>
|
||||
<Typography variant="caption" color="textSecondary">Color</Typography>
|
||||
<div style={{ margin: 4,display: "flex", flexFlow: "row wrap", gap: 8 }}>
|
||||
{
|
||||
snapshotColors.map((colorKey, index) => {
|
||||
let selected = this.state.color === colorKey;
|
||||
let color = getBackgroundColor(colorKey);
|
||||
return (
|
||||
<ButtonBase
|
||||
key={"colorTag" + index}
|
||||
style={{
|
||||
width: 48, height: 48,
|
||||
borderRadius: 8,
|
||||
borderStyle: "solid",
|
||||
borderColor: selected ? color : "transparent",
|
||||
borderWidth: 2,
|
||||
}}
|
||||
onClick={()=> {
|
||||
this.setState({color: colorKey});
|
||||
}}
|
||||
|
||||
>
|
||||
<div style={{
|
||||
width: 40, height: 40,
|
||||
borderRadius: 4,
|
||||
margin: 2,
|
||||
background: color
|
||||
}}>
|
||||
</div>
|
||||
|
||||
</ButtonBase>
|
||||
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button variant="dialogSecondary" onClick={handleClose} >
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="dialogPrimary" onClick={()=> {this.handleOk();}} >
|
||||
{okButtonText}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</DialogEx>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 -960 960 960"
|
||||
id="svg2989"
|
||||
>
|
||||
<defs
|
||||
id="defs2995" />
|
||||
<path
|
||||
d="m 480,-260 q 75,0 127.5,-52.5 Q 660,-365 660,-440 660,-515 607.5,-567.5 555,-620 480,-620 405,-620 352.5,-567.5 300,-515 300,-440 q 0,75 52.5,127.5 Q 405,-260 480,-260 z m 0,-80 q -42,0 -71,-29 -29,-29 -29,-71 0,-42 29,-71 29,-29 71,-29 42,0 71,29 29,29 29,71 0,42 -29,71 -29,29 -71,29 z m -320,220 q -33,0 -56.5,-23.5 Q 80,-167 80,-200 v -480 q 0,-33 23.5,-56.5 Q 127,-760 160,-760 h 126 l 74,-80 h 240 l 74,80 h 126 q 33,0 56.5,23.5 23.5,23.5 23.5,56.5 v 480 q 0,33 -23.5,56.5 Q 833,-120 800,-120 H 160 z m 0,-80 H 800 V -680 H 638 l -73,-80 H 395 l -73,80 H 160 v 480 z m 320,-240 z"
|
||||
id="path2991" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 914 B |
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 -960 960 960"
|
||||
id="svg2989"
|
||||
>
|
||||
<path
|
||||
d="m 160,-120 c -22,0 -40.83333,-7.83333 -56.5,-23.5 C 87.833333,-159.16667 80,-178 80,-200 l 0,-480 c 0,-22 7.833333,-40.83333 23.5,-56.5 15.66667,-15.66667 34.5,-23.5 56.5,-23.5 l 126,0 74,-80 240,0 74,80 126,0 c 22,0 40.83333,7.83333 56.5,23.5 15.66667,15.66667 23.5,34.5 23.5,56.5 l 0,480 c 0,22 -7.83333,40.83333 -23.5,56.5 -15.66667,15.66667 -34.5,23.5 -56.5,23.5 z m 0,-80 640,0 0,-480 -162,0 -73,-80 -170,0 -73,80 -162,0 z"
|
||||
id="path2991" />
|
||||
<path
|
||||
d="m 466.24994,-624.73151 74.61914,0 0,335.91797 -89.82422,0 0,-238.53515 -56.95312,41.36718 -42.31934,-62.44629 z"
|
||||
id="path3772"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 886 B |
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 -960 960 960"
|
||||
id="svg2989"
|
||||
>
|
||||
<path
|
||||
d="m 160,-120 c -22,0 -40.83333,-7.83333 -56.5,-23.5 C 87.833333,-159.16667 80,-178 80,-200 l 0,-480 c 0,-22 7.833333,-40.83333 23.5,-56.5 15.66667,-15.66667 34.5,-23.5 56.5,-23.5 l 126,0 74,-80 240,0 74,80 126,0 c 22,0 40.83333,7.83333 56.5,23.5 15.66667,15.66667 23.5,34.5 23.5,56.5 l 0,480 c 0,22 -7.83333,40.83333 -23.5,56.5 -15.66667,15.66667 -34.5,23.5 -56.5,23.5 z m 0,-80 640,0 0,-480 -162,0 -73,-80 -170,0 -73,80 -162,0 z"
|
||||
id="path2991" />
|
||||
<path
|
||||
d="m 464.58002,-359.31647 130.06347,0 0,70.50293 -243.77929,0 0,-69.71191 c 52.40226,-42.73426 89.24793,-76.47202 110.53711,-101.21338 21.2889,-24.74102 31.93342,-45.08523 31.93359,-61.03272 -1.7e-4,-11.65991 -3.7697,-20.95677 -11.30859,-27.89062 -7.53922,-6.93333 -17.67593,-10.40012 -30.41016,-10.40039 -11.68957,2.7e-4 -24.34337,1.91189 -37.96143,5.73486 -13.61824,3.82351 -30.86676,9.8025 -51.7456,17.93701 l -2.54883,-75.04394 c 20.30268,-7.66569 39.73137,-13.22965 58.28613,-16.6919 18.55458,-3.46157 37.07507,-5.19252 55.56153,-5.19287 34.45294,3.5e-4 62.57791,9.49252 84.375,28.47657 21.79661,18.98467 32.69504,43.37917 32.69531,73.18359 -2.7e-4,25.61545 -10.25905,52.81513 -30.77637,81.59912 -20.51779,28.78431 -52.15839,58.69883 -94.92187,89.74365 z"
|
||||
id="path3775"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 -960 960 960"
|
||||
id="svg2989"
|
||||
>
|
||||
<path
|
||||
d="m 160,-120 c -22,0 -40.83333,-7.83333 -56.5,-23.5 C 87.833333,-159.16667 80,-178 80,-200 l 0,-480 c 0,-22 7.833333,-40.83333 23.5,-56.5 15.66667,-15.66667 34.5,-23.5 56.5,-23.5 l 126,0 74,-80 240,0 74,80 126,0 c 22,0 40.83333,7.83333 56.5,23.5 15.66667,15.66667 23.5,34.5 23.5,56.5 l 0,480 c 0,22 -7.83333,40.83333 -23.5,56.5 -15.66667,15.66667 -34.5,23.5 -56.5,23.5 z m 0,-80 640,0 0,-480 -162,0 -73,-80 -170,0 -73,80 -162,0 z"
|
||||
id="path2991" />
|
||||
<path
|
||||
d="m 525.94232,-463.5401 c 20.01931,2.99822 35.90064,11.34538 47.64404,25.0415 11.74291,13.69643 17.61448,30.62268 17.61475,50.77881 -2.7e-4,31.46491 -13.95533,56.27446 -41.86523,74.42871 -27.91036,18.15431 -65.69352,27.23145 -113.34961,27.23145 -14.88291,0 -29.52157,-0.92285 -43.91602,-2.76856 -14.39458,-1.8457 -30.20023,-5.083 -47.41699,-9.71191 l 5.33203,-75.9375 c 16.123,6.02547 30.38813,10.44197 42.79541,13.24951 12.40714,2.80769 25.42715,4.2115 39.06006,4.21143 20.41003,7e-5 36.10581,-3.4179 47.0874,-10.25391 10.98128,-6.83585 16.472,-15.95205 16.47217,-27.34863 -1.7e-4,-10.37098 -4.06023,-18.31042 -12.18018,-23.81836 -8.12027,-5.50768 -20.41762,-8.26158 -36.89209,-8.26172 l -45.60058,0 0,-66.81152 42.37793,0 c 15.26353,2e-4 27.3729,-2.98564 36.32812,-8.95752 8.95492,-5.97146 13.43245,-13.52761 13.43262,-22.66846 -1.7e-4,-9.69702 -4.57048,-17.58275 -13.71094,-23.65723 -9.14077,-6.07395 -21.76771,-9.11106 -37.88086,-9.11132 -12.22667,2.6e-4 -24.54843,1.18435 -36.96533,3.55224 -12.41706,2.36843 -27.25835,6.00856 -44.52392,10.92041 l -4.33594,-68.67187 c 18.74018,-5.57585 36.48919,-9.57242 53.24707,-11.98975 16.75771,-2.41665 33.81824,-3.62515 51.18164,-3.62549 39.17951,3.4e-4 69.86551,8.23031 92.0581,24.68994 22.19214,16.46026 33.28832,39.28495 33.28858,68.47412 -2.6e-4,18.86742 -5.34694,34.70725 -16.04004,47.51954 -10.6936,12.81268 -25.10764,20.6447 -43.24219,23.49609 z"
|
||||
id="path3778"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 -960 960 960"
|
||||
id="svg2989"
|
||||
>
|
||||
<path
|
||||
d="m 160,-120 c -22,0 -40.83333,-7.83333 -56.5,-23.5 C 87.833333,-159.16667 80,-178 80,-200 l 0,-480 c 0,-22 7.833333,-40.83333 23.5,-56.5 15.66667,-15.66667 34.5,-23.5 56.5,-23.5 l 126,0 74,-80 240,0 74,80 126,0 c 22,0 40.83333,7.83333 56.5,23.5 15.66667,15.66667 23.5,34.5 23.5,56.5 l 0,480 c 0,22 -7.83333,40.83333 -23.5,56.5 -15.66667,15.66667 -34.5,23.5 -56.5,23.5 z m 0,-80 640,0 0,-480 -162,0 -73,-80 -170,0 -73,80 -162,0 z"
|
||||
id="path2991" />
|
||||
<path
|
||||
d="m 485.98138,-553.77448 -81.15234,133.79883 76.93359,0 z m -19.13086,-70.95703 104.58985,0 0,204.75586 44.34082,0 0,68.20313 -44.34082,0 0,62.95898 -89.82422,0 0,-62.95898 -146.38184,0 0,-78.22266 z"
|
||||
id="path3781" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 966 B |
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 -960 960 960"
|
||||
id="svg2989"
|
||||
>
|
||||
<path
|
||||
d="m 160,-120 c -22,0 -40.83333,-7.83333 -56.5,-23.5 C 87.833333,-159.16667 80,-178 80,-200 l 0,-480 c 0,-22 7.833333,-40.83333 23.5,-56.5 15.66667,-15.66667 34.5,-23.5 56.5,-23.5 l 126,0 74,-80 240,0 74,80 126,0 c 22,0 40.83333,7.83333 56.5,23.5 15.66667,15.66667 23.5,34.5 23.5,56.5 l 0,480 c 0,22 -7.83333,40.83333 -23.5,56.5 -15.66667,15.66667 -34.5,23.5 -56.5,23.5 z m 0,-80 640,0 0,-480 -162,0 -73,-80 -170,0 -73,80 -162,0 z"
|
||||
id="path2991" />
|
||||
<path
|
||||
d="m 363.08099,-624.73151 221.22071,0 0,67.74903 -135.30762,0 -1.18652,45.54199 c 2.29479,-0.68337 6.31822,-1.23757 12.07031,-1.6626 5.75181,-0.42458 10.65415,-0.63698 14.70703,-0.63721 37.49004,2.3e-4 67.33132,9.93186 89.52393,29.79493 22.19211,19.86345 33.2883,46.49917 33.28857,79.90722 -2.7e-4,35.85946 -13.09108,64.48736 -39.27246,85.88379 -26.18185,21.39649 -61.22576,32.09473 -105.13184,32.09473 -17.79308,0 -34.15048,-1.04248 -49.07226,-3.12744 -14.92194,-2.08496 -30.3858,-5.71045 -46.3916,-10.87647 l 0,-77.63672 c 17.0019,6.62118 31.97747,11.49422 44.92675,14.61914 12.94913,3.12508 26.03994,4.68757 39.27246,4.6875 18.59362,7e-5 33.33725,-3.98918 44.23096,-11.96777 10.89338,-7.97843 16.34015,-18.28115 16.34033,-30.9082 -1.8e-4,-14.66784 -5.84001,-26.09606 -17.51953,-34.28467 -11.67984,-8.18832 -28.5597,-12.28256 -50.63965,-12.28272 -9.88291,1.6e-4 -20.32235,0.67887 -31.31836,2.03614 -10.99616,1.35758 -24.86821,3.51578 -41.61621,6.47461 z"
|
||||
id="path3784"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 -960 960 960"
|
||||
id="svg2989"
|
||||
>
|
||||
<path
|
||||
d="m 160,-120 c -22,0 -40.83333,-7.83333 -56.5,-23.5 C 87.833333,-159.16667 80,-178 80,-200 l 0,-480 c 0,-22 7.833333,-40.83333 23.5,-56.5 15.66667,-15.66667 34.5,-23.5 56.5,-23.5 l 126,0 74,-80 240,0 74,80 126,0 c 22,0 40.83333,7.83333 56.5,23.5 15.66667,15.66667 23.5,34.5 23.5,56.5 l 0,480 c 0,22 -7.83333,40.83333 -23.5,56.5 -15.66667,15.66667 -34.5,23.5 -56.5,23.5 z m 0,-80 640,0 0,-480 -162,0 -73,-80 -170,0 -73,80 -162,0 z"
|
||||
id="path2991" />
|
||||
<path
|
||||
d="m 478.26166,-438.41803 c -9.97085,1.5e-4 -18.49623,4.18716 -25.57617,12.56104 -7.08021,8.37415 -10.62024,19.2506 -10.62012,32.62939 -1.2e-4,13.74032 3.31531,24.70467 9.94629,32.89307 6.63072,8.18854 15.07798,12.28277 25.34179,12.28271 11.14242,6e-5 19.88508,-3.84759 26.22803,-11.54297 6.34259,-7.69523 9.51397,-18.75479 9.51416,-33.17871 -1.9e-4,-13.94519 -3.26923,-25.03893 -9.80713,-33.28125 -6.53826,-8.24204 -14.88053,-12.36313 -25.02685,-12.36328 z m 110.18554,-176.64551 -7.52929,72.09961 c -14.57056,-6.2595 -27.30492,-10.88841 -38.20313,-13.88672 -10.89864,-2.99777 -21.96309,-4.49679 -33.19336,-4.49707 -23.86735,2.8e-4 -41.80434,7.36355 -53.81103,22.08985 -12.00696,14.72679 -18.0885,35.71798 -18.24463,62.97363 2.90027,-10.0584 10.48816,-17.93437 22.76367,-23.62793 12.27524,-5.69314 26.02523,-8.53982 41.25,-8.54004 30.15604,2.2e-4 54.94606,10.35666 74.37012,31.06934 19.42355,20.71305 29.13546,47.00209 29.13574,78.86718 -2.8e-4,34.4532 -11.29666,62.6148 -33.88916,84.48487 -22.593,21.87012 -51.7751,32.80517 -87.54639,32.80517 -43.32043,0 -76.57235,-14.64843 -99.75586,-43.94531 -23.18363,-29.29681 -34.77541,-70.67372 -34.77539,-124.13086 -2e-5,-58.50564 13.35201,-103.32737 40.05616,-134.46533 26.704,-31.13737 65.54918,-46.7062 116.53564,-46.70654 12.91973,3.4e-4 26.01786,1.19174 39.29443,3.57421 13.27614,2.38315 27.79028,6.32846 43.54248,11.83594 z"
|
||||
id="path3886"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |