diff --git a/react/package.json b/react/package.json index 21f609e..55a887c 100644 --- a/react/package.json +++ b/react/package.json @@ -17,6 +17,7 @@ "@types/react-dom": "^17.0.8", "react": "^17.0.2", "react-dom": "^17.0.2", + "react-remark": "^2.1.0", "react-scripts": "5.0.0", "react-virtualized-auto-sizer": "^1.0.6", "react-window": "^1.8.6", @@ -33,7 +34,12 @@ "extends": [ "react-app", "react-app/jest" - ] + ], + "rules": { + "eqeqeq": 2, + "no-unused-expressions": 2, + "@typescript-eslint/no-unused-vars": 2 + } }, "browserslist": { "production": [ diff --git a/react/public/img/cc-by.png b/react/public/img/cc-by.png new file mode 100644 index 0000000..cf59608 Binary files /dev/null and b/react/public/img/cc-by.png differ diff --git a/react/src/FilePropertyControl.tsx b/react/src/FilePropertyControl.tsx index a48b308..8990edd 100644 --- a/react/src/FilePropertyControl.tsx +++ b/react/src/FilePropertyControl.tsx @@ -207,7 +207,7 @@ const FilePropertyControl = {fileProperty.name} + }}> {fileProperty.label} {/* CONTROL SECTION */} diff --git a/react/src/FilePropertyDialog.tsx b/react/src/FilePropertyDialog.tsx index 0bf01c6..cc4ef23 100644 --- a/react/src/FilePropertyDialog.tsx +++ b/react/src/FilePropertyDialog.tsx @@ -153,9 +153,12 @@ export default class FilePropertyDialog extends ResizeResponsiveComponent - {this.props.fileProperty.name} + {this.props.fileProperty.label}
 
@@ -230,7 +233,7 @@ export default class FilePropertyDialog extends ResizeResponsiveComponent this.onSelect(value)} + onClick={() => this.onSelectValue(value)} onDoubleClick={()=> {this.onDoubleClickValue(value);}} >
diff --git a/react/src/LoadPluginDialog.tsx b/react/src/LoadPluginDialog.tsx index 5dd1080..80f2f9d 100644 --- a/react/src/LoadPluginDialog.tsx +++ b/react/src/LoadPluginDialog.tsx @@ -432,7 +432,7 @@ export const LoadPluginDialog = if (!uiPlugin) { return (); } else { - let stereoIndicator = this.stereo_indicator(uiPlugin) + let stereoIndicator = "\u00A0" + this.stereo_indicator(uiPlugin) if (uiPlugin.author_name !== "") { if (uiPlugin.author_homepage !== "") { return ( @@ -440,21 +440,24 @@ export const LoadPluginDialog = {uiPlugin.name + ",\u00A0"}
- + {uiPlugin.author_name}
+
+ {stereoIndicator} +
); } else { return ( - {uiPlugin.name + ", " + uiPlugin.author_name} + {uiPlugin.name + ", " + uiPlugin.author_name + stereoIndicator} ); } } return ( - {uiPlugin?.name ?? ""} + {uiPlugin?.name ?? "" + stereoIndicator} ); } @@ -545,7 +548,7 @@ export const LoadPluginDialog = break; } } - if (position != -1) { + if (position !== -1) { element.scrollToItem({ rowIndex: Math.floor(position / this.state.grid_cell_columns) }); } } diff --git a/react/src/Lv2Plugin.tsx b/react/src/Lv2Plugin.tsx index 2d52d97..7265a1b 100644 --- a/react/src/Lv2Plugin.tsx +++ b/react/src/Lv2Plugin.tsx @@ -18,7 +18,7 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -import {PiPedalArgumentError} from "./PiPedalError"; + import Units from './Units'; interface Deserializable { @@ -83,6 +83,7 @@ export class Port implements Deserializable { export class PortGroup { deserialize(input: any): PortGroup { + this.uri = input.uri; this.symbol = input.symbol; this.name = input.name; this.parent_group = input.parent_group; @@ -99,6 +100,7 @@ export class PortGroup { return result; } + uri: string = ""; symbol: string = ""; name: string = ""; parent_group: string = ""; @@ -151,10 +153,12 @@ export class UiPropertyNotification { export class PiPedalFileProperty { deserialize(input: any): PiPedalFileProperty { - this.name = input.name; + this.label = input.label; this.fileTypes = PiPedalFileType.deserialize_array(input.fileTypes); this.patchProperty = input.patchProperty; this.directory = input.directory; + this.index = input.index; + this.portGroup = input.portGroup; return this; } static deserialize_array(input: any): PiPedalFileProperty[] @@ -198,10 +202,12 @@ export class PiPedalFileProperty { return false; } - name: string = ""; + label: string = ""; fileTypes: PiPedalFileType[] = []; patchProperty: string = ""; directory: string = ""; + index: number = -1; + portGroup: string = ""; }; export class Lv2Plugin implements Deserializable { @@ -635,9 +641,22 @@ export class UiPlugin implements Deserializable { } } return undefined; + } + + getPortGroupByUri(uri: string) :PortGroup | null + { + for (let i = 0; i < this.port_groups.length; ++i) + { + let port_group = this.port_groups[i]; + if (port_group.uri === uri) + { + return port_group; + } + } + return null; } - getPortGroup(symbol: string): PortGroup + getPortGroupBySymbol(symbol: string): PortGroup | null { for (let i = 0; i < this.port_groups.length; ++i) { @@ -647,7 +666,7 @@ export class UiPlugin implements Deserializable { return port_group; } } - throw new PiPedalArgumentError("Port group not found."); + return null; } uri: string = ""; diff --git a/react/src/MainPage.tsx b/react/src/MainPage.tsx index aea72a1..baee5ab 100644 --- a/react/src/MainPage.tsx +++ b/react/src/MainPage.tsx @@ -45,7 +45,7 @@ import PluginInfoDialog from './PluginInfoDialog'; import { GetControlView } from './ControlViewFactory'; import MidiBindingsDialog from './MidiBindingsDialog'; import PluginPresetSelector from './PluginPresetSelector'; -import SaveIconOutline from '@mui/icons-material/Save'; + const SPLIT_CONTROLBAR_THRESHHOLD = 650; @@ -194,7 +194,7 @@ export const MainPage = } onPedalboardChanged(value: Pedalboard) { let selectedItem = -1; - if (this.state.selectedPedal == Pedalboard.START_CONTROL || this.state.selectedPedal == Pedalboard.END_CONTROL) { + if (this.state.selectedPedal === Pedalboard.START_CONTROL || this.state.selectedPedal === Pedalboard.END_CONTROL) { selectedItem = this.state.selectedPedal; } else if (value.hasItem(this.state.selectedPedal)) { selectedItem = this.state.selectedPedal; diff --git a/react/src/PedalboardView.tsx b/react/src/PedalboardView.tsx index 41ebb8e..99b47dd 100644 --- a/react/src/PedalboardView.tsx +++ b/react/src/PedalboardView.tsx @@ -66,6 +66,7 @@ const TERMINAL_ICON_URL = "img/fx_terminal.svg"; function CalculateConnection(numberOfInputs: number, numberOfOutputs: number) { + let result = Math.min(numberOfInputs, numberOfOutputs); if (result > 2) result = 2; return result; diff --git a/react/src/PiPedalModel.tsx b/react/src/PiPedalModel.tsx index ffe201f..faae50a 100644 --- a/react/src/PiPedalModel.tsx +++ b/react/src/PiPedalModel.tsx @@ -38,7 +38,6 @@ import GovernorSettings from './GovernorSettings'; import WifiChannel from './WifiChannel'; import AlsaDeviceInfo from './AlsaDeviceInfo'; import { AndroidHostInterface, FakeAndroidHost } from './AndroidHost'; -import Xxx from './XxxSnippet'; export enum State { @@ -1176,7 +1175,7 @@ export class PiPedalModel //implements PiPedalModel let changed: boolean = false; let pedalboard = this.pedalboard.get(); - if (pedalboard.input_volume_db != volume_db) + if (pedalboard.input_volume_db !== volume_db) { let newPedalboard = pedalboard.clone(); newPedalboard.input_volume_db = volume_db; @@ -1207,7 +1206,7 @@ export class PiPedalModel //implements PiPedalModel let changed: boolean = false; let pedalboard = this.pedalboard.get(); - if (pedalboard.output_volume_db != volume_db) + if (pedalboard.output_volume_db !== volume_db) { let newPedalboard = pedalboard.clone(); newPedalboard.output_volume_db = volume_db; @@ -1234,7 +1233,7 @@ export class PiPedalModel //implements PiPedalModel if (pedalboard === undefined) throw new PiPedalStateError("Pedalboard not ready."); let newPedalboard = pedalboard.clone(); - if (instanceId === Pedalboard.START_CONTROL && key == "volume_db") + if (instanceId === Pedalboard.START_CONTROL && key === "volume_db") { this._setInputVolume(value,notifyServer); return; @@ -1371,7 +1370,7 @@ export class PiPedalModel //implements PiPedalModel // mouse is down. Don't update EVERYBODY, but we must change // the control on the running audio plugin. // TODO: respect "expensive" port attribute. - if (instanceId === Pedalboard.START_CONTROL && key == "volume_db") + if (instanceId === Pedalboard.START_CONTROL && key === "volume_db") { this.previewInputVolume(value); return; @@ -1391,7 +1390,7 @@ export class PiPedalModel //implements PiPedalModel this.updateVst3State(newPedalboard); let result = newPedalboard.deleteItem(instanceId); - if (result != null) { + if (result !== null) { this.pedalboard.set(newPedalboard); this.updateServerPedalboard(); } diff --git a/react/src/PluginControlView.tsx b/react/src/PluginControlView.tsx index 831025a..6645c1b 100644 --- a/react/src/PluginControlView.tsx +++ b/react/src/PluginControlView.tsx @@ -23,7 +23,7 @@ import { WithStyles } from '@mui/styles'; import createStyles from '@mui/styles/createStyles'; import withStyles from '@mui/styles/withStyles'; import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel'; -import { UiPlugin, UiControl, PiPedalFileProperty,ScalePoint} from './Lv2Plugin'; +import { UiPlugin, UiControl, PiPedalFileProperty, ScalePoint } from './Lv2Plugin'; import { Pedalboard, PedalboardItem, ControlValue } from './Pedalboard'; @@ -48,7 +48,7 @@ export const StandardItemSize = { width: 80, height: 110 }; const LANDSCAPE_HEIGHT_BREAK = 500; -function makeIoPluginInfo(name: string, uri: string) : UiPlugin { +function makeIoPluginInfo(name: string, uri: string): UiPlugin { let result = new UiPlugin(); result.name = name; result.uri = uri; @@ -58,12 +58,12 @@ function makeIoPluginInfo(name: string, uri: string) : UiPlugin { volumeControl.index = 0; volumeControl.is_input = true; - volumeControl.min_value = -60; + 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}) + new ScalePoint().deserialize({ label: "-INF", value: -60 }) ]; result.controls = [ volumeControl @@ -71,11 +71,11 @@ function makeIoPluginInfo(name: string, uri: string) : UiPlugin { return result; } -let startPluginInfo: UiPlugin = -makeIoPluginInfo("Input",Pedalboard.START_PEDALBOARD_ITEM_URI); +let startPluginInfo: UiPlugin = + makeIoPluginInfo("Input", Pedalboard.START_PEDALBOARD_ITEM_URI); -let endPluginInfo: UiPlugin = -makeIoPluginInfo("Output",Pedalboard.END_PEDALBOARD_ITEM_URI); +let endPluginInfo: UiPlugin = + makeIoPluginInfo("Output", Pedalboard.END_PEDALBOARD_ITEM_URI); const styles = (theme: Theme) => createStyles({ @@ -181,6 +181,7 @@ const styles = (theme: Theme) => createStyles({ borderRadius: 8, elevation: 12, display: "flex", + textOverflow: "ellipsis", flexDirection: "row", flexWrap: "nowrap", flex: "0 0 auto" }, @@ -188,9 +189,12 @@ const styles = (theme: Theme) => createStyles({ position: "absolute", top: -15, background: "white", + textOverflow: "ellipsis", + minWidth: 0, marginLeft: 20, paddingLeft: 8, - paddingRight: 8 + paddingRight: 8, + margin_right: 28 }, portGroupControls: { display: "flex", @@ -203,11 +207,13 @@ const styles = (theme: Theme) => createStyles({ }); export class ControlGroup { - constructor(name: string, controls: ReactNode[]) { + constructor(name: string, indexes: number[], controls: ReactNode[]) { this.name = name; + this.indexes = indexes; this.controls = controls; } name: string; + indexes: number[]; controls: React.ReactNode[]; } @@ -320,21 +326,20 @@ const PluginControlView = makeFilePropertyUI(fileProperty: PiPedalFileProperty): ReactNode { return (( - { - this.setState({showFileDialog: true,dialogFileProperty: fileProperty,dialogFileValue: selectedFile}); + onFileClick={(fileProperty, selectedFile) => { + this.setState({ showFileDialog: true, dialogFileProperty: fileProperty, dialogFileValue: selectedFile }); }} /> )); } makeStandardControl(uiControl: UiControl, controlValues: ControlValue[]): ReactNode { let symbol = uiControl.symbol; - if (!uiControl.is_input) - { + if (!uiControl.is_input) { return ( - + ); } @@ -362,40 +367,82 @@ const PluginControlView = 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.not_on_gui) { - if (pluginControl.port_group !== "") { - let portGroup = pluginControl.port_group; + 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) ); - while (i + 1 < plugin.controls.length && plugin.controls[i + 1].port_group === portGroup) { + 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.not_on_gui) { groupControls.push( - this.makeStandardControl(pluginControl,controlValues) + this.makeStandardControl(pluginControl, controlValues) ) + indexes.push(pluginControl.index); } } + let controlGroup = new ControlGroup(portGroup.name, indexes, groupControls); result.push( - new ControlGroup(plugin.getPortGroup(portGroup).name, groupControls) + controlGroup ) + portGroupMap[pluginControl.port_group] = controlGroup; } else { result.push( - this.makeStandardControl(pluginControl,controlValues) + this.makeStandardControl(pluginControl, controlValues) ); } } } for (let i = 0; i < plugin.fileProperties.length; ++i) { let fileProperty = plugin.fileProperties[i]; - result.push( - this.makeFilePropertyUI(fileProperty) - ); + 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 + ); + } } return result; } @@ -454,7 +501,7 @@ const PluginControlView = result.push((
- {controlGroup.name} + {controlGroup.name}
{ @@ -476,22 +523,20 @@ const PluginControlView = return result; } - static startPluginInfo: UiPlugin = - makeIoPluginInfo("Input",Pedalboard.START_PEDALBOARD_ITEM_URI); + static startPluginInfo: UiPlugin = + makeIoPluginInfo("Input", Pedalboard.START_PEDALBOARD_ITEM_URI); - static endPluginInfo: UiPlugin = - makeIoPluginInfo("Output",Pedalboard.END_PEDALBOARD_ITEM_URI); + static endPluginInfo: UiPlugin = + makeIoPluginInfo("Output", Pedalboard.END_PEDALBOARD_ITEM_URI); render(): ReactNode { let classes = this.props.classes; let pedalboardItem: PedalboardItem; let pedalboard = this.model.pedalboard.get(); - if (this.props.instanceId == Pedalboard.START_CONTROL) - { + if (this.props.instanceId === Pedalboard.START_CONTROL) { pedalboardItem = pedalboard.makeStartItem(); - } else if (this.props.instanceId == Pedalboard.END_CONTROL) - { + } else if (this.props.instanceId === Pedalboard.END_CONTROL) { pedalboardItem = pedalboard.makeEndItem(); } else { pedalboardItem = pedalboard.getItem(this.props.instanceId); @@ -504,14 +549,12 @@ const PluginControlView = let plugin: UiPlugin; - if (pedalboardItem.isStart()) - { + if (pedalboardItem.isStart()) { plugin = startPluginInfo; - controlValues = [new ControlValue("volume_db",pedalboard.input_volume_db)]; - } else if (pedalboardItem.isEnd()) - { + controlValues = [new ControlValue("volume_db", pedalboard.input_volume_db)]; + } else if (pedalboardItem.isEnd()) { plugin = endPluginInfo; - controlValues = [new ControlValue("volume_db",pedalboard.output_volume_db)]; + controlValues = [new ControlValue("volume_db", pedalboard.output_volume_db)]; } else { plugin = nullCast(this.model.getUiPlugin(pedalboardItem.uri)); } @@ -553,27 +596,27 @@ const PluginControlView = ) }
- { this.setState({ showFileDialog: false});}} - onOk={(fileProperty,selectedFile)=> { + { this.setState({ showFileDialog: false }); }} + onOk={(fileProperty, selectedFile) => { this.model.setPatchProperty( this.props.instanceId, fileProperty.patchProperty, JsonAtom.Path(selectedFile) ) - .then( () => { + .then(() => { - }) - .catch((error) => - { - this.model.showAlert("setPatchProperty failed: " +error); - }); - this.setState({ showFileDialog: false});} + }) + .catch((error) => { + this.model.showAlert("setPatchProperty failed: " + error); + }); + this.setState({ showFileDialog: false }); } - /> + } + /> this.onImeValueChange(key, value)} diff --git a/react/src/PluginInfoDialog.tsx b/react/src/PluginInfoDialog.tsx index 666479c..986d43d 100644 --- a/react/src/PluginInfoDialog.tsx +++ b/react/src/PluginInfoDialog.tsx @@ -35,10 +35,15 @@ import { PiPedalModelFactory } from "./PiPedalModel"; import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; import { UiPlugin, UiControl } from './Lv2Plugin'; import PluginIcon from './PluginIcon'; +import { Remark } from 'react-remark'; + /* eslint-disable */ +let myTheme: Theme| undefined = undefined; const styles = (theme: Theme) => - createStyles({ +{ + myTheme = theme; + return createStyles({ root: { margin: 0, padding: theme.spacing(2), @@ -50,7 +55,7 @@ const styles = (theme: Theme) => color: theme.palette.grey[500], }, }); - +}; export interface PluginInfoDialogTitleProps extends WithStyles { id: string; children: React.ReactNode; @@ -116,23 +121,23 @@ function ioDescription(plugin: UiPlugin): string { } -function makeParagraphs(description: string) { - description = description.replaceAll('\r', ''); - description = description.replaceAll('\n\n', '\r'); - description = description.replaceAll('\n', ' '); +// function makeParagraphs(description: string) { +// description = description.replaceAll('\r', ''); +// description = description.replaceAll('\n\n', '\r'); +// description = description.replaceAll('\n', ' '); - let paragraphs: string[] = description.split('\r'); - return ( -
- {paragraphs.map((para) => ( - - {para} - - ))} -
+// let paragraphs: string[] = description.split('\r'); +// return ( +//
+// {paragraphs.map((para) => ( +// +// {para} +// +// ))} +//
- ); -} +// ); +// } function makeControls(controls: UiControl[]) { let hasComments = false; @@ -146,7 +151,7 @@ function makeControls(controls: UiControl[]) { let trs: React.ReactElement[] = []; for (let i = 0; i < controls.length; ++i) { let control = controls[i]; - + if (!(control.not_on_gui) && control.is_input) trs.push(( @@ -246,37 +251,66 @@ const PluginInfoDialog = withStyles(styles)((props: PluginInfoProps) => {
-
-
- +
+
+ Author:  {(plugin.author_homepage !== "") - ? {plugin.author_name} - : ( - plugin.author_name + ? ( + {plugin.author_name} + ) + : ( + {plugin.author_name} + ) + }
- + {ioDescription(plugin)}
- + Controls: { makeControls(plugin.controls) } - + Description: - { - (plugin.description !== "") && makeParagraphs(plugin.description) - } +
+ { + // return ( + //

+ // ); + return ( + + ); + + }, + code: (props: any) => { + return (); + }, + a: (props: any) => { + return ( + + ); + + } + }, + }} + > + {plugin.description} + +