Memory hardening for AlsaDriver.
This commit is contained in:
+10
-1
@@ -20,6 +20,7 @@
|
||||
import React from 'react';
|
||||
import { ThemeProvider, createTheme, StyledEngineProvider, Theme } from '@mui/material/styles';
|
||||
|
||||
import VirtualKeyboardHandler from './VirtualKeyboardHandler';
|
||||
import AppThemed from "./AppThemed";
|
||||
import { isDarkMode } from './DarkMode';
|
||||
|
||||
@@ -152,6 +153,8 @@ const theme = createTheme(
|
||||
type AppThemeProps = {
|
||||
|
||||
};
|
||||
|
||||
|
||||
const App = (class extends React.Component {
|
||||
// Before the component mounts, we initialise our state
|
||||
|
||||
@@ -159,13 +162,19 @@ const App = (class extends React.Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
};
|
||||
if (!App.virtualKeyboardHandler)
|
||||
{
|
||||
App.virtualKeyboardHandler = new VirtualKeyboardHandler();
|
||||
}
|
||||
}
|
||||
|
||||
static virtualKeyboardHandler?: VirtualKeyboardHandler;
|
||||
|
||||
render() {
|
||||
return (
|
||||
<StyledEngineProvider injectFirst>
|
||||
<ThemeProvider theme={theme}>
|
||||
<AppThemed />
|
||||
<AppThemed />
|
||||
</ThemeProvider>
|
||||
</StyledEngineProvider>
|
||||
);
|
||||
|
||||
+22
-11
@@ -232,10 +232,11 @@ const appStyles = (theme: Theme) => createStyles({
|
||||
});
|
||||
|
||||
|
||||
|
||||
function supportsFullScreen(): boolean {
|
||||
let doc: any = window.document;
|
||||
let docEl: any = doc.documentElement;
|
||||
var requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullScreen || docEl.msRequestFullscreen;
|
||||
var requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullscreen || docEl.msRequestFullScreen;
|
||||
return (!!requestFullScreen);
|
||||
|
||||
}
|
||||
@@ -244,10 +245,20 @@ 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 (docEl.requestFullscren) // the latest offical api.
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
window.document.documentElement.requestFullscreen({navigationUI: "show"});
|
||||
} else {
|
||||
window.document.exitFullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
if (!doc.fullscreenElement && !doc.mozFullScreenElement && !doc.webkitFullscreenElement && !doc.msFullscreenElement) {
|
||||
var requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullscreen || docEl.msRequestFullScreen;
|
||||
var cancelFullScreen = docEl.exitFullscreen || doc.mozCancelFullScreen || doc.webkitExitFullscreen || doc.msExitFullscreen;
|
||||
|
||||
if (value) {
|
||||
requestFullScreen.call(docEl);
|
||||
}
|
||||
else {
|
||||
@@ -547,7 +558,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
||||
});
|
||||
}
|
||||
toggleFullScreen(): void {
|
||||
setFullScreen(this.state.isFullScreen);
|
||||
setFullScreen(!this.state.isFullScreen);
|
||||
this.setState({ isFullScreen: !this.state.isFullScreen });
|
||||
}
|
||||
|
||||
@@ -735,7 +746,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
||||
return (
|
||||
<div style={{
|
||||
colorScheme: isDarkMode() ? "dark" : "light", // affects scrollbar color
|
||||
minHeight: 345, minWidth: 390,
|
||||
minHeight: 300, minWidth: 300,
|
||||
position: "absolute", left:0, top: 0, right:0,bottom:0,
|
||||
overscrollBehavior: this.state.isDebug ? "auto" : "none"
|
||||
}}
|
||||
@@ -747,7 +758,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
||||
>
|
||||
<CssBaseline />
|
||||
{this.state.performanceView ? (
|
||||
<PerformanceView
|
||||
<PerformanceView open={this.state.performanceView}
|
||||
onClose={() => { this.setState({ performanceView: false }); }}
|
||||
/>
|
||||
) : (
|
||||
@@ -1028,8 +1039,8 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
||||
</DialogEx>
|
||||
|
||||
|
||||
<div className={classes.errorContent} style={{ display: this.state.displayState === State.Error ? "flex" : "none" }}
|
||||
onMouseDown={preventDefault} onKeyDown={preventDefault}
|
||||
<div className={classes.errorContent} style={{ zIndex: 1200, display: this.state.displayState === State.Error ? "flex" : "none" }}
|
||||
onMouseDown={preventDefault} onKeyDown={preventDefault}
|
||||
>
|
||||
<div className={classes.errorContentMask} />
|
||||
<div style={{ flex: "2 2 3px", height: 20 }} > </div>
|
||||
@@ -1056,7 +1067,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
||||
|
||||
</div>
|
||||
{/* initial load mask*/}
|
||||
<div className={classes.errorContent} style={{ display: this.state.displayState === State.Loading ? "block" : "none" }}>
|
||||
<div className={classes.errorContent} style={{ display: this.state.displayState === State.Loading ? "block" : "none",zIndex: 1201 }}>
|
||||
<div className={classes.errorContentMask} />
|
||||
<div className={classes.loadingBox}>
|
||||
<div className={classes.loadingBoxItem}>
|
||||
@@ -1069,7 +1080,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
||||
</div>
|
||||
{/* Reloading mask */}
|
||||
<div className={classes.errorContent} style={{
|
||||
zIndex: 501,
|
||||
zIndex: 1201,
|
||||
display: (
|
||||
wantsReloadingScreen(this.state.displayState)
|
||||
? "block" : "none"
|
||||
|
||||
@@ -7,14 +7,21 @@ import { useTheme } from '@mui/styles';
|
||||
|
||||
const colors: string[] = colorKeys;
|
||||
|
||||
|
||||
export enum DropdownAlignment {
|
||||
SE, SW
|
||||
}
|
||||
|
||||
interface ColorDropdownButtonProps {
|
||||
currentColor?: string;
|
||||
onColorChange: (color: string) => void;
|
||||
dropdownAlignment: DropdownAlignment;
|
||||
}
|
||||
|
||||
const ColorDropdownButton: React.FC<ColorDropdownButtonProps> = ({
|
||||
currentColor = colors[0],
|
||||
onColorChange,
|
||||
dropdownAlignment = DropdownAlignment.SE
|
||||
}) => {
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
const [selectedColor, setSelectedColor] = useState<string>(currentColor);
|
||||
@@ -38,7 +45,6 @@ const ColorDropdownButton: React.FC<ColorDropdownButtonProps> = ({
|
||||
setSelectedColor(color);
|
||||
onColorChange(color);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ButtonBase
|
||||
@@ -63,11 +69,11 @@ const ColorDropdownButton: React.FC<ColorDropdownButtonProps> = ({
|
||||
onClose={handleClose}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'right',
|
||||
horizontal: (dropdownAlignment === DropdownAlignment.SW ? 'right' : 'left')
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right',
|
||||
horizontal: (dropdownAlignment === DropdownAlignment.SW ? 'right' : 'left'),
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", flexFlow: "row wrap", width: 56 * 4 + 2 }}>
|
||||
|
||||
+42
-12
@@ -28,29 +28,34 @@ interface DialogExProps extends DialogProps {
|
||||
}
|
||||
|
||||
interface DialogExState {
|
||||
}
|
||||
|
||||
export interface IDialogStackable {
|
||||
getTag: ()=> string;
|
||||
isOpen: ()=> boolean;
|
||||
onDialogStackClose(): void;
|
||||
}
|
||||
|
||||
class DialogStackEntry {
|
||||
constructor(dialog: DialogEx)
|
||||
constructor(dialog: IDialogStackable)
|
||||
{
|
||||
this.tag = dialog.props.tag;
|
||||
this.tag = dialog.getTag();
|
||||
this.dialog = dialog;
|
||||
}
|
||||
tag: string;
|
||||
dialog: DialogEx;
|
||||
dialog: IDialogStackable;
|
||||
};
|
||||
|
||||
let dialogStack: DialogStackEntry[] = [];
|
||||
|
||||
|
||||
function popDialogStack(): void {
|
||||
export function popDialogStack(): void {
|
||||
if (dialogStack.length !== 0)
|
||||
{
|
||||
dialogStack.splice(dialogStack.length-1,1);
|
||||
}
|
||||
}
|
||||
function pushDialogStack(dialog: DialogEx): void {
|
||||
export function pushDialogStack(dialog: IDialogStackable): void {
|
||||
dialogStack.push(new DialogStackEntry(dialog));
|
||||
}
|
||||
|
||||
@@ -66,18 +71,19 @@ export function removeDialogStackEntriesAbove(tag: string)
|
||||
for (let i = dialogStack.length-1; i >= 0; --i)
|
||||
{
|
||||
let entry = dialogStack[i];
|
||||
if (entry.tag === tag)
|
||||
if (entry.dialog.getTag() === tag)
|
||||
{
|
||||
return;
|
||||
}
|
||||
popDialogStack();
|
||||
if (entry.dialog.props.open && entry.dialog.props.onClose)
|
||||
if (entry.dialog.isOpen())
|
||||
{
|
||||
entry.dialog.props.onClose({}, "backdropClick");
|
||||
entry.dialog.onDialogStackClose();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
class DialogEx extends React.Component<DialogExProps,DialogExState> {
|
||||
class DialogEx extends React.Component<DialogExProps,DialogExState> implements IDialogStackable {
|
||||
constructor(props: DialogExProps)
|
||||
{
|
||||
super(props);
|
||||
@@ -93,6 +99,16 @@ class DialogEx extends React.Component<DialogExProps,DialogExState> {
|
||||
hasHooks: boolean = false;
|
||||
stateWasPopped: boolean = false;
|
||||
|
||||
getTag() {
|
||||
return this.props.tag;
|
||||
}
|
||||
isOpen(): boolean {
|
||||
return this.props.open;
|
||||
}
|
||||
onDialogStackClose() {
|
||||
this.props.onClose?.({},"backdropClick");
|
||||
}
|
||||
|
||||
handlePopState(ev: PopStateEvent): any
|
||||
{
|
||||
let evTag: DialogStackState | null = ev.state as DialogStackState | null;
|
||||
@@ -169,11 +185,25 @@ class DialogEx extends React.Component<DialogExProps,DialogExState> {
|
||||
if (this.props.onClose) this.props.onClose(event,reason);
|
||||
}
|
||||
|
||||
onEnterKey() {
|
||||
|
||||
}
|
||||
onKeyDown(evt: React.KeyboardEvent<HTMLDivElement>)
|
||||
{
|
||||
if (evt.key === 'Enter')
|
||||
{
|
||||
this.onEnterKey();
|
||||
}
|
||||
}
|
||||
render() {
|
||||
let { tag,onClose, ...extra} = this.props;
|
||||
let { tag,onClose,...extra} = this.props;
|
||||
return (
|
||||
<Dialog fullWidth={this.props.fullWidth??false} maxWidth={this.props.fullWidth ? false: undefined} {...extra} onClose={(event,reason)=>{ this.myOnClose(event,reason);}}>
|
||||
{this.props.children}
|
||||
<Dialog fullWidth={this.props.fullWidth??false}
|
||||
maxWidth={this.props.fullWidth ? false: undefined} {...extra}
|
||||
onClose={(event,reason)=>{ this.myOnClose(event,reason);}}
|
||||
onKeyDown={(evt)=>{ this.onKeyDown(evt); }}
|
||||
>
|
||||
{this.props.children}
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ import MenuItem from '@mui/material/MenuItem';
|
||||
import Menu from '@mui/material/Menu';
|
||||
import MoreIcon from '@mui/icons-material/MoreVert';
|
||||
import { PiPedalModel, PiPedalModelFactory, FileEntry } from './PiPedalModel';
|
||||
import Link from '@mui/material/Link';
|
||||
import { isDarkMode } from './DarkMode';
|
||||
import Button from '@mui/material/Button';
|
||||
import FileUploadIcon from '@mui/icons-material/FileUpload';
|
||||
@@ -36,6 +35,7 @@ import FolderIcon from '@mui/icons-material/Folder';
|
||||
import InsertDriveFileOutlinedIcon from '@mui/icons-material/InsertDriveFileOutlined';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import OldDeleteIcon from './OldDeleteIcon';
|
||||
import Toolbar from '@mui/material/Toolbar';
|
||||
@@ -49,7 +49,6 @@ import Typography from '@mui/material/Typography';
|
||||
import DialogEx from './DialogEx';
|
||||
import UploadFileDialog from './UploadFileDialog';
|
||||
import OkCancelDialog from './OkCancelDialog';
|
||||
import Breadcrumbs from '@mui/material/Breadcrumbs';
|
||||
import HomeIcon from '@mui/icons-material/Home';
|
||||
import FilePropertyDirectorySelectDialog from './FilePropertyDirectorySelectDialog';
|
||||
|
||||
@@ -73,6 +72,7 @@ export interface FilePropertyDialogProps extends WithStyles<typeof styles> {
|
||||
export interface FilePropertyDialogState {
|
||||
fullScreen: boolean;
|
||||
selectedFile: string;
|
||||
selectedFileIsDirectory: boolean;
|
||||
navDirectory: string;
|
||||
hasSelection: boolean;
|
||||
canDelete: boolean;
|
||||
@@ -165,8 +165,9 @@ export default withStyles(styles, { withTheme: true })(
|
||||
this.model = PiPedalModelFactory.getInstance();
|
||||
|
||||
this.state = {
|
||||
fullScreen: false,
|
||||
fullScreen: this.getFullScreen(),
|
||||
selectedFile: props.selectedFile,
|
||||
selectedFileIsDirectory: false,
|
||||
navDirectory: this.getNavDirectoryFromFile(props.selectedFile, props.fileProperty),
|
||||
hasSelection: false,
|
||||
canDelete: false,
|
||||
@@ -182,6 +183,9 @@ export default withStyles(styles, { withTheme: true })(
|
||||
};
|
||||
this.requestScroll = true;
|
||||
}
|
||||
getFullScreen() {
|
||||
return window.innerWidth < 450 || window.innerHeight < 450;
|
||||
}
|
||||
|
||||
private scrollRef: HTMLDivElement | null = null;
|
||||
|
||||
@@ -244,7 +248,7 @@ export default withStyles(styles, { withTheme: true })(
|
||||
onWindowSizeChanged(width: number, height: number): void {
|
||||
|
||||
this.setState({
|
||||
fullScreen: height < 200
|
||||
fullScreen: this.getFullScreen()
|
||||
})
|
||||
if (this.lastDivRef !== null) {
|
||||
this.onMeasureRef(this.lastDivRef);
|
||||
@@ -302,9 +306,9 @@ export default withStyles(styles, { withTheme: true })(
|
||||
return hasSelection;
|
||||
}
|
||||
|
||||
onSelectValue(selectedFile: string) {
|
||||
onSelectValue(selectedFile: string, isDirectory: boolean) {
|
||||
this.requestScroll = true;
|
||||
this.setState({ selectedFile: selectedFile, hasSelection: this.isFileInList(this.state.fileEntries, selectedFile) })
|
||||
this.setState({ selectedFile: selectedFile, selectedFileIsDirectory: isDirectory, hasSelection: this.isFileInList(this.state.fileEntries, selectedFile) })
|
||||
}
|
||||
onDoubleClickValue(selectedFile: string) {
|
||||
this.openSelectedFile();
|
||||
@@ -364,17 +368,17 @@ export default withStyles(styles, { withTheme: true })(
|
||||
}
|
||||
renderBreadcrumbs() {
|
||||
if (this.state.navDirectory === "") {
|
||||
return (<div style={{ height: 0 }} />);
|
||||
return (<Divider />);
|
||||
}
|
||||
let breadcrumbs: React.ReactElement[] = [(
|
||||
<Link underline="hover"
|
||||
<Button variant="text"
|
||||
color="inherit"
|
||||
key="1" onClick={() => { this.navigate(""); }}
|
||||
sx={{ display: 'flex', alignItems: 'center',cursor: "default" }}
|
||||
key="h" onClick={() => { this.navigate(""); }}
|
||||
style={{ textTransform: "none", flexShrink: 0 }}
|
||||
startIcon={(<HomeIcon sx={{ mr: 0.6 }} fontSize="inherit" />)}
|
||||
>
|
||||
<HomeIcon sx={{ mr: 0.6 }} fontSize="inherit" />
|
||||
Home
|
||||
</Link>
|
||||
<Typography style={{}} variant="body2" noWrap>Home</Typography>
|
||||
</Button>
|
||||
)
|
||||
];
|
||||
let directories = this.state.navDirectory.split("/");
|
||||
@@ -383,25 +387,48 @@ export default withStyles(styles, { withTheme: true })(
|
||||
target = pathConcat(target, directories[i]);
|
||||
let myTarget = target;
|
||||
breadcrumbs.push((
|
||||
<Link underline="hover" key={(i + 1).toString()} style={{cursor: "default"}}
|
||||
color="inherit" onClick={() => { this.navigate(myTarget) }}>
|
||||
{directories[i]}
|
||||
</Link>
|
||||
<Typography key={"x"+(i)} variant="body2" style={{marginTop: 6, marginBottom: 6}} noWrap>/</Typography>
|
||||
|
||||
)
|
||||
);
|
||||
breadcrumbs.push((
|
||||
<Button variant="text"
|
||||
key={(i).toString()}
|
||||
color="inherit"
|
||||
onClick={() => { this.navigate(myTarget) }}
|
||||
style={{ minWidth: 0,textTransform: "none" , flexShrink: 0 }}
|
||||
>
|
||||
<Typography variant="body2" style={{}} noWrap>{directories[i]}</Typography>
|
||||
|
||||
|
||||
</Button>
|
||||
));
|
||||
}
|
||||
{
|
||||
let lastdirectory = directories[directories.length - 1];
|
||||
breadcrumbs.push((
|
||||
<Typography noWrap key={directories.length.toString()}
|
||||
style={{cursor: "default"}}
|
||||
<Typography key={"x"+(directories.length)} variant="body2" style={{userSelect: "none",marginTop: 6, marginBottom: 6}} noWrap>/</Typography>
|
||||
|
||||
)
|
||||
);
|
||||
breadcrumbs.push((
|
||||
<div style={{flexShrink: 0,paddingLeft: 8, paddingRight: 8, paddingTop: 6, paddingBottom: 6, overflowX:"clip"}}>
|
||||
<Typography key={directories.length.toString()} noWrap
|
||||
style={{}} variant="body2"
|
||||
color="text.secondary"> {lastdirectory}</Typography>
|
||||
</div>
|
||||
));
|
||||
|
||||
}
|
||||
return (
|
||||
<Breadcrumbs separator='/' aria-label="breadcrumb" style={{ marginLeft: 24 }}>
|
||||
{breadcrumbs}
|
||||
</Breadcrumbs>
|
||||
<div>
|
||||
|
||||
<div aria-label="breadcrumb"
|
||||
style={{ marginLeft: 16, marginBottom: 8, marginRight:8, textOverflow: "",display: "flex",flexFlow: "row wrap",alignItems: "center",justifyContent: "start" }}>
|
||||
{breadcrumbs}
|
||||
</div>
|
||||
<Divider/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -446,12 +473,30 @@ export default withStyles(styles, { withTheme: true })(
|
||||
render() {
|
||||
let classes = this.props.classes;
|
||||
let columnWidth = this.state.columnWidth;
|
||||
let okButtonText = "Select";
|
||||
if (this.state.hasSelection && this.state.selectedFileIsDirectory)
|
||||
{
|
||||
okButtonText = "Open";
|
||||
}
|
||||
return this.props.open &&
|
||||
(
|
||||
<DialogEx onClose={() => this.props.onCancel()} open={this.props.open} tag="fileProperty" fullWidth maxWidth="xl" style={{ height: "90%" }}
|
||||
PaperProps={{ style: { minHeight: "90%", maxHeight: "90%", overflowY: "visible" } }}
|
||||
<DialogEx
|
||||
fullScreen={this.state.fullScreen}
|
||||
onClose={() => {
|
||||
this.props.onCancel();
|
||||
}}
|
||||
open={this.props.open} tag="fileProperty"
|
||||
fullWidth maxWidth="xl"
|
||||
PaperProps={
|
||||
this.state.fullScreen ?
|
||||
{}
|
||||
:
|
||||
{ style: {
|
||||
minHeight: "90%",
|
||||
maxHeight: "90%",
|
||||
} }}
|
||||
>
|
||||
<DialogTitle >
|
||||
<DialogTitle style={{paddingBottom: 0}} >
|
||||
<Toolbar style={{ padding: 0 }}>
|
||||
<IconButton
|
||||
edge="start"
|
||||
@@ -507,17 +552,17 @@ export default withStyles(styles, { withTheme: true })(
|
||||
{this.hasSelectedFileOrFolder() && (<MenuItem onClick={() => { this.handleMenuClose();this.onRename(); }}>Rename</MenuItem>)}
|
||||
</Menu>
|
||||
</Toolbar>
|
||||
<div style={{ flex: "0 0 auto", marginLeft: "auto", marginRight: "auto" }}>
|
||||
<div style={{ flex: "0 0 auto", marginLeft: 0, marginRight: 0, overflowX: "clip" }}>
|
||||
{this.renderBreadcrumbs()}
|
||||
</div>
|
||||
</DialogTitle>
|
||||
<div style={{ flex: "0 0 auto", height: "1px", background: isDarkMode() ? "rgba(255,255,255,0.1" : "rgba(0,0,0,0.2" }}> </div>
|
||||
<div style={{ flex: "1 1 auto", height: "300", display: "flex", flexFlow: "row wrap", overflowX: "hidden", overflowY: "auto" }}>
|
||||
<DialogContent style={{paddingLeft: 0, paddingRight: 0, paddingTop: 0,colorScheme: (isDarkMode() ? "dark" : "light")}}>
|
||||
|
||||
<div
|
||||
ref={(element) => this.onMeasureRef(element)}
|
||||
style={{
|
||||
flex: "1 1 100%", display: "flex", flexFlow: "row wrap",
|
||||
justifyContent: "flex-start", alignContent: "flex-start", paddingLeft: 16, paddingTop: 16, paddingBottom: 16
|
||||
justifyContent: "flex-start", alignContent: "flex-start", paddingLeft: 16, paddingBottom: 16
|
||||
}}>
|
||||
{
|
||||
(this.state.columns !== 0) && // don't render until we have number of columns derived from layout.
|
||||
@@ -549,7 +594,7 @@ export default withStyles(styles, { withTheme: true })(
|
||||
<ButtonBase key={value.filename}
|
||||
|
||||
style={{ width: columnWidth, flex: "0 0 auto", height: 48, position: "relative" }}
|
||||
onClick={() => this.onSelectValue(value.filename)} onDoubleClick={() => { this.onDoubleClickValue(value.filename); }}
|
||||
onClick={() => this.onSelectValue(value.filename,value.isDirectory)} onDoubleClick={() => { this.onDoubleClickValue(value.filename); }}
|
||||
>
|
||||
<div ref={scrollRef} style={{ position: "absolute", background: selectBg, width: "100%", height: "100%", borderRadius: 4 }} />
|
||||
<div style={{ display: "flex", flexFlow: "row nowrap", textOverflow: "ellipsis", justifyContent: "start", alignItems: "center", width: "100%", height: "100%" }}>
|
||||
@@ -569,8 +614,8 @@ export default withStyles(styles, { withTheme: true })(
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ flex: "0 0 auto", height: "1px", background: "rgba(0,0,0,0.2" }}> </div>
|
||||
</DialogContent>
|
||||
<Divider/>
|
||||
<DialogActions style={{ justifyContent: "stretch" }}>
|
||||
<div style={{ display: "flex", width: "100%", alignItems: "center", flexFlow: "row nowrap" }}>
|
||||
<IconButton style={{ visibility: (this.state.hasSelection ? "visible" : "hidden") }} aria-label="delete" component="label" color="primary"
|
||||
@@ -594,7 +639,7 @@ export default withStyles(styles, { withTheme: true })(
|
||||
|
||||
disabled={(!this.state.hasSelection) && this.state.selectedFile !== ""} aria-label="select"
|
||||
>
|
||||
Select
|
||||
{okButtonText}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogActions>
|
||||
|
||||
@@ -200,7 +200,7 @@ const FullScreenIME =
|
||||
<div style={{
|
||||
width: "100%", height: "100%", position: "relative",
|
||||
display: "flex", flexDirection: "column", flexWrap: "nowrap",
|
||||
justifyContent: "center", alignItems: "center"
|
||||
justifyContent: "start", alignItems: "center", marginTop:12
|
||||
}}
|
||||
>
|
||||
<Typography noWrap variant="body2">{this.props.caption}</Typography>
|
||||
|
||||
@@ -519,8 +519,8 @@ export const MainPage =
|
||||
display: "flex", flexFlow: "row nowrap", alignItems: "center", justifyContent: "center", minWidth: 0,
|
||||
width: "100%", height: 48, paddingLeft: 16, paddingRight: 16
|
||||
}} >
|
||||
<div style={{ flex: "0 0 auto", width: 80 }} >
|
||||
<div style={{ display: bypassVisible ? "block" : "none", width: 80 }} >
|
||||
<div style={{ flex: "0 0 auto", width: this.state.splitControlBar? undefined: 80 }} >
|
||||
<div style={{ display: bypassVisible ? "block" : "none", width: this.state.splitControlBar? undefined: 80 }} >
|
||||
<Switch color="secondary" checked={bypassChecked} onChange={this.handleEnableCurrentItemChanged} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -348,9 +348,9 @@ export function getBorderColor(colorKey: string): string {
|
||||
} else {
|
||||
if (colorKey === "grey")
|
||||
{
|
||||
return list[300];
|
||||
return list[500];
|
||||
}
|
||||
return list[200];
|
||||
return list[400];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -228,7 +228,7 @@ export class 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.
|
||||
result.pathProperties = {...item.pathProperties}; // clone the dictionary.
|
||||
return result;
|
||||
}
|
||||
instanceId: number = -1;
|
||||
|
||||
+109
-10
@@ -28,14 +28,18 @@ 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 ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
|
||||
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
|
||||
//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';
|
||||
import JackStatusView from './JackStatusView';
|
||||
import {IDialogStackable, DialogStackState, removeDialogStackEntriesAbove, popDialogStack, pushDialogStack} from './DialogEx';
|
||||
|
||||
|
||||
const selectColor = isDarkMode() ? "#888" : "#FFFFFF";
|
||||
@@ -63,6 +67,7 @@ const styles = (theme: Theme) => createStyles({
|
||||
|
||||
|
||||
interface PerformanceViewProps extends WithStyles<typeof styles> {
|
||||
open: boolean,
|
||||
onClose: () => void,
|
||||
theme: Theme
|
||||
}
|
||||
@@ -78,7 +83,7 @@ interface PerformanceViewState {
|
||||
|
||||
export const PerformanceView =
|
||||
withStyles(styles, { withTheme: true })(
|
||||
class extends ResizeResponsiveComponent<PerformanceViewProps, PerformanceViewState> {
|
||||
class extends ResizeResponsiveComponent<PerformanceViewProps, PerformanceViewState> implements IDialogStackable {
|
||||
model: PiPedalModel;
|
||||
|
||||
constructor(props: PerformanceViewProps) {
|
||||
@@ -96,7 +101,76 @@ export const PerformanceView =
|
||||
};
|
||||
this.onPresetsChanged = this.onPresetsChanged.bind(this);
|
||||
this.onBanksChanged = this.onBanksChanged.bind(this);
|
||||
this.handlePopState = this.handlePopState.bind(this);
|
||||
|
||||
}
|
||||
|
||||
getTag() { return "performView"}
|
||||
isOpen() { return this.props.open }
|
||||
onDialogStackClose() {
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
private stateWasPopped: boolean = false;
|
||||
|
||||
|
||||
private hasHooks: boolean = false;
|
||||
|
||||
updateHooks() : void {
|
||||
let wantHooks = this.mounted && this.props.open;
|
||||
|
||||
if (wantHooks !== this.hasHooks)
|
||||
{
|
||||
this.hasHooks = wantHooks;
|
||||
|
||||
if (this.hasHooks)
|
||||
{
|
||||
this.stateWasPopped = false;
|
||||
window.addEventListener("popstate",this.handlePopState);
|
||||
|
||||
pushDialogStack(this);
|
||||
let dialogStackState: DialogStackState = {tag: this.getTag(),previousState: window.history.state as DialogStackState | null};
|
||||
window.history.replaceState(dialogStackState,"",window.location.href);
|
||||
window.history.pushState(
|
||||
null,
|
||||
"",
|
||||
"#" + this.getTag()
|
||||
);
|
||||
} else {
|
||||
if (!this.stateWasPopped)
|
||||
{
|
||||
window.history.back();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handlePopState(ev: PopStateEvent): any
|
||||
{
|
||||
let evTag: DialogStackState | null = ev.state as DialogStackState | null;
|
||||
if (evTag && evTag.tag === this.getTag()) {
|
||||
removeDialogStackEntriesAbove(this.getTag());
|
||||
|
||||
if (!this.stateWasPopped)
|
||||
{
|
||||
this.stateWasPopped = true;
|
||||
popDialogStack ();
|
||||
if (this.props.open) {
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
window.history.replaceState(evTag.previousState,"",window.location.href);
|
||||
window.removeEventListener("popstate",this.handlePopState);
|
||||
|
||||
if (window.location.hash === "#menu") // The menu popstate is next?
|
||||
{
|
||||
window.history.back(); // then clear off the menu popstate too.
|
||||
}
|
||||
ev.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onWindowSizeChanged(width: number, height: number): void {
|
||||
this.setState({ wrapSelects: width < 700 });
|
||||
}
|
||||
@@ -107,19 +181,34 @@ export const PerformanceView =
|
||||
onBanksChanged(newValue: BankIndex) {
|
||||
this.setState({ banks: this.model.banks.get() })
|
||||
}
|
||||
|
||||
private mounted: boolean = false;
|
||||
componentDidMount(): void {
|
||||
super.componentDidMount();
|
||||
this.mounted = true;
|
||||
this.model.presets.addOnChangedHandler(this.onPresetsChanged);
|
||||
this.model.banks.addOnChangedHandler(this.onBanksChanged);
|
||||
this.setState({
|
||||
presets: this.model.presets.get(),
|
||||
banks: this.model.banks.get()
|
||||
});
|
||||
|
||||
this.updateHooks();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
componentWillUnmount(): void {
|
||||
|
||||
this.model.presets.removeOnChangedHandler(this.onPresetsChanged);
|
||||
this.model.banks.removeOnChangedHandler(this.onBanksChanged);
|
||||
|
||||
this.mounted = false;
|
||||
this.updateHooks();
|
||||
|
||||
super.componentWillUnmount();
|
||||
|
||||
}
|
||||
handlePresetSelectClose(event: any): void {
|
||||
let value = event.currentTarget.getAttribute("data-value");
|
||||
@@ -178,7 +267,7 @@ export const PerformanceView =
|
||||
<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,
|
||||
position: "static", flex: "0 0 auto", height: wrapSelects ? 108 : 58,
|
||||
display: this.state.showSnapshotEditor ? "none" : undefined
|
||||
}}
|
||||
>
|
||||
@@ -188,7 +277,10 @@ export const PerformanceView =
|
||||
alignItems: "start"
|
||||
}}>
|
||||
<IconButton aria-label="menu" color="inherit"
|
||||
onClick={() => { this.props.onClose(); }} style={{ flex: "0 0 auto" }} >
|
||||
onClick={() => { this.props.onClose(); }}
|
||||
style={{
|
||||
position: "relative",top: 3,
|
||||
flex: "0 0 auto" }} >
|
||||
<ArrowBackIcon />
|
||||
</IconButton>
|
||||
|
||||
@@ -206,12 +298,13 @@ export const PerformanceView =
|
||||
<IconButton
|
||||
aria-label="previous-bank"
|
||||
onClick={() => { this.handlePreviousBank(); }}
|
||||
size="medium"
|
||||
color="inherit"
|
||||
style={{
|
||||
borderTopRightRadius: "3px", borderBottomRightRadius: "3px", marginRight: 4
|
||||
}}
|
||||
>
|
||||
<ArrowLeftOutlined style={{ opacity: 0.75 }} />
|
||||
<ArrowBackIosIcon style={{ opacity: 0.75 }} />
|
||||
</IconButton>
|
||||
|
||||
<Select variant="standard"
|
||||
@@ -239,10 +332,11 @@ export const PerformanceView =
|
||||
<IconButton
|
||||
aria-label="next-bank"
|
||||
onClick={() => { this.handleNextBank(); }}
|
||||
size="large"
|
||||
color="inherit"
|
||||
style={{ borderTopLeftRadius: "3px", borderBottomLeftRadius: "3px", marginLeft: 4 }}
|
||||
>
|
||||
<ArrowRightOutlined style={{ opacity: 0.75 }} />
|
||||
<ArrowForwardIosIcon style={{ opacity: 0.75 }} />
|
||||
</IconButton>
|
||||
|
||||
</div>
|
||||
@@ -254,7 +348,7 @@ export const PerformanceView =
|
||||
color="inherit"
|
||||
style={{ borderTopRightRadius: "3px", borderBottomRightRadius: "3px", marginRight: 4 }}
|
||||
>
|
||||
<ArrowLeftOutlined style={{ opacity: 0.75 }} />
|
||||
<ArrowBackIosIcon style={{ opacity: 0.75 }} />
|
||||
</IconButton>
|
||||
<Select variant="standard"
|
||||
className={classes.select}
|
||||
@@ -288,16 +382,21 @@ export const PerformanceView =
|
||||
color="inherit"
|
||||
style={{ borderTopLeftRadius: "3px", borderBottomLeftRadius: "3px", marginLeft: 4 }}
|
||||
>
|
||||
<ArrowRightOutlined style={{ opacity: 0.75 }} />
|
||||
<ArrowForwardIosIcon style={{ opacity: 0.75 }} />
|
||||
</IconButton>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AppBar >
|
||||
<div style={{ flex: "1 0 auto", display: "flex", marginTop: 16 }}>
|
||||
<div style={{ flex: "1 0 auto", display: "flex", marginTop: 16,marginBottom: 20 }}>
|
||||
<SnapshotPanel onEdit={(index) => { return this.handleOnEdit(index); }} />
|
||||
</div>
|
||||
|
||||
|
||||
{!this.state.showSnapshotEditor && (
|
||||
<JackStatusView />)
|
||||
}
|
||||
</div>
|
||||
{this.state.showSnapshotEditor && (
|
||||
<SnapshotEditor snapshotIndex={this.state.snapshotEditorIndex}
|
||||
|
||||
@@ -653,7 +653,9 @@ const PluginControlView =
|
||||
<FilePropertyDialog open={this.state.showFileDialog}
|
||||
fileProperty={this.state.dialogFileProperty}
|
||||
selectedFile={this.state.dialogFileValue}
|
||||
onCancel={() => { this.setState({ showFileDialog: false }); }}
|
||||
onCancel={() => {
|
||||
this.setState({ showFileDialog: false });
|
||||
}}
|
||||
onOk={(fileProperty, selectedFile) => {
|
||||
|
||||
this.model.setPatchProperty(
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
|
||||
|
||||
export default class Rectangle {
|
||||
top: number;
|
||||
left: number;
|
||||
width: number;
|
||||
height: number;
|
||||
|
||||
constructor(top?: number, left?: number, width?: number, height?: number) {
|
||||
this.top = top ? top: 0;
|
||||
this.left = left ? left: 0;
|
||||
this.width = width ? width: 0;
|
||||
this.height = height ? height: 0;
|
||||
}
|
||||
|
||||
get right(): number {
|
||||
return this.left + this.width;
|
||||
}
|
||||
|
||||
get bottom(): number {
|
||||
return this.top + this.height;
|
||||
}
|
||||
|
||||
set right(value: number) {
|
||||
this.width = value - this.left;
|
||||
}
|
||||
|
||||
set bottom(value: number) {
|
||||
this.height = value - this.top;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -19,13 +19,14 @@
|
||||
|
||||
import React from 'react';
|
||||
import Button from '@mui/material/Button';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import DialogEx from './DialogEx';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import { nullCast } from './Utility';
|
||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||
import {PiPedalModel,PiPedalModelFactory} from './PiPedalModel';
|
||||
//import TextFieldEx from './TextFieldEx';
|
||||
import TextField from '@mui/material/TextField';
|
||||
|
||||
|
||||
export interface RenameDialogProps {
|
||||
@@ -113,15 +114,19 @@ export default class RenameDialog extends ResizeResponsiveComponent<RenameDialog
|
||||
}
|
||||
};
|
||||
return (
|
||||
<DialogEx tag="nameDialog" open={open} fullWidth onClose={handleClose} aria-labelledby="Rename-dialog-title"
|
||||
<DialogEx tag="nameDialog" open={open} fullWidth maxWidth="sm" onClose={handleClose} aria-labelledby="Rename-dialog-title"
|
||||
fullScreen={this.state.fullScreen}
|
||||
style={{userSelect: "none"}}
|
||||
>
|
||||
<DialogContent>
|
||||
<DialogContent style={{minHeight: 96}}>
|
||||
<TextField
|
||||
autoFocus
|
||||
|
||||
onKeyDown={handleKeyDown}
|
||||
margin="dense"
|
||||
variant="outlined"
|
||||
inputProps={{
|
||||
scrollMargin: 24,
|
||||
}}
|
||||
id="name"
|
||||
label="Name"
|
||||
type="text"
|
||||
@@ -130,7 +135,7 @@ export default class RenameDialog extends ResizeResponsiveComponent<RenameDialog
|
||||
inputRef={this.refText}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<DialogActions style={{flexShrink: 1}}>
|
||||
<Button onClick={handleClose} variant="dialogSecondary" >
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
@@ -38,7 +38,8 @@ import { isDarkMode } from './DarkMode';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import { Snapshot } from './Pedalboard';
|
||||
import ColorDropdownButton from './ColorDropdownButton';
|
||||
import ColorDropdownButton, { DropdownAlignment } from './ColorDropdownButton';
|
||||
import { getBackgroundColor } from './MaterialColors';
|
||||
|
||||
|
||||
const selectColor = isDarkMode() ? "#888" : "#FFFFFF";
|
||||
@@ -252,7 +253,7 @@ const SnapshotEditor = withStyles(appStyles)(class extends ResizeResponsiveCompo
|
||||
}}
|
||||
>
|
||||
<CssBaseline />
|
||||
<AppBar position="static" sx={{ bgcolor: isDarkMode() ? "#100020" : "#200040" }} >
|
||||
<AppBar position="static" sx={{ bgcolor: isDarkMode() ? getBackgroundColor("purple") : "#200040" }} >
|
||||
<Toolbar variant="dense" >
|
||||
<IconButton
|
||||
edge="start"
|
||||
@@ -288,6 +289,7 @@ const SnapshotEditor = withStyles(appStyles)(class extends ResizeResponsiveCompo
|
||||
</div>
|
||||
<ColorDropdownButton
|
||||
currentColor={this.state.color}
|
||||
dropdownAlignment={DropdownAlignment.SW}
|
||||
onColorChange={(newColor) => {
|
||||
this.setState({ color: newColor });
|
||||
}} />
|
||||
@@ -335,7 +337,7 @@ const SnapshotEditor = withStyles(appStyles)(class extends ResizeResponsiveCompo
|
||||
onDialogClosed={() => { this.model_.zoomedUiControl.set(undefined); }
|
||||
}
|
||||
/>
|
||||
{this.state.showStatusMonitor && (<JackStatusView />)}
|
||||
{ this.state.showStatusMonitor && (<JackStatusView />)}
|
||||
</div >
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
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';
|
||||
@@ -33,8 +32,9 @@ 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 { colorKeys } from "./MaterialColors";
|
||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||
import ColorDropdownButton, { DropdownAlignment } from './ColorDropdownButton';
|
||||
|
||||
const snapshotColors = colorKeys.slice(0,100);
|
||||
const NBSP = "\u00A0";
|
||||
@@ -111,7 +111,7 @@ export default class SnapshotPropertiesDialog extends ResizeResponsiveComponent<
|
||||
|
||||
let okButtonText = this.props.editing ? "OK" : "Save";
|
||||
return (
|
||||
<DialogEx maxWidth={false} tag="snapshotProps" open={this.props.open} onClose={handleClose}
|
||||
<DialogEx maxWidth="sm" fullWidth={true} tag="snapshotProps" open={this.props.open} onClose={handleClose}
|
||||
style={{ userSelect: "none" }} fullScreen={this.state.compactVertical}
|
||||
>
|
||||
<DialogTitle>
|
||||
@@ -126,7 +126,7 @@ export default class SnapshotPropertiesDialog extends ResizeResponsiveComponent<
|
||||
</div>
|
||||
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContent style={{paddingBottom: 0}}>
|
||||
|
||||
<TextField variant="standard" style={{ flexGrow: 1, flexBasis: 1 }}
|
||||
autoComplete="off"
|
||||
@@ -144,40 +144,11 @@ export default class SnapshotPropertiesDialog extends ResizeResponsiveComponent<
|
||||
}}
|
||||
/>
|
||||
<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>
|
||||
<ColorDropdownButton aria-label="color" currentColor={this.state.color} dropdownAlignment={DropdownAlignment.SE}
|
||||
onColorChange={(newcolor) => {
|
||||
this.setState({color: newcolor});
|
||||
}} />
|
||||
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button variant="dialogSecondary" onClick={handleClose} >
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import TextField, { TextFieldProps } from '@mui/material/TextField';
|
||||
import { withStyles, WithStyles } from '@mui/styles';
|
||||
import createStyles from '@mui/styles/createStyles';
|
||||
import { Theme } from '@mui/material/styles';
|
||||
import Modal from '@mui/material/Modal';
|
||||
|
||||
const styles = (theme: Theme) => createStyles({
|
||||
});
|
||||
|
||||
|
||||
export interface TextFieldExPropsBase extends WithStyles<typeof styles> {
|
||||
label?: string;
|
||||
containerStyle?: React.CSSProperties;
|
||||
autoFocus?: boolean;
|
||||
inputRef?: React.Ref<any>;
|
||||
theme: Theme;
|
||||
};
|
||||
|
||||
export type TextFieldExProps = TextFieldExPropsBase & TextFieldProps;
|
||||
|
||||
|
||||
export interface TextFieldExState {
|
||||
androidHosted: boolean;
|
||||
composingText: string;
|
||||
displayText: string;
|
||||
showIme: boolean;
|
||||
imeValue: string;
|
||||
};
|
||||
|
||||
|
||||
const TextFieldEx = withStyles(styles, { withTheme: true })(
|
||||
class extends React.Component<TextFieldExProps, TextFieldExState> {
|
||||
constructor(props: TextFieldExProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
composingText: "",
|
||||
displayText: "",
|
||||
androidHosted: true,
|
||||
showIme: false,
|
||||
imeValue: ""
|
||||
};
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
this.handleImeCompositionStart = this.handleImeCompositionStart.bind(this);
|
||||
this.handleCompositionEnd = this.handleCompositionEnd.bind(this);
|
||||
this.handleImeCompositionUpdate = this.handleImeCompositionUpdate.bind(this);
|
||||
this.handleImeCompositionEnd = this.handleImeCompositionEnd.bind(this);
|
||||
}
|
||||
|
||||
private getUseImeMask() {
|
||||
return this.state.androidHosted && window.innerHeight < 4500;
|
||||
}
|
||||
|
||||
private originalInputHref: HTMLInputElement | undefined = undefined;
|
||||
private imeInputHref: HTMLInputElement | undefined = undefined;
|
||||
|
||||
handleInputRef(input: HTMLInputElement) {
|
||||
if (input) {
|
||||
this.originalInputHref = input;
|
||||
if (this.getUseImeMask()) {
|
||||
input.addEventListener('click', this.handleClick);
|
||||
input.addEventListener('input', this.handleInput);
|
||||
}
|
||||
} else {
|
||||
if (this.originalInputHref) {
|
||||
this.originalInputHref.removeEventListener('click', this.handleClick);
|
||||
this.originalInputHref.removeEventListener('input', this.handleInput);
|
||||
this.originalInputHref = undefined;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleClick(event: FocusEvent) {
|
||||
(event.target as any).blur();
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.setState({
|
||||
imeValue: (event.target as any).value as string,
|
||||
showIme: true
|
||||
});
|
||||
return false;
|
||||
}
|
||||
handleImeCompositionStart(event: any) {
|
||||
// this.setState({
|
||||
// composingText: '' ,
|
||||
// imeValue: '', //event.target.value as string,
|
||||
// showIme: true
|
||||
// });
|
||||
}
|
||||
|
||||
handleImeCompositionUpdate(event: CompositionEvent) {
|
||||
this.setState({ composingText: event.data });
|
||||
};
|
||||
|
||||
handleCompositionEnd = (event: CompositionEvent) => {
|
||||
this.setState(prevState => ({
|
||||
displayText: prevState.displayText + event.data,
|
||||
composingText: '',
|
||||
}));
|
||||
};
|
||||
|
||||
dismissIme() {
|
||||
if (this.originalInputHref && this.imeInputHref)
|
||||
{
|
||||
let value = this.imeInputHref.value;
|
||||
this.originalInputHref.value = this.imeInputHref.value;
|
||||
this.originalInputHref.blur();
|
||||
this.imeInputHref.blur();
|
||||
this.setState({
|
||||
showIme: false, imeValue: value
|
||||
});
|
||||
}
|
||||
}
|
||||
handleImeCompositionEnd()
|
||||
{
|
||||
this.dismissIme();
|
||||
}
|
||||
handleInput = (event: Event) => {
|
||||
const target = event.target as HTMLInputElement;
|
||||
if (!this.state.composingText) {
|
||||
this.setState({ displayText: target.value });
|
||||
}
|
||||
};
|
||||
render() {
|
||||
let { autoFocus, containerStyle, label, ...extra } = this.props;
|
||||
return (
|
||||
<div style={this.props.containerStyle}>
|
||||
<TextField {...extra} autoFocus={autoFocus} tabIndex={-1} inputRef={(input) => this.handleInputRef(input)} />
|
||||
<p>Display Text: {this.state.imeValue}</p>
|
||||
<p>Composing Text: {this.state.composingText}</p>
|
||||
{this.state.showIme && (
|
||||
<Modal open={this.state.showIme}>
|
||||
<div style={{
|
||||
position: "absolute", top: 0, left: 0, right: 0, bottom: 0,
|
||||
background: this.props.theme.palette.background.default,
|
||||
display: "flex", flexFlow: "row nowrap",justifyContent: "center"
|
||||
|
||||
}}>
|
||||
<TextField variant="standard"
|
||||
inputRef={(element: HTMLInputElement)=>{
|
||||
if (this.imeInputHref) {
|
||||
this.imeInputHref.removeEventListener("compositionend",this.handleImeCompositionEnd);
|
||||
this.imeInputHref.removeEventListener("compositionupdate",this.handleImeCompositionUpdate);
|
||||
this.imeInputHref.removeEventListener("compositionstart",this.handleImeCompositionStart);
|
||||
}
|
||||
this.imeInputHref = element;
|
||||
if (this.imeInputHref)
|
||||
{
|
||||
this.imeInputHref.addEventListener("compositionend",this.handleImeCompositionEnd);
|
||||
this.imeInputHref.addEventListener("compositionupdate",this.handleImeCompositionUpdate);
|
||||
this.imeInputHref.addEventListener("compositionstart",this.handleImeCompositionStart);
|
||||
|
||||
}
|
||||
}}
|
||||
style={{width: 200,marginTop: 16}}
|
||||
autoFocus
|
||||
value={this.state.imeValue}
|
||||
onChange={(event: any) => {
|
||||
this.setState({ imeValue: event.target.value as string });
|
||||
}}
|
||||
onBlur={() => {
|
||||
this.dismissIme();
|
||||
}}
|
||||
onKeyDown={(event)=> {
|
||||
if (event.key === "Enter")
|
||||
{
|
||||
this.setState({showIme: false});
|
||||
}
|
||||
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default TextFieldEx;
|
||||
@@ -230,16 +230,15 @@ const ToobFrequencyResponseView =
|
||||
currentPath: string = "";
|
||||
|
||||
majorGridLine(x0: number, y0: number, x1: number, y1: number): React.ReactNode {
|
||||
return (<line x1={x0} y1={y0} x2={x1} y2={y1} fill='none' stroke="#FFF" strokeWidth="0.75" opacity="1" />);
|
||||
return (<line key={"l" + this.nextKey++} x1={x0} y1={y0} x2={x1} y2={y1} fill='none' stroke="#FFF" strokeWidth="0.75" opacity="1" />);
|
||||
}
|
||||
|
||||
gridLine(x0: number, y0: number, x1: number, y1: number): React.ReactNode {
|
||||
return (<line x1={x0} y1={y0} x2={x1} y2={y1} fill='none' stroke="#FFF" strokeWidth="0.25" opacity="1" />);
|
||||
return (<line key={"l" + this.nextKey++} x1={x0} y1={y0} x2={x1} y2={y1} fill='none' stroke="#FFF" strokeWidth="0.25" opacity="1" />);
|
||||
}
|
||||
grid(): React.ReactNode[] {
|
||||
let result: React.ReactNode[] = [];
|
||||
|
||||
|
||||
for (var db = Math.ceil(this.dbMax / this.dbTickSpacing) * this.dbTickSpacing; db < this.dbMin; db += this.dbTickSpacing) {
|
||||
var y = (db - this.dbMin) / (this.dbMax - this.dbMin) * (this.yMax - this.yMin);
|
||||
if (db === 0) {
|
||||
@@ -274,8 +273,10 @@ const ToobFrequencyResponseView =
|
||||
dbMin: number = 5;
|
||||
dbMax: number = -35;
|
||||
|
||||
private nextKey: number = 0;
|
||||
render() {
|
||||
// deliberately reversed to flip up and down.
|
||||
this.nextKey = 0;
|
||||
this.dbMax = this.state.minDb;
|
||||
this.dbMin = this.state.maxDb;
|
||||
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
// 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.
|
||||
|
||||
|
||||
|
||||
// converts android handling of the virtual keyboard to use pan instead of zoom.
|
||||
|
||||
import Rectangle from './Rectangle';
|
||||
|
||||
|
||||
export default class VirtualKeyboardHandler
|
||||
{
|
||||
|
||||
enabled: boolean = false;
|
||||
originalPosition: Rectangle
|
||||
|
||||
|
||||
constructor()
|
||||
{
|
||||
|
||||
let enabled = this.isChromeMobile();
|
||||
|
||||
if ("virtualKeyboard" in navigator) {
|
||||
this.virtualKeyboard = navigator["virtualKeyboard"];
|
||||
if (this.virtualKeyboard)
|
||||
{
|
||||
alert("Has Virtual keyboard!");
|
||||
this.virtualKeyboard.addEventListener("geometryChange",this.handleGeometryChange);
|
||||
}
|
||||
}
|
||||
|
||||
if ('visualViewport' in window) {
|
||||
window.visualViewport?.addEventListener('resize', this.handleVisualViewportResize.bind(this));
|
||||
} else {
|
||||
enabled = false;
|
||||
}
|
||||
this.enabled = enabled;
|
||||
this.originalPosition = new Rectangle(0,0, window.innerWidth,window.innerHeight)
|
||||
}
|
||||
private handleVisualViewportResize(event: Event): void {
|
||||
const viewport = event.target as VisualViewport;
|
||||
if (viewport.width !== this.originalPosition.width)
|
||||
{
|
||||
// a flip, not a virtual keyboard event.
|
||||
this.originalPosition = new Rectangle(0,0,viewport.width,viewport.height);
|
||||
this.resetContentPosition();
|
||||
}
|
||||
const heightDifference = this.originalPosition.height - viewport.height;
|
||||
if (Math.abs(heightDifference) < 150)
|
||||
{
|
||||
// fullscren !fullscreen
|
||||
this.originalPosition = new Rectangle(0,0,viewport.width,viewport.height);
|
||||
this.resetContentPosition();
|
||||
|
||||
}
|
||||
|
||||
let viewportRect = new Rectangle(viewport.offsetLeft,viewport.offsetTop,viewport.width,viewport.height);
|
||||
if (heightDifference > 0) {
|
||||
// Keyboard is likely visible
|
||||
this.panContent(viewportRect);
|
||||
} else {
|
||||
// Keyboard is likely hidden
|
||||
this.resetContentPosition();
|
||||
}
|
||||
}
|
||||
|
||||
panContent(viewportRect: Rectangle)
|
||||
{
|
||||
let focusObject = this.getFocusedInputElement();
|
||||
if (focusObject) {
|
||||
let clientRect_ = focusObject.getBoundingClientRect();
|
||||
let clientRect = new Rectangle(clientRect_.left,clientRect_.top,clientRect_.width,clientRect_.height);
|
||||
|
||||
// center the client in the available height.
|
||||
let yOffset = viewportRect.top + viewportRect.height/2 - (clientRect.top + clientRect.height/2);
|
||||
|
||||
if (yOffset < 0) {
|
||||
yOffset = 0;
|
||||
}
|
||||
let transform = `translateY(-${yOffset}px)`;
|
||||
document.body.style.transform = transform;
|
||||
document.body.style.height = `${this.originalPosition.height}px`;
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
}
|
||||
resetContentPosition() {
|
||||
document.body.style.transform = "";
|
||||
document.body.style.height = "";
|
||||
document.body.style.overflow = "hidden";
|
||||
}
|
||||
|
||||
getFocusedInputElement(): HTMLInputElement | null {
|
||||
const element = document.activeElement;
|
||||
return element instanceof HTMLInputElement ? element : null;
|
||||
}
|
||||
|
||||
|
||||
isChromeMobile(): boolean {
|
||||
const userAgent = navigator.userAgent.toLowerCase();
|
||||
const vendor = navigator.vendor.toLowerCase();
|
||||
|
||||
const isAndroid = /android/.test(userAgent);
|
||||
const isChrome = /chrome/.test(userAgent) && /google inc/.test(vendor);
|
||||
const isMobile = /mobile/.test(userAgent);
|
||||
|
||||
// Additional check for Chrome's navigator.userAgentData (if available)
|
||||
const userAgentData = (navigator as any).userAgentData;
|
||||
const isMobileDevice = userAgentData?.mobile ?? false;
|
||||
|
||||
// Check if it's Chrome
|
||||
if (isChrome) {
|
||||
// Check if it's on Android or explicitly marked as mobile
|
||||
if (isAndroid || isMobile || isMobileDevice) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private handleGeometryChange(event: any) {
|
||||
}
|
||||
|
||||
private virtualKeyboard: any;
|
||||
componentDidMount(): void {
|
||||
}
|
||||
componentWillUnmount(): void {
|
||||
if (this.virtualKeyboard)
|
||||
{
|
||||
this.virtualKeyboard.removeEventListener("geometryChange",this.handleGeometryChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user