// Copyright (c) 2022 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 { SyntheticEvent } from 'react'; 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 } from './PiPedalModel'; import { PedalBoard, PedalBoardItem, PedalBoardSplitItem, SplitType } from './PedalBoard'; import Button from '@mui/material/Button'; import InputIcon from '@mui/icons-material/Input'; import LoadPluginDialog from './LoadPluginDialog'; import Switch from '@mui/material/Switch'; import Typography from '@mui/material/Typography'; import PedalBoardView from './PedalBoardView'; import { PiPedalStateError } from './PiPedalError'; import IconButton from '@mui/material/IconButton'; import AddIcon from '@mui/icons-material/Add'; import Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; import Fade from '@mui/material/Fade'; import Divider from '@mui/material/Divider'; import ResizeResponsiveComponent from './ResizeResponsiveComponent'; import PluginInfoDialog from './PluginInfoDialog'; import { GetControlView } from './ControlViewFactory'; import MidiBindingsDialog from './MidiBindingsDialog'; import PluginPresetSelector from './PluginPresetSelector'; const SPLIT_CONTROLBAR_THRESHHOLD = 650; 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)"; const styles = ({ palette }: Theme) => createStyles({ frame: { position: "absolute", display: "flex", flexDirection: "column", flexWrap: "nowrap", justifyContent: "flex-start", left: "0px", top: "0px", bottom: "0px", right: "0px", overflow: "hidden" }, pedalBoardScroll: { position: "relative", width: "100%", flex: "0 0 auto", overflow: "auto", maxHeight: 220 }, pedalBoardScrollSmall: { position: "relative", width: "100%", flex: "1 1 1px", overflow: "auto" }, separator: { width: "100%", height: "1px", background: "#888", opacity: "0.5", flex: "0 0 1px" }, controlToolBar: { flex: "0 0 auto", width: "100%", height: 48 }, splitControlBar: { flex: "0 0 48px", width: "100%", paddingLeft: 24, paddingRight: 16, paddingBottom: 16 }, controlContent: { flex: "1 1 auto", width: "100%", overflowY: "auto", minHeight: 240 }, controlContentSmall: { flex: "0 0 162px", width: "100%", height: 162, overflowY: "hidden", }, title: { fontSize: "1.3em", fontWeight: 700, marginRight: 8 }, author: { fontWeight: 500, marginRight: 8 } }); interface MainProps extends WithStyles { hasTinyToolBar: boolean; theme: Theme; } interface MainState { selectedPedal: number; loadDialogOpen: boolean; pedalBoard: PedalBoard; addMenuAnchorEl: HTMLElement | null; splitControlBar: boolean; horizontalScrollLayout: boolean; showMidiBindingsDialog: boolean; screenHeight: number; } export const MainPage = withStyles(styles, { withTheme: true })( class extends ResizeResponsiveComponent { model: PiPedalModel; constructor(props: MainProps) { super(props); this.model = PiPedalModelFactory.getInstance(); let pedalboard = this.model.pedalBoard.get(); let selectedPedal = pedalboard.getFirstSelectableItem(); this.state = { selectedPedal: selectedPedal, loadDialogOpen: false, pedalBoard: pedalboard, addMenuAnchorEl: null, splitControlBar: this.windowSize.width < SPLIT_CONTROLBAR_THRESHHOLD, 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); this.onLoadClick = this.onLoadClick.bind(this); this.onLoadOk = this.onLoadOk.bind(this); this.onLoadCancel = this.onLoadCancel.bind(this); this.onPedalBoardChanged = this.onPedalBoardChanged.bind(this); this.handleEnableCurrentItemChanged = this.handleEnableCurrentItemChanged.bind(this); } onInsertPedal(instanceId: number) { this.setAddMenuAnchorEl(null); let newId = this.model.addPedalBoardItem(instanceId, false); this.setSelection(newId); } onAppendPedal(instanceId: number) { this.setAddMenuAnchorEl(null); let newId = this.model.addPedalBoardItem(instanceId, true); this.setSelection(newId); } onInsertSplit(instanceId: number) { this.setAddMenuAnchorEl(null); let newId = this.model.addPedalBoardSplitItem(instanceId, false); this.setSelection(newId); } onAppendSplit(instanceId: number) { this.setAddMenuAnchorEl(null); let newId = this.model.addPedalBoardSplitItem(instanceId, true); this.setSelection(newId); } setAddMenuAnchorEl(value: HTMLElement | null) { this.setState({ addMenuAnchorEl: value }); } onAddClick(e: SyntheticEvent) { this.setAddMenuAnchorEl(e.currentTarget as HTMLElement); } handleMidiBindingsDialogClose() { this.setState({ showMidiBindingsDialog: false }) } handleAddClose(): void { this.setAddMenuAnchorEl(null); } handleMidiConfiguration(instanceId: number): void { this.setState({ showMidiBindingsDialog: true }); } handleEnableCurrentItemChanged(event: any): void { let newValue = event.target.checked; let item = this.getSelectedPedalBoardItem(); if (item != null) { this.model.setPedalBoardItemEnabled(item.getInstanceId(), newValue); } } handleSelectPluginPreset(instanceId: number, presetInstanceId: number) { this.model.loadPluginPreset(instanceId, presetInstanceId); } onPedalBoardChanged(value: PedalBoard) { let selectedItem = -1; if (value.hasItem(this.state.selectedPedal)) { selectedItem = this.state.selectedPedal; } else { selectedItem = value.getFirstSelectableItem(); } this.setState({ pedalBoard: value, selectedPedal: selectedItem }); } onDeletePedal(instanceId: number): void { let result = this.model.deletePedalBoardPedal(instanceId); if (result != null) { this.setState({ selectedPedal: result }); } } componentDidMount() { super.componentDidMount(); this.model.pedalBoard.addOnChangedHandler(this.onPedalBoardChanged); } componentWillUnmount() { this.model.pedalBoard.removeOnChangedHandler(this.onPedalBoardChanged); super.componentWillUnmount(); } updateResponsive() { this.setState({ splitControlBar: this.windowSize.width < SPLIT_CONTROLBAR_THRESHHOLD, horizontalScrollLayout: this.windowSize.height < HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK, screenHeight: this.windowSize.height }); } onWindowSizeChanged(width: number, height: number): void { super.onWindowSizeChanged(width, height); this.updateResponsive(); } setSelection(selectedId_: number): void { this.setState({ selectedPedal: selectedId_ }); } onSelectionChanged(selectedId: number): void { this.setSelection(selectedId); } onPedalDoubleClick(selectedId: number): void { this.setSelection(selectedId); let item = this.getPedalBoardItem(selectedId); if (item != null) { if (item.isSplit()) { let split = item as PedalBoardSplitItem; if (split.getSplitType() === SplitType.Ab) { let cv = split.getToggleAbControlValue(); if (split.instanceId === undefined) throw new PiPedalStateError("Split without valid id."); this.model.setPedalBoardControlValue(split.instanceId, cv.key, cv.value); } } else { this.setState({ loadDialogOpen: true }); } } } onLoadCancel(): void { this.setState({ loadDialogOpen: false }); } onLoadOk(selectedUri: string): void { this.setState({ loadDialogOpen: false }); let itemId = this.state.selectedPedal; let newSelectedItem = this.model.loadPedalBoardPlugin(itemId, selectedUri); this.setState({ selectedPedal: newSelectedItem }); } onLoadClick(e: SyntheticEvent) { this.setState({ loadDialogOpen: true }); } getPedalBoardItem(selectedId?: number): PedalBoardItem | null { if (selectedId === undefined) return null; let pedalBoard = this.model.pedalBoard.get(); if (!pedalBoard) return null; let it = pedalBoard.itemsGenerator(); if (!selectedId) return null; while (true) { let v = it.next(); if (v.done) break; let item = v.value; if (item.instanceId === selectedId) { return item; } } return null; } onPedalboardPropertyChanged(instanceId: number, key: string, value: number) { this.model.setPedalBoardControlValue(instanceId, key, value); } getSelectedPedalBoardItem(): PedalBoardItem | null { return this.getPedalBoardItem(this.state.selectedPedal); } getSelectedUri(): string { let pedalBoardItem = this.getSelectedPedalBoardItem(); if (pedalBoardItem == null) return ""; return pedalBoardItem.uri; } titleBar(pedalBoardItem: PedalBoardItem | null): React.ReactNode { let title = ""; let author = ""; let pluginUri = ""; let presetsUri = ""; let missing = false; if (pedalBoardItem) { if (pedalBoardItem.isEmpty()) { title = ""; } else if (pedalBoardItem.isSplit()) { title = "Split"; } else { let uiPlugin = this.model.getUiPlugin(pedalBoardItem.uri); if (!uiPlugin) { missing = true; title = pedalBoardItem?.pluginName ?? "Missing plugin"; } else { title = uiPlugin.name; author = uiPlugin.author_name; presetsUri = uiPlugin.uri; if (uiPlugin.description.length > 20) { pluginUri = uiPlugin.uri; } } } } let classes = this.props.classes; if (missing) { return (
{title}
); } else { return (
{title} {author}
); } } render() { let classes = this.props.classes; let pedalBoard = this.model.pedalBoard.get(); let pedalBoardItem = this.getSelectedPedalBoardItem(); let uiPlugin = null; let bypassVisible = false; let bypassChecked = false; let canDelete = false; let canAdd = false; let instanceId = -1; let missing = false; let pluginUri = "#error"; if (pedalBoardItem) { canDelete = pedalBoard.canDeleteItem(pedalBoardItem.instanceId); instanceId = pedalBoardItem.instanceId; if (pedalBoardItem.isEmpty()) { canAdd = true; } else if (pedalBoardItem.isSplit()) { canAdd = true; } else { pluginUri = pedalBoardItem.uri; uiPlugin = this.model.getUiPlugin(pluginUri); canAdd = true; if (uiPlugin) { bypassVisible = true; bypassChecked = pedalBoardItem.isEnabled; } else { missing = true; } } } let horizontalScrollLayout = this.state.horizontalScrollLayout; return (
{ (!this.state.splitControlBar) && this.titleBar(pedalBoardItem) }
{ this.onAddClick(e) }} size="large"> this.handleAddClose()} TransitionComponent={Fade} > this.onInsertPedal(instanceId)}>Insert pedal this.onAppendPedal(instanceId)}>Append pedal this.onInsertSplit(instanceId)}>Insert split this.onAppendSplit(instanceId)}>Append split
{ this.onDeletePedal(pedalBoardItem?.instanceId ?? -1) }} size="large"> Delete
{ this.handleMidiConfiguration(instanceId); }} size="large"> Midi configuration
{ this.state.splitControlBar && (
{ this.titleBar(pedalBoardItem) }
) }
{ missing ? (
Error: Plugin is not installed. {pluginUri}
): ( GetControlView(pedalBoardItem) ) }
this.setState({showMidiBindingsDialog: false} ) } /> { (this.state.loadDialogOpen) && ( ) }
); } } ); export default MainPage