From 9d4cc6e978518421e0ff9cac4cd5ac980478fd25 Mon Sep 17 00:00:00 2001 From: Robin Davies Date: Sat, 5 Oct 2024 02:14:58 -0400 Subject: [PATCH] Memory hardening for AlsaDriver. --- react/CMakeLists.txt | 307 +++++++++++++----------- react/src/App.tsx | 11 +- react/src/AppThemed.tsx | 33 ++- react/src/ColorDropdownButton.tsx | 12 +- react/src/DialogEx.tsx | 54 ++++- react/src/FilePropertyDialog.tsx | 111 ++++++--- react/src/FullScreenIME.tsx | 2 +- react/src/MainPage.tsx | 4 +- react/src/MaterialColors.tsx | 4 +- react/src/Pedalboard.tsx | 2 +- react/src/PerformanceView.tsx | 119 ++++++++- react/src/PluginControlView.tsx | 4 +- react/src/Rectangle.tsx | 32 +++ react/src/RenameDialog.tsx | 15 +- react/src/SnapshotEditor.tsx | 8 +- react/src/SnapshotPropertiesDialog.tsx | 47 +--- react/src/TextFieldEx.tsx | 184 ++++++++++++++ react/src/ToobFrequencyResponseView.tsx | 7 +- react/src/VirtualKeyboardHandler.tsx | 150 ++++++++++++ react/tmp.txt | 171 +++++++++++++ src/AlsaDriver.cpp | 251 +++++++++---------- todo.txt | 18 +- 22 files changed, 1137 insertions(+), 409 deletions(-) create mode 100644 react/src/Rectangle.tsx create mode 100644 react/src/TextFieldEx.tsx create mode 100644 react/src/VirtualKeyboardHandler.tsx create mode 100644 react/tmp.txt diff --git a/react/CMakeLists.txt b/react/CMakeLists.txt index 862f75d..d69c08b 100644 --- a/react/CMakeLists.txt +++ b/react/CMakeLists.txt @@ -21,161 +21,178 @@ add_custom_command( WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/react DEPENDS - src/Updater.tsx - src/OkCancelDialog.tsx - src/PluginControl.tsx - src/svg/ic_save_bank_as.svg - src/svg/fx_constant.svg - src/svg/fx_distortion.svg - src/svg/ic_help_outline.svg - src/svg/fx_oscillator.svg - src/svg/fx_filter.svg - src/svg/fx_function.svg - src/svg/fx_generator.svg - src/svg/fx_compressor.svg - src/svg/file_download_black_24dp.svg - src/svg/fx_filter_hp.svg - src/svg/fx_plugin.svg - src/svg/fx_error.svg - src/svg/fx_gate.svg - src/svg/ic_midi.svg - src/svg/ic_pluginpreset.svg - src/svg/fx_split_a.svg - src/svg/fx_utility.svg - src/svg/ic_pluginpreset2.svg - src/svg/fx_parametric_eq.svg - src/svg/fx_terminal.svg - src/svg/ic_presets.svg - src/svg/fx_flanger.svg - src/svg/fx_analyzer.svg - src/svg/fx_delay.svg - src/svg/fx_spectral.svg - src/svg/fx_chorus.svg - src/svg/fx_simulator.svg - src/svg/drive_file_rename_outline_black_24dp.svg - src/svg/fx_mixer.svg - src/svg/fx_eq.svg - src/svg/fx_modulator.svg - src/svg/file_upload_black_24dp.svg - src/svg/fx_pitch.svg - src/svg/fx_phaser.svg - src/svg/fx_amplifier.svg - src/svg/fx_lr.svg - src/svg/fx_instrument.svg - src/svg/fx_reverb.svg - src/svg/ic_edit_banks.svg - src/svg/fx_limiter.svg - src/svg/fx_empty.svg - src/svg/fx_dial.svg - src/svg/fx_flanger2.svg - src/svg/fx_split_b.svg - src/svg/fx_spatial.svg - src/svg/fx_converter.svg - src/svg/old_delete_outline_24dp.svg - src/svg/ic_settings.svg - src/reportWebVitals.ts - src/JackHostStatus.tsx - src/PluginPresetsDialog.tsx - src/ToobSpectrumAnalyzerView.tsx - src/MidiBindingsDialog.tsx - src/MidiBinding.tsx - src/TemporaryDrawer.tsx - src/ToobFrequencyResponseView.tsx - src/index.css - src/PluginOutputControl.tsx - src/BankDialog.tsx - src/VuMeter.tsx - src/StringBuilder.tsx - src/FilePropertyDialog.tsx - src/GxTunerControl.tsx - src/DarkMode.tsx - src/App.tsx - src/GxTunerView.tsx - src/Rect.tsx - src/ToobSpectrumResponseView.tsx - src/DialogEx.tsx - src/GovernorSettings.tsx - src/FilePropertyControl.tsx - src/JackServerSettingsDialog.tsx - src/ZoomedUiControl.tsx - src/Pedal.tsx - src/FullScreenIME.tsx - src/PluginPreset.tsx - src/SystemMidiBindingsDialog.tsx - src/AppThemed.tsx - src/MainPage.tsx - src/PiPedalModel.tsx - src/PedalboardView.tsx - src/ControlViewFactory.tsx - src/PiPedalSocket.tsx - src/PluginClass.tsx - src/SplitControlView.tsx - src/logo.svg - src/ToobToneStackView.tsx - src/Banks.tsx - src/ToobPowerStage2View.tsx - src/ListSelectDialog.tsx - src/ToobWaveShapeView.tsx - src/SelectThemeDialog.tsx - src/PluginInfoDialog.tsx - src/SearchFilter.tsx - src/SearchControl.tsx - src/JackServerSettings.tsx - src/WifiChannel.tsx - src/WifiConfigDialog.tsx - src/FilePropertyDirectoryTree.tsx - src/App.test.tsx - src/PluginPresetSelector.tsx - src/ObservableProperty.tsx - src/PresetDialog.tsx - src/AndroidHost.tsx - src/SelectHoverBackground.tsx - src/WifiDirectConfigDialog.tsx - src/WifiDirectConfigSettings.tsx - src/WifiConfigSettings.tsx - src/Draggable.tsx - src/JsonAtom.tsx - src/SplitUiControls.tsx - src/SelectChannelsDialog.tsx - src/ToobMLView.tsx - src/IControlViewFactory.tsx - src/AppThemed.css - src/ZoomedDial.tsx - src/Units.tsx - src/UploadPresetDialog.tsx - src/NumericInput.tsx + src/PiPedalError.tsx + src/OldDeleteIcon.tsx + src/MaterialColors.tsx src/Pedalboard.tsx + src/SystemMidiBindingView.tsx + src/GovernorSettings.tsx + src/ObservableProperty.tsx + src/SnapshotEditor.tsx src/SelectMidiChannelsDialog.tsx src/AlsaMidiDeviceInfo.tsx - src/ResizeResponsiveComponent.tsx - src/RenameDialog.tsx - src/FilePropertyDirectorySelectDialog.tsx - src/SystemMidiBindingView.tsx - src/OldDeleteIcon.tsx + src/WifiDirectConfigSettings.tsx + src/WifiConfigSettings.tsx + src/JsonAtom.tsx + src/PiPedalSocket.tsx + src/TemporaryDrawer.tsx + src/DarkMode.tsx + src/MidiBindingsDialog.tsx + src/MidiBinding.tsx + src/SnapshotDialog.tsx + src/SettingsDialog.tsx + src/PedalboardView.tsx + src/reportWebVitals.ts + src/SelectChannelsDialog.tsx + src/PluginPresetSelector.tsx + src/VuMeter.tsx + src/ToobFrequencyResponseView.tsx + src/SystemMidiBindingsDialog.tsx + src/FullScreenIME.tsx + src/App.tsx src/ToobInputStageView.tsx src/XxxSnippet.tsx + src/JackServerSettings.tsx + src/ToobWaveShapeView.tsx + src/ToobPowerStage2View.tsx + src/PluginPreset.tsx + src/logo.svg + src/Banks.tsx src/NoChangePassword.tsx - src/react-app-env.d.ts - src/PiPedalError.tsx - src/AlsaDeviceInfo.tsx - src/index.tsx - src/SettingsDialog.tsx - src/PluginControlView.tsx - src/PresetSelector.tsx - src/ToobCabSimView.tsx - src/Utility.tsx - src/Lv2Plugin.tsx - src/Jack.tsx - src/JackStatusView.tsx - src/AboutDialog.tsx + src/SearchFilter.tsx + src/FilePropertyDirectorySelectDialog.tsx + src/GxTunerView.tsx + src/UpdateDialog.tsx + src/svg/file_download_black_24dp.svg + src/svg/fx_filter.svg + src/svg/fx_pitch.svg + src/svg/fx_distortion.svg + src/svg/fx_split_b.svg + src/svg/fx_chorus.svg + src/svg/fx_limiter.svg + src/svg/fx_phaser.svg + src/svg/snapshot_0.svg + src/svg/snapshot_3.svg + src/svg/file_upload_black_24dp.svg + src/svg/ic_bank.svg + src/svg/fx_function.svg + src/svg/fx_dial.svg + src/svg/ic_settings.svg + src/svg/fx_split_a.svg + src/svg/fx_empty.svg + src/svg/fx_amplifier.svg + src/svg/fx_spectral.svg + src/svg/fx_eq.svg + src/svg/ic_edit_banks.svg + src/svg/ic_help_outline.svg + src/svg/ic_save_bank_as.svg + src/svg/ic_pluginpreset2.svg + src/svg/fx_flanger2.svg + src/svg/snapshot_6.svg + src/svg/fx_lr.svg + src/svg/fx_generator.svg + src/svg/snapshot_1.svg + src/svg/fx_reverb.svg + src/svg/fx_plugin.svg + src/svg/snapshot_4.svg + src/svg/drive_file_rename_outline_black_24dp.svg + src/svg/snapshot_2.svg + src/svg/fx_instrument.svg + src/svg/fx_modulator.svg + src/svg/fx_error.svg + src/svg/fx_simulator.svg + src/svg/fx_filter_hp.svg + src/svg/fx_compressor.svg + src/svg/fx_gate.svg + src/svg/fx_terminal.svg + src/svg/ic_midi.svg + src/svg/fx_oscillator.svg + src/svg/fx_analyzer.svg + src/svg/fx_flanger.svg + src/svg/old_delete_outline_24dp.svg + src/svg/ic_presets.svg + src/svg/fx_delay.svg + src/svg/fx_parametric_eq.svg + src/svg/fx_spatial.svg + src/svg/fx_converter.svg + src/svg/fx_constant.svg + src/svg/snapshot_5.svg + src/svg/ic_pluginpreset.svg + src/svg/fx_mixer.svg + src/svg/fx_utility.svg + src/Updater.tsx + src/UploadPresetDialog.tsx src/setupTests.ts + src/ToobCabSimView.tsx + src/RenameDialog.tsx + src/SplitControlView.tsx + src/SelectHoverBackground.tsx + src/IControlViewFactory.tsx + src/OkCancelDialog.tsx + src/BankDialog.tsx + src/PluginPresetsDialog.tsx + src/App.test.tsx + src/ControlViewFactory.tsx src/DraggableGrid.tsx - src/SvgPathBuilder.tsx - src/LoadPluginDialog.tsx - src/MidiBindingView.tsx + src/ZoomedDial.tsx + src/index.css + src/Pedal.tsx + src/Draggable.tsx src/PluginIcon.tsx + src/react-app-env.d.ts + src/FilePropertyDialog.tsx + src/FilePropertyDirectoryTree.tsx + src/StringBuilder.tsx + src/MainPage.tsx + src/PluginInfoDialog.tsx + src/AppThemed.tsx + src/Lv2Plugin.tsx + src/ToobSpectrumResponseView.tsx + src/PerformanceView.tsx + src/JackServerSettingsDialog.tsx + src/ListSelectDialog.tsx + src/Utility.tsx + src/JackStatusView.tsx + src/ToobSpectrumAnalyzerView.tsx + src/SvgPathBuilder.tsx + src/PresetSelector.tsx + src/PresetDialog.tsx + src/WifiChannel.tsx + src/AboutDialog.tsx + src/GxTunerControl.tsx + src/PluginControl.tsx + src/LoadPluginDialog.tsx + src/ZoomedUiControl.tsx + src/AlsaDeviceInfo.tsx + src/ToobToneStackView.tsx src/UploadFileDialog.tsx - + src/PluginOutputControl.tsx + src/SelectThemeDialog.tsx + src/Rect.tsx + src/AppThemed.css + src/ColorDropdownButton.tsx + src/AndroidHost.tsx + src/SnapshotPanel.tsx + src/ToobMLView.tsx + src/WifiConfigDialog.tsx + src/SnapshotColors.tsx + src/PiPedalModel.tsx + src/JackHostStatus.tsx + src/index.tsx + src/FilePropertyControl.tsx + src/WifiDirectConfigDialog.tsx + src/MidiBindingView.tsx + src/PluginControlView.tsx + src/SplitUiControls.tsx + src/PluginClass.tsx + src/DialogEx.tsx + src/Jack.tsx + src/ResizeResponsiveComponent.tsx + src/NumericInput.tsx + src/SnapshotPropertiesDialog.tsx + src/SearchControl.tsx + src/Units.tsx + ) add_custom_target ( diff --git a/react/src/App.tsx b/react/src/App.tsx index d1daccd..b7c8603 100644 --- a/react/src/App.tsx +++ b/react/src/App.tsx @@ -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 ( - + ); diff --git a/react/src/AppThemed.tsx b/react/src/AppThemed.tsx index bbf4d5a..73615d5 100644 --- a/react/src/AppThemed.tsx +++ b/react/src/AppThemed.tsx @@ -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 (
{this.state.performanceView ? ( - { this.setState({ performanceView: false }); }} /> ) : ( @@ -1028,8 +1039,8 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent< -
 
@@ -1056,7 +1067,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
{/* initial load mask*/} -
+
@@ -1069,7 +1080,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
{/* Reloading mask */}
void; + dropdownAlignment: DropdownAlignment; } const ColorDropdownButton: React.FC = ({ currentColor = colors[0], onColorChange, + dropdownAlignment = DropdownAlignment.SE }) => { const [anchorEl, setAnchorEl] = useState(null); const [selectedColor, setSelectedColor] = useState(currentColor); @@ -38,7 +45,6 @@ const ColorDropdownButton: React.FC = ({ setSelectedColor(color); onColorChange(color); }; - return (
= ({ onClose={handleClose} anchorOrigin={{ vertical: 'bottom', - horizontal: 'right', + horizontal: (dropdownAlignment === DropdownAlignment.SW ? 'right' : 'left') }} transformOrigin={{ vertical: 'top', - horizontal: 'right', + horizontal: (dropdownAlignment === DropdownAlignment.SW ? 'right' : 'left'), }} >
diff --git a/react/src/DialogEx.tsx b/react/src/DialogEx.tsx index 1fe4e28..4879613 100644 --- a/react/src/DialogEx.tsx +++ b/react/src/DialogEx.tsx @@ -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 { +class DialogEx extends React.Component implements IDialogStackable { constructor(props: DialogExProps) { super(props); @@ -93,6 +99,16 @@ class DialogEx extends React.Component { 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 { if (this.props.onClose) this.props.onClose(event,reason); } + onEnterKey() { + + } + onKeyDown(evt: React.KeyboardEvent) + { + if (evt.key === 'Enter') + { + this.onEnterKey(); + } + } render() { - let { tag,onClose, ...extra} = this.props; + let { tag,onClose,...extra} = this.props; return ( - { this.myOnClose(event,reason);}}> - {this.props.children} + { this.myOnClose(event,reason);}} + onKeyDown={(evt)=>{ this.onKeyDown(evt); }} + > + {this.props.children} ); } diff --git a/react/src/FilePropertyDialog.tsx b/react/src/FilePropertyDialog.tsx index b2ca5d2..7a15512 100644 --- a/react/src/FilePropertyDialog.tsx +++ b/react/src/FilePropertyDialog.tsx @@ -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 { 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 (
); + return (); } let breadcrumbs: React.ReactElement[] = [( - { this.navigate(""); }} - sx={{ display: 'flex', alignItems: 'center',cursor: "default" }} + key="h" onClick={() => { this.navigate(""); }} + style={{ textTransform: "none", flexShrink: 0 }} + startIcon={()} > - - Home - + Home + ) ]; 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(( - { this.navigate(myTarget) }}> - {directories[i]} - + / + + ) + ); + breadcrumbs.push(( + )); } { let lastdirectory = directories[directories.length - 1]; breadcrumbs.push(( - / + + ) + ); + breadcrumbs.push(( +
+ {lastdirectory} +
)); } return ( - - {breadcrumbs} - +
+ +
+ {breadcrumbs} +
+ +
); } @@ -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 && ( - this.props.onCancel()} open={this.props.open} tag="fileProperty" fullWidth maxWidth="xl" style={{ height: "90%" }} - PaperProps={{ style: { minHeight: "90%", maxHeight: "90%", overflowY: "visible" } }} + { + this.props.onCancel(); + }} + open={this.props.open} tag="fileProperty" + fullWidth maxWidth="xl" + PaperProps={ + this.state.fullScreen ? + {} + : + { style: { + minHeight: "90%", + maxHeight: "90%", + } }} > - + { this.handleMenuClose();this.onRename(); }}>Rename)} -
+
{this.renderBreadcrumbs()}
-
 
-
+ +
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 })( this.onSelectValue(value.filename)} onDoubleClick={() => { this.onDoubleClickValue(value.filename); }} + onClick={() => this.onSelectValue(value.filename,value.isDirectory)} onDoubleClick={() => { this.onDoubleClickValue(value.filename); }} >
@@ -569,8 +614,8 @@ export default withStyles(styles, { withTheme: true })( ) }
-
-
 
+ +
- Select + {okButtonText}
diff --git a/react/src/FullScreenIME.tsx b/react/src/FullScreenIME.tsx index d706603..dcc9a08 100644 --- a/react/src/FullScreenIME.tsx +++ b/react/src/FullScreenIME.tsx @@ -200,7 +200,7 @@ const FullScreenIME =
{this.props.caption} diff --git a/react/src/MainPage.tsx b/react/src/MainPage.tsx index 3ec74a0..00db0f4 100644 --- a/react/src/MainPage.tsx +++ b/react/src/MainPage.tsx @@ -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 }} > -
-
+
+
diff --git a/react/src/MaterialColors.tsx b/react/src/MaterialColors.tsx index 2ebf99f..bd1d4c0 100644 --- a/react/src/MaterialColors.tsx +++ b/react/src/MaterialColors.tsx @@ -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]; } } \ No newline at end of file diff --git a/react/src/Pedalboard.tsx b/react/src/Pedalboard.tsx index 2f76533..5a8d010 100644 --- a/react/src/Pedalboard.tsx +++ b/react/src/Pedalboard.tsx @@ -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; diff --git a/react/src/PerformanceView.tsx b/react/src/PerformanceView.tsx index 44218ce..8ca17aa 100644 --- a/react/src/PerformanceView.tsx +++ b/react/src/PerformanceView.tsx @@ -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 { + open: boolean, onClose: () => void, theme: Theme } @@ -78,7 +83,7 @@ interface PerformanceViewState { export const PerformanceView = withStyles(styles, { withTheme: true })( - class extends ResizeResponsiveComponent { + class extends ResizeResponsiveComponent 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 =
@@ -188,7 +277,10 @@ export const PerformanceView = alignItems: "start" }}> { this.props.onClose(); }} style={{ flex: "0 0 auto" }} > + onClick={() => { this.props.onClose(); }} + style={{ + position: "relative",top: 3, + flex: "0 0 auto" }} > @@ -206,12 +298,13 @@ export const PerformanceView = { this.handlePreviousBank(); }} + size="medium" color="inherit" style={{ borderTopRightRadius: "3px", borderBottomRightRadius: "3px", marginRight: 4 }} > - + - +
-
+
{ return this.handleOnEdit(index); }} />
+ + + {!this.state.showSnapshotEditor && ( + ) + }
{this.state.showSnapshotEditor && ( { this.setState({ showFileDialog: false }); }} + onCancel={() => { + this.setState({ showFileDialog: false }); + }} onOk={(fileProperty, selectedFile) => { this.model.setPatchProperty( diff --git a/react/src/Rectangle.tsx b/react/src/Rectangle.tsx new file mode 100644 index 0000000..3777516 --- /dev/null +++ b/react/src/Rectangle.tsx @@ -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; + } +}; + diff --git a/react/src/RenameDialog.tsx b/react/src/RenameDialog.tsx index 2cb82bc..583dadb 100644 --- a/react/src/RenameDialog.tsx +++ b/react/src/RenameDialog.tsx @@ -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 - + - + diff --git a/react/src/SnapshotEditor.tsx b/react/src/SnapshotEditor.tsx index 8fe5a9b..c386c12 100644 --- a/react/src/SnapshotEditor.tsx +++ b/react/src/SnapshotEditor.tsx @@ -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 }} > - + { this.setState({ color: newColor }); }} /> @@ -335,7 +337,7 @@ const SnapshotEditor = withStyles(appStyles)(class extends ResizeResponsiveCompo onDialogClosed={() => { this.model_.zoomedUiControl.set(undefined); } } /> - {this.state.showStatusMonitor && ()} + { this.state.showStatusMonitor && ()}
); } diff --git a/react/src/SnapshotPropertiesDialog.tsx b/react/src/SnapshotPropertiesDialog.tsx index 4c3045c..8f3d33a 100644 --- a/react/src/SnapshotPropertiesDialog.tsx +++ b/react/src/SnapshotPropertiesDialog.tsx @@ -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 ( - @@ -126,7 +126,7 @@ export default class SnapshotPropertiesDialog extends ResizeResponsiveComponent<
- + Color -
- { - snapshotColors.map((colorKey, index) => { - let selected = this.state.color === colorKey; - let color = getBackgroundColor(colorKey); - return ( - { - this.setState({color: colorKey}); - }} - - > -
-
- -
- - ); - }) - } -
+ { + this.setState({color: newcolor}); + }} /> +