// Copyright (c) Robin E.R. Davies // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import { ReactNode } from 'react'; import { Theme } from '@mui/material/styles'; import WithStyles, { withTheme } from './WithStyles'; import { createStyles } from './WithStyles'; import { css } from '@emotion/react'; import IconButtonEx from './IconButtonEx'; import SidechainConnectIcon from './svg/ic_sidechain_open_48.svg?react'; import AutoZoom from './AutoZoom'; import ModGuiHost from './ModGuiHost'; import { midiChannelBindingControlFeatureEnabled } from './MidiChannelBinding'; import { withStyles } from "tss-react/mui"; import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel'; import { UiPlugin, UiControl, UiFileProperty, UiFrequencyPlot, ScalePoint } from './Lv2Plugin'; import { Pedalboard, PedalboardItem, ControlValue } from './Pedalboard'; import PluginControl, {CustomPluginControl} from './PluginControl'; import ResizeResponsiveComponent from './ResizeResponsiveComponent'; import SideChainSelectControl, { SideChainSelectDialog } from './SideChainSelectControl'; import VuMeter from './VuMeter'; import { nullCast } from './Utility' import { PiPedalStateError } from './PiPedalError'; import Typography from '@mui/material/Typography'; import FullScreenIME from './FullScreenIME'; import FilePropertyControl from './FilePropertyControl'; import FilePropertyDialog from './FilePropertyDialog'; import JsonAtom from './JsonAtom'; import PluginOutputControl from './PluginOutputControl'; import Units from './Units'; import ToobFrequencyResponseView from './ToobFrequencyResponseView'; import ToolTipEx from './ToolTipEx'; import MidiChannelBindingControl from './MidiChannelBindingControl'; import MidiChannelBinding from './MidiChannelBinding'; export const StandardItemSize = { width: 80, height: 110 }; const LANDSCAPE_HEIGHT_BREAK = 500; export interface ICustomizationHost { makeStandardControl(uiControl: UiControl, controlValues: ControlValue[]): ReactNode; makeCustomControl(title: string, mainControl: ReactNode, editControl?: ReactNode): ReactNode; renderControlGroup(controlGroup: ControlGroup, key: string): ReactNode; isLandscapeGrid(): boolean; } interface SideChainSelectItem { instanceId: number; title: string; } function makeIoPluginInfo(name: string, uri: string): UiPlugin { let result = new UiPlugin(); result.name = name; result.uri = uri; let volumeControl = new UiControl(); volumeControl.name = "Volume"; volumeControl.symbol = "volume_db"; volumeControl.index = 0; volumeControl.is_input = true; volumeControl.min_value = -60; volumeControl.max_value = 30; volumeControl.default_value = 0; volumeControl.units = Units.db; volumeControl.scale_points = [ new ScalePoint().deserialize({ label: "-INF", value: -60 }) ]; result.controls = [ volumeControl ]; return result; } export const startPluginInfo: UiPlugin = makeIoPluginInfo("Input", Pedalboard.START_PEDALBOARD_ITEM_URI); export const endPluginInfo: UiPlugin = makeIoPluginInfo("Output", Pedalboard.END_PEDALBOARD_ITEM_URI); const styles = (theme: Theme) => createStyles({ frame: css({ display: "block", position: "relative", flexDirection: "row", flexWrap: "nowrap", paddingTop: "8px", paddingBottom: "0px", height: "100%", overflowX: "hidden", overflowY: "hidden" }), noScrollFrame: css({ display: "block", position: "relative", width: "100%", height: "100%", left: 0, top: 0, flexDirection: "row", flexWrap: "nowrap", paddingTop: "0px", paddingBottom: "0px", overflowX: "hidden", overflowY: "hidden" }), frameScrollNone: css({ display: "block", position: "relative", left: 0, top: 0, width: "100%", height: "100%", flexDirection: "row", flexWrap: "nowrap", paddingTop: "0px", paddingBottom: "0px", overflowX: "auto", overflowY: "hidden" }), frameScrollLandscape: css({ display: "block", position: "absolute", left: 0, top: 0, right: 0, bottom: 0, flexDirection: "row", flexWrap: "nowrap", paddingTop: "0px", paddingBottom: "0px", overflowX: "auto", overflowY: "hidden" }), frameScrollPortrait: css({ display: "block", position: "absolute", left: 0, top: 0, right: 0, bottom: 0, flexDirection: "row", flexWrap: "nowrap", paddingTop: "0px", paddingBottom: "96px", overflowX: "hidden", overflowY: "auto", }), vuMeterL: css({ position: "absolute", left: 0, top: 0, paddingLeft: 6, paddingRight: 4, paddingBottom: 12, // cover bottom line of a portgroup in landscape. background: theme.mainBackground, zIndex: 3 }), vuMeterR: css({ position: "absolute", right: 0, top: 0, marginRight: 20, // has to potentially clear a scrollbar. paddingLeft: 4, paddingBottom: 12, background: theme.mainBackground, zIndex: 3 }), vuMeterRLandscape: css({ position: "absolute", right: 0, top: 0, paddingRight: 6, paddingLeft: 12, paddingBottom: 13, // cover bottom line of a portgroup in landscape. background: theme.mainBackground, zIndex: 3 }), noScrollGrid: css({ position: "relative", left: 0, top: 0, bottom: 0, width: "100%", height: "100%", paddingLeft: 30, paddingRight: 45, paddingTop: 0, flex: "1 1 auto", }), normalGrid: css({ position: "relative", paddingLeft: 30, paddingRight: 45, paddingTop: 8, flex: "1 1 auto", display: "flex", flexDirection: "row", flexWrap: "wrap", justifyContent: "flex-start", alignItems: "flex_start", rowGap: 14, height: "fit-content" }), landscapeGrid: css({ paddingLeft: 40, paddingRight: 40, paddingTop: 8, // marginRight: 40, : bug in chrome layout engine wrt/ right margin/padding. // See the spacer div added after all controls in render() with provides the same effect. display: "flex", flexDirection: "row", flexWrap: "nowrap", justifyContent: "flex-start", alignItems: "flex-start", position: "relative", overflowX: "hidden", overflowY: "hidden", flex: "0 0 auto", width: "fit-content" }), portgroupControlPadding: css({ flex: "0 0 auto", marginTop: 12, marginBottom: 8 }), controlPadding: css({ flex: "0 0 auto", marginTop: 0, marginBottom: 0, height: 116 }), controlPair: css({ display: "flex", flexFlow: "row nowrap", flex: "0 0 auto", height: 116 }), controlSpacer: css({ display: "none", minWidth: 0, width: 0, height: 116 }), portGroup: css({ marginLeft: 8, marginTop: 0, marginRight: 8, marginBottom: 12, position: "relative", paddingLeft: 3, paddingRight: 8, paddingTop: 0, paddingBottom: 0, border: "2pt #AAA solid", borderRadius: 8, elevation: 12, display: "flex", flexDirection: "row", flexWrap: "wrap", flex: "0 1 auto", }), portGroupLandscape: css({ marginLeft: 8, marginTop: 0, marginRight: 8, marginBottom: 12, position: "relative", paddingLeft: 0, paddingRight: 0, paddingTop: 0, paddingBottom: 0, border: "2pt #AAA solid", borderRadius: 8, elevation: 12, display: "inline-flex", textOverflow: "ellipsis", flexDirection: "row", flexWrap: "nowrap", flex: "0 0 auto", width: "fit-content", minWidth: "max-content" }), portGroupTitle: css({ position: "absolute", top: -15, background: theme.mainBackground, textOverflow: "ellipsis", minWidth: 0, marginLeft: 20, paddingLeft: 8, paddingRight: 8, margin_right: 28 }), portGroupControls: css({ display: "flex", flexDirection: "row", flexWrap: "wrap", paddingTop: 6, rowGap: 16, paddingBottom: 8 }), portGroupControlsLandscape: css({ display: "flex", flexFlow: "row nowrap", width: "fit-content", paddingTop: 6, rowGap: 16, paddingBottom: 8 }) }); export class ControlGroup { constructor(name: string, indexes: number[], controls: ReactNode[]) { this.name = name; this.indexes = indexes; this.controls = controls; } name: string; indexes: number[]; controls: React.ReactNode[]; } export type ControlNodes = (ReactNode | ControlGroup)[]; export interface ControlViewCustomization { modifyControls(host: ICustomizationHost, controls: (React.ReactNode | ControlGroup)[]): (React.ReactNode | ControlGroup)[]; fullScreen(): boolean; } export interface PluginControlViewProps extends WithStyles { theme: Theme; instanceId: number; item: PedalboardItem; customization?: ControlViewCustomization; customizationId?: number; showModGui: boolean; onSetShowModGui: (instanceId: number, showModGui: boolean) => void; } type PluginControlViewState = { landscapeGrid: boolean; imeUiControl?: UiControl; imeValue: number; imeCaption: string; imeInitialHeight: number; showFileDialog: boolean, dialogFileProperty: UiFileProperty, dialogFileValue: string, modGuiContentReady: boolean, showModGuiZoomed: boolean, sidechainDialogOpen: boolean }; const PluginControlView = withTheme(withStyles( class extends ResizeResponsiveComponent { model: PiPedalModel; constructor(props: PluginControlViewProps) { super(props); this.model = PiPedalModelFactory.getInstance(); this.state = { landscapeGrid: false, imeUiControl: undefined, imeValue: 0, imeCaption: "", imeInitialHeight: 0, showFileDialog: false, dialogFileProperty: new UiFileProperty(), dialogFileValue: "", modGuiContentReady: false, showModGuiZoomed: false, sidechainDialogOpen: false } this.onPedalboardChanged = this.onPedalboardChanged.bind(this); this.onControlValueChanged = this.onControlValueChanged.bind(this); this.onPreviewChange = this.onPreviewChange.bind(this); } // unmonitorPort: (handle: MonitorPortHandle) => void; // onValueChanged: (instanceId: number, symbol: string, value: number) => void; // monitorPatchProperty( // instanceId: number, // propertyUri: string, // onReceived: PatchPropertyListener // ): ListenHandle; // cancelMonitorPatchProperty(listenHandle: ListenHandle): void; // getPatchProperty(instanceId: number, uri: string): Promise; // setPatchProperty(instanceId: number, uri: string, value: any): Promise onPreviewChange(key: string, value: number): void { this.model.previewPedalboardValue(this.props.instanceId, key, value); } onControlValueChanged(key: string, value: number): void { this.model.setPedalboardControl(this.props.instanceId, key, value); } onPedalboardChanged(value?: Pedalboard) { //let item = this.model.pedalboard.get().maybeGetItem(this.props.instanceId); //this.setState({ pedalboardItem: item }); } componentDidMount() { super.componentDidMount(); this.model.pedalboard.addOnChangedHandler(this.onPedalboardChanged); } componentWillUnmount() { this.model.pedalboard.removeOnChangedHandler(this.onPedalboardChanged); super.componentWillUnmount(); } onWindowSizeChanged(width: number, height: number): void { this.setState({ landscapeGrid: height < LANDSCAPE_HEIGHT_BREAK }); } filterNotOnGui(controlValues: ControlValue[], uiPlugin: UiPlugin): ControlValue[] { let result: ControlValue[] = []; for (let i = 0; i < controlValues.length; ++i) { let controlValue = controlValues[i]; let control = uiPlugin.getControl(controlValue.key); if (control && !control.isHidden()) { result.push(controlValue); } } return result; } requestImeEdit(uiControl: UiControl, value: number) { // eslint-disable-next-line no-restricted-globals this.setState({ imeUiControl: uiControl, imeValue: value, imeCaption: uiControl.name, imeInitialHeight: document.documentElement.clientHeight }); } makeFilePropertyUI(fileProperty: UiFileProperty): ReactNode { return (( { this.setState({ showFileDialog: true, dialogFileProperty: fileProperty, dialogFileValue: selectedFile }); }} /> )); } makeFrequencyPlotUI(frequencyPlot: UiFrequencyPlot): ReactNode { return (( )); } private ixKey: number = 1; makeStandardControl(uiControl: UiControl, controlValues: ControlValue[]): ReactNode { let symbol = uiControl.symbol; if (!uiControl.is_input) { return ( ); } let controlValue: ControlValue | undefined = undefined; for (let i = 0; i < controlValues.length; ++i) { if (controlValues[i].key === symbol) { controlValue = controlValues[i]; break; } } if (!controlValue) { throw new PiPedalStateError("Missing control value."); } return (( { this.onControlValueChanged(controlValue!.key, value) }} onPreviewChange={(value: number) => { this.onPreviewChange(controlValue!.key, value) }} requestIMEEdit={(uiControl: any, value: any) => this.requestImeEdit(uiControl, value)} /> )); // return (( // { this.onControlValueChanged(controlValue!.key, value) }} // onPreviewChange={(value: number) => { this.onPreviewChange(controlValue!.key, value) }} // requestIMEEdit={(uiControl: any, value: any) => this.requestImeEdit(uiControl, value)} // /> // )); } push_control(controls: ControlNodes, pluginControl: UiControl, controlValues: ControlValue[]) { // combine lamps with their previous control if ((pluginControl.isLamp() || pluginControl.isDbVu()) && controls.length !== 0) { const classes = withStyles.getClasses(this.props); let newControl = this.makeStandardControl(pluginControl, controlValues); let previousControl = controls[controls.length - 1]; if (!(previousControl instanceof ControlGroup)) { let pair = (
{previousControl as ReactNode} {newControl}
); controls[controls.length - 1] = pair; // push a spacer control in order to make placing of extended controls predictable. // (e.g.. inserting at position 4 still places the extended control after four previous controls controls.push((
)); } else { controls.push(newControl); } } else { controls.push( this.makeStandardControl(pluginControl, controlValues) ) } } private getSidechainSelectItems(): SideChainSelectItem[] { let myInstanceId = this.props.item.instanceId; let items: SideChainSelectItem[] = []; items.push({ instanceId: -1, title: "None" }); items.push({ instanceId: -2, title: "Input" }); let pedalboard = this.model.pedalboard.get(); if (!pedalboard) return items; let it = pedalboard.itemsGenerator(); let found = false; while (true) { let v = it.next(); if (v.done) { break; } let pedalboardItem = v.value; if (pedalboardItem.isSplit()) { continue; } if (pedalboardItem.instanceId === myInstanceId) { found = true; break; } if (pedalboardItem.uri.length === 0) { continue; } let pluginInfo = this.model.getUiPlugin(pedalboardItem.uri); if (!pluginInfo) continue; let name = pluginInfo.name; if (pedalboardItem.title.length !== 0) { name = pedalboardItem.title; } items.push({ instanceId: pedalboardItem.instanceId, title: name }); } if (!found) { return [{ instanceId: -1, title: "None" }]; } return items; } private makeSideChainSelect(uiPlugin: UiPlugin) { let items = this.getSidechainSelectItems(); let selectedInstanceId = this.props.item.sideChainInputId; let title = uiPlugin.audio_side_chain_title ? uiPlugin.audio_side_chain_title : "Side chain"; return ( { this.model.setPedalboardSideChainInput(this.props.item.instanceId, instanceId); }} /> ); } isLandscapeGrid(): boolean { return this.state.landscapeGrid; } renderControlGroup(controlGroup: ControlGroup, key: string): ReactNode { let isLandscapeGrid = this.state.landscapeGrid; const classes = withStyles.getClasses(this.props); let controls: ReactNode[] = []; for (let j = 0; j < controlGroup.controls.length; ++j) { let item = controlGroup.controls[j]; controls.push( (
{item}
) ); } return (
{controlGroup.name !== "" && (
{controlGroup.name}
)}
{ controls }
); } getStandardControlNodes(plugin: UiPlugin, controlValues: ControlValue[]): ControlNodes { let result: ControlNodes = []; let portGroupMap: { [id: string]: ControlGroup } = {}; for (let i = 0; i < plugin.controls.length; ++i) { let pluginControl = plugin.controls[i]; if (!pluginControl.isHidden()) { if (pluginControl.port_group !== "" && plugin.getPortGroupBySymbol(pluginControl.port_group)) { let portGroup = nullCast(plugin.getPortGroupBySymbol(pluginControl.port_group)); let groupControls: ReactNode[] = []; let indexes: number[] = []; groupControls.push( this.makeStandardControl(pluginControl, controlValues) ); indexes.push(pluginControl.index); while (i + 1 < plugin.controls.length && plugin.controls[i + 1].port_group === pluginControl.port_group) { ++i; pluginControl = plugin.controls[i]; if (!pluginControl.isHidden()) { this.push_control(groupControls, pluginControl, controlValues); indexes.push(pluginControl.index); } } let controlGroup = new ControlGroup(portGroup.name, indexes, groupControls); result.push( controlGroup ) portGroupMap[pluginControl.port_group] = controlGroup; } else { this.push_control(result, pluginControl, controlValues); } } } for (let i = 0; i < plugin.fileProperties.length; ++i) { let fileProperty = plugin.fileProperties[i]; let filePropertyUi = this.makeFilePropertyUI(fileProperty); if (fileProperty.portGroup !== "" && plugin.getPortGroupByUri(fileProperty.portGroup)) { let portGroup = nullCast(plugin.getPortGroupByUri(fileProperty.portGroup)); let controlGroup = portGroupMap[portGroup.symbol]; if (controlGroup) { let insertPosition = controlGroup.indexes.length; if (fileProperty.index !== -1) { for (let i = 0; i < controlGroup.controls.length; ++i) { if (controlGroup.indexes[i] >= fileProperty.index) { insertPosition = i; break; } } } let index = fileProperty.index !== -1 ? fileProperty.index : 100; controlGroup.controls.splice(insertPosition, 0, filePropertyUi); controlGroup.indexes.splice(insertPosition, 0, index); } else { let index = fileProperty.index !== -1 ? fileProperty.index : 100; let controlGroup = new ControlGroup( portGroup.name, [index], [filePropertyUi]); result.push( controlGroup ); portGroupMap[portGroup.symbol] = controlGroup; } } else if (fileProperty.index !== -1) { result.splice(fileProperty.index, 0, filePropertyUi); } else { result.push( filePropertyUi ); } } for (let i = 0; i < plugin.frequencyPlots.length; ++i) { let frequencyPlot = plugin.frequencyPlots[i]; let frequencyPlotUi = this.makeFrequencyPlotUI(frequencyPlot); if (frequencyPlot.portGroup !== "" && plugin.getPortGroupByUri(frequencyPlot.portGroup)) { let portGroup = nullCast(plugin.getPortGroupByUri(frequencyPlot.portGroup)); let controlGroup = portGroupMap[portGroup.symbol]; if (controlGroup) { let insertPosition = controlGroup.indexes.length; if (frequencyPlot.index !== -1) { for (let i = 0; i < controlGroup.controls.length; ++i) { if (controlGroup.indexes[i] >= frequencyPlot.index) { insertPosition = i; break; } } } let index = frequencyPlot.index !== -1 ? frequencyPlot.index : 100; controlGroup.controls.splice(insertPosition, 0, frequencyPlotUi); controlGroup.indexes.splice(insertPosition, 0, index); } else { let index = frequencyPlot.index !== -1 ? frequencyPlot.index : 100; let controlGroup = new ControlGroup( portGroup.name, [index], [frequencyPlotUi]); result.push( controlGroup ); portGroupMap[portGroup.symbol] = controlGroup; } } else if (frequencyPlot.index !== -1) { result.splice(frequencyPlot.index, 0, frequencyPlotUi); } else { result.push( frequencyPlotUi ); } } if (plugin.audio_side_chain_inputs !== 0) { result.push(this.makeSideChainSelect(plugin)); } return result; } getControl(controlValues: ControlValue[], key: string) { for (let i = 0; i < controlValues.length; ++i) { if (controlValues[i].key === key) { return controlValues[i]; } } throw new Error("Not found."); } onImeValueChange(key: string, value: number) { this.model.setPedalboardControl(this.props.instanceId, key, value); this.onImeClose(); } onImeClose() { this.setState({ imeUiControl: undefined }); } hasGroups(nodes: (ReactNode | ControlGroup)[]): boolean { for (let i = 0; i < nodes.length; ++i) { let node = nodes[i]; if (node instanceof ControlGroup) return true; } return false; } controlKeyIndex: number = 0; controlNodesToNodes(nodes: (ReactNode | ControlGroup)[]): ReactNode[] { const classes = withStyles.getClasses(this.props); let hasGroups = this.hasGroups(nodes); let result: ReactNode[] = []; for (let i = 0; i < nodes.length; ++i) { let node = nodes[i]; if (node instanceof ControlGroup) { let controlGroup = node as ControlGroup; let item = this.renderControlGroup(controlGroup, "cgix" + i) result.push(item); } else { if (this.fullScreen()) { result.push(
{node as ReactNode}
); } else { result.push((
{node as ReactNode}
)); } } } return result; } static startPluginInfo: UiPlugin = makeIoPluginInfo("Input", Pedalboard.START_PEDALBOARD_ITEM_URI); static endPluginInfo: UiPlugin = makeIoPluginInfo("Output", Pedalboard.END_PEDALBOARD_ITEM_URI); midiBindingControl(pedalboardItem: PedalboardItem): ReactNode { if ((!pedalboardItem.midiChannelBinding) || (midiChannelBindingControlFeatureEnabled === false)) { return false; } return ( { }} /> ) } fullScreen() { if (this.props.customization) { return this.props.customization.fullScreen(); } return false; } handleSelectSidechainInput() { this.setState({ sidechainDialogOpen: true }); } handleModGuiFileProperty(instanceId: number, filePropertyUri: string, selectedFile: string) { let plugin = this.model.getUiPlugin(this.props.item.uri); if (!plugin) { throw (new Error("Plugin not found.")); } let fileProperty = plugin.getFilePropertyByUri(filePropertyUri); if (!fileProperty) { throw (new Error("File property not found.")); } this.setState({ showFileDialog: true, dialogFileProperty: fileProperty, dialogFileValue: selectedFile }); } renderModGui(): ReactNode { const classes = withStyles.getClasses(this.props); void classes; let uiPlugin = this.model.getUiPlugin(this.props.item.uri); if (!uiPlugin) { return (
); } let toolBarChildren: ReactNode[] = []; if (uiPlugin.audio_side_chain_inputs > 0) { toolBarChildren.push( { this.handleSelectSidechainInput(); }} > ); } return (
{ this.setState({ showModGuiZoomed: value }); } } > { if (this.props.onSetShowModGui) { this.props.onSetShowModGui(this.props.instanceId, false); } this.setState({ showModGuiZoomed: false }) }} handleFileSelect={(instanceId, fileProperty, selectedFile) => { this.handleModGuiFileProperty( instanceId, fileProperty, selectedFile); }} onContentReady={(value) => { this.setState({ modGuiContentReady: value }); }} />
) } makeCustomControl(title: string, mainControl: ReactNode, editControl?: ReactNode): ReactNode { return (); } renderPiPedalControl(pedalboardItem?: PedalboardItem): ReactNode { this.controlKeyIndex = 0; const classes = withStyles.getClasses(this.props); let pedalboard = this.model.pedalboard.get(); if (!pedalboardItem) return (
); let controlValues = pedalboardItem.controlValues; let plugin: UiPlugin; if (pedalboardItem.isStart()) { plugin = startPluginInfo; controlValues = [new ControlValue("volume_db", pedalboard.input_volume_db)]; } else if (pedalboardItem.isEnd()) { plugin = endPluginInfo; controlValues = [new ControlValue("volume_db", pedalboard.output_volume_db)]; } else { plugin = nullCast(this.model.getUiPlugin(pedalboardItem.uri)); } controlValues = this.filterNotOnGui(controlValues, plugin); let gridClass = this.state.landscapeGrid ? classes.landscapeGrid : classes.normalGrid; let scrollClass = this.state.landscapeGrid ? classes.frameScrollLandscape : classes.frameScrollPortrait; if (this.fullScreen()) { gridClass = classes.noScrollGrid; scrollClass = classes.frameScrollNone; } let controlNodes: ControlNodes; controlNodes = this.getStandardControlNodes(plugin, controlValues); if (this.props.customization) { // allow wrapper class to insert/remove/rebuild controls. controlNodes = this.props.customization.modifyControls(this, controlNodes); } let nodes = this.controlNodesToNodes(controlNodes); if (plugin.has_midi_input && !pedalboardItem.midiChannelBinding) { pedalboardItem.midiChannelBinding = MidiChannelBinding.CreateMissingValue(); } if (pedalboardItem.midiChannelBinding && midiChannelBindingControlFeatureEnabled) { nodes.push(this.midiBindingControl(pedalboardItem)); } return (
{ nodes } {/* Extra space to allow scrolling right to the end in lascape especially */} {!this.fullScreen() && (
)} { (!this.state.landscapeGrid) && (!this.fullScreen()) && (
) }
); } render(): ReactNode { const classes = withStyles.getClasses(this.props); let pedalboard = this.model.pedalboard.get(); let pedalboardItem: PedalboardItem; if (this.props.instanceId === Pedalboard.START_CONTROL_ID) { pedalboardItem = pedalboard.makeStartItem(); } else if (this.props.instanceId === Pedalboard.END_CONTROL_ID) { pedalboardItem = pedalboard.makeEndItem(); } else { pedalboardItem = pedalboard.getItem(this.props.instanceId); } let vuMeterRClass = this.state.landscapeGrid ? classes.vuMeterRLandscape : classes.vuMeterR; let frameClass = classes.frame; if (this.fullScreen() || this.props.showModGui) { frameClass = classes.noScrollFrame; } return (
{ this.props.showModGui && this.renderModGui() || this.renderPiPedalControl(pedalboardItem) } {this.state.showFileDialog && ( { this.setState({ showFileDialog: false }); }} onApply={(fileProperty, selectedFile) => { this.model.setPatchProperty( this.props.instanceId, fileProperty.patchProperty, JsonAtom._Path(selectedFile).asAny() ) .then(() => { }) .catch((error) => { this.model.showAlert("Unable to complete the operation. " + error); }); }} onOk={(fileProperty, selectedFile) => { this.model.setPatchProperty( this.props.instanceId, fileProperty.patchProperty, JsonAtom._Path(selectedFile).asAny() ) .then(() => { }) .catch((error) => { this.model.showAlert("Unable to complete the operation. " + error); }); this.setState({ showFileDialog: false }); } } /> )} {this.state.sidechainDialogOpen && ( { this.setState({ sidechainDialogOpen: false }); }} selectItems={this.getSidechainSelectItems()} selectedInstanceId={this.props.item.sideChainInputId} onChanged={(instanceId: number) => { this.model.setPedalboardSideChainInput(this.props.item.instanceId, instanceId); }} /> )} {/* xxx: I don't think we need this anyore. */} this.onImeValueChange(key, value)} initialHeight={ this.state.imeInitialHeight} caption={this.state.imeCaption} onClose={() => this.onImeClose()} />
); } }, styles )); export default PluginControlView;