Copy/Paste Plugin

This commit is contained in:
Robin E.R. Davies
2026-05-23 18:48:05 -04:00
parent a9b7ece592
commit aa411574fb
3 changed files with 171 additions and 91 deletions
+8 -9
View File
@@ -427,7 +427,6 @@ export const MainPage =
let title = ""; let title = "";
let author = ""; let author = "";
let infoPluginUri = ""; let infoPluginUri = "";
let presetsUri = "";
let missing = false; let missing = false;
let canEditTitle = false; let canEditTitle = false;
let modGuiButtonVisible = false; let modGuiButtonVisible = false;
@@ -439,7 +438,6 @@ export const MainPage =
} else if (pedalboardItem.isSyntheticItem()) { } else if (pedalboardItem.isSyntheticItem()) {
title = pedalboardItem.pluginName ?? "#error"; title = pedalboardItem.pluginName ?? "#error";
author = ""; author = "";
presetsUri = "";
infoPluginUri = ""; infoPluginUri = "";
} }
else { else {
@@ -456,7 +454,6 @@ export const MainPage =
title = uiPlugin.name; title = uiPlugin.name;
author = uiPlugin.author_name; author = uiPlugin.author_name;
} }
presetsUri = uiPlugin.uri;
// if (uiPlugin.description.length > 20) { // if (uiPlugin.description.length > 20) {
// } // }
infoPluginUri = uiPlugin.uri; infoPluginUri = uiPlugin.uri;
@@ -544,10 +541,6 @@ export const MainPage =
<div style={{ flex: "0 0 auto", verticalAlign: "center" }}> <div style={{ flex: "0 0 auto", verticalAlign: "center" }}>
<PluginInfoDialog plugin_uri={infoPluginUri} /> <PluginInfoDialog plugin_uri={infoPluginUri} />
</div> </div>
<div style={{ flex: "0 0 auto" }}>
<PluginPresetSelector pluginUri={presetsUri} instanceId={pedalboardItem?.instanceId ?? 0}
/>
</div>
{modGuiButtonVisible && ( {modGuiButtonVisible && (
<div style={{ flex: "0 0 auto" }}> <div style={{ flex: "0 0 auto" }}>
@@ -634,6 +627,7 @@ export const MainPage =
canShowModUi = this.canShowModUi(pedalboardItem); canShowModUi = this.canShowModUi(pedalboardItem);
canDelete = pedalboard.canDeleteItem(pedalboardItem.instanceId); canDelete = pedalboard.canDeleteItem(pedalboardItem.instanceId);
instanceId = pedalboardItem.instanceId; instanceId = pedalboardItem.instanceId;
pluginUri = pedalboardItem.uri;
if (pedalboardItem.isEmpty()) { if (pedalboardItem.isEmpty()) {
canInsert = true; canInsert = true;
canAppend = true; canAppend = true;
@@ -699,7 +693,12 @@ export const MainPage =
</div> </div>
{this.props.enableStructureEditing && ( {this.props.enableStructureEditing && (
<div style={{ flex: "0 0 auto", display: "flex", flexFlow: "row nowrap", alignItems: "center" }}> <div style={{ flex: "0 0 auto", display: "flex", flexFlow: "row nowrap", alignItems: "center" }}>
<div style={{ flex: "0 0 auto", display: (canInsert || canAppend) ? "block" : "none", paddingRight: 8 }}> <div style={{ flex: "0 0 auto" }}>
<PluginPresetSelector pedalboardItem={pedalboardItem} instanceId={pedalboardItem?.instanceId ?? 0}
/>
</div>
<div style={{ flex: "0 0 auto", display: (canInsert || canAppend) ? "block" : "none" }}>
<IconButtonEx tooltip="Add pedal slot" onClick={(e) => { this.onAddClick(e) }} size="large"> <IconButtonEx tooltip="Add pedal slot" onClick={(e) => { this.onAddClick(e) }} size="large">
<AddIcon style={{ height: 24, width: 24, fill: this.props.theme.palette.text.primary, opacity: 0.6 }} /> <AddIcon style={{ height: 24, width: 24, fill: this.props.theme.palette.text.primary, opacity: 0.6 }} />
</IconButtonEx> </IconButtonEx>
@@ -718,7 +717,7 @@ export const MainPage =
{canAppend && (<MenuItem onClick={() => this.onAppendSplit(instanceId)}>Append split</MenuItem>)} {canAppend && (<MenuItem onClick={() => this.onAppendSplit(instanceId)}>Append split</MenuItem>)}
</Menu> </Menu>
</div> </div>
<div style={{ flex: "0 0 auto", display: canDelete ? "block" : "none", paddingRight: 8 }}> <div style={{ flex: "0 0 auto", display: canDelete ? "block" : "none", }}>
<IconButtonEx tooltip="Delete pedal" <IconButtonEx tooltip="Delete pedal"
onClick={() => { this.onDeletePedal(pedalboardItem?.instanceId ?? -1) }} onClick={() => { this.onDeletePedal(pedalboardItem?.instanceId ?? -1) }}
size="large"> size="large">
+6
View File
@@ -59,6 +59,9 @@ export class ControlValue implements Deserializable<ControlValue> {
} }
export class PedalboardItem implements Deserializable<PedalboardItem> { export class PedalboardItem implements Deserializable<PedalboardItem> {
clone() {
return new PedalboardItem().deserialize(this);
}
deserializePedalboardItem(input: any): PedalboardItem { deserializePedalboardItem(input: any): PedalboardItem {
this.instanceId = input.instanceId ?? -1; this.instanceId = input.instanceId ?? -1;
this.title = input.title ?? ""; this.title = input.title ?? "";
@@ -753,11 +756,14 @@ export class Pedalboard implements Deserializable<Pedalboard> {
replaceItem(instanceId: number, newItem: PedalboardItem) replaceItem(instanceId: number, newItem: PedalboardItem)
{ {
newItem.instanceId = ++this.nextInstanceId;
let result = this._replaceItem(this.items,instanceId,newItem); let result = this._replaceItem(this.items,instanceId,newItem);
if (!result) if (!result)
{ {
throw new PiPedalArgumentError("instanceId not found."); throw new PiPedalArgumentError("instanceId not found.");
} }
return newItem.instanceId;
} }
private _addItem(items: PedalboardItem[], newItem: PedalboardItem, instanceId: number, append: boolean) private _addItem(items: PedalboardItem[], newItem: PedalboardItem, instanceId: number, append: boolean)
{ {
+157 -82
View File
@@ -23,29 +23,30 @@ import { PiPedalModel, PiPedalModelFactory, PluginPresetsChangedHandle } from '.
import { Theme } from '@mui/material/styles'; import { Theme } from '@mui/material/styles';
import WithStyles from './WithStyles'; import WithStyles from './WithStyles';
import { withStyles } from "tss-react/mui"; import { withStyles } from "tss-react/mui";
import {createStyles} from './WithStyles'; import { createStyles } from './WithStyles';
import PluginPresetsDialog from './PluginPresetsDialog'; import PluginPresetsDialog from './PluginPresetsDialog';
import Menu from '@mui/material/Menu'; import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem'; import MenuItem from '@mui/material/MenuItem';
import Fade from '@mui/material/Fade'; import Fade from '@mui/material/Fade';
import RenameDialog from './RenameDialog' import RenameDialog from './RenameDialog'
import {PluginUiPresets} from './PluginPreset'; import { PluginUiPresets } from './PluginPreset';
import Divider from "@mui/material/Divider"; import Divider from "@mui/material/Divider";
import PluginPresetsIcon from "./svg/ic_pluginpreset.svg?react"; import PluginPresetsIcon from "./svg/ic_pluginpreset.svg?react";
import PluginPresetIcon from "./svg/ic_pluginpreset2.svg?react"; import PluginPresetIcon from "./svg/ic_pluginpreset2.svg?react";
import { Pedalboard, PedalboardItem } from './Pedalboard';
interface PluginPresetSelectorProps extends WithStyles<typeof styles> { interface PluginPresetSelectorProps extends WithStyles<typeof styles> {
pluginUri?: string; pedalboardItem: PedalboardItem | null;
instanceId: number; instanceId: number;
} }
interface PluginPresetSelectorState { interface PluginPresetSelectorState {
presets : PluginUiPresets; presets: PluginUiPresets;
enabled: boolean; //enabled: boolean;
presetChanged: boolean; presetChanged: boolean;
showPresetsDialog: boolean; showPresetsDialog: boolean;
showEditPresetsDialog: boolean; showEditPresetsDialog: boolean;
@@ -57,6 +58,10 @@ interface PluginPresetSelectorState {
renameDialogOnOk?: (name: string) => void; renameDialogOnOk?: (name: string) => void;
saveAsName: string; saveAsName: string;
isVisible: boolean;
hasPresets: boolean;
isPastePluginEnabled: boolean;
} }
@@ -65,15 +70,17 @@ const styles = (theme: Theme) => createStyles({
width: 24, height: 24, marginRight: "4px", opacity: 0.6 width: 24, height: 24, marginRight: "4px", opacity: 0.6
}, },
pluginIcon: { pluginIcon: {
width: 24, height: 24, opacity: 0.6,fill: theme.palette.text.primary width: 24, height: 24, opacity: 0.6, fill: theme.palette.text.primary
}, },
pluginMenuIcon: { pluginMenuIcon: {
width: 24, height: 24, opacity: 0.6,fill: theme.palette.text.primary,marginRight: 4 width: 24, height: 24, opacity: 0.6, fill: theme.palette.text.primary, marginRight: 4
} }
}); });
let pluginClipboardContents: PedalboardItem | null = null;
const PluginPresetSelector = const PluginPresetSelector =
withStyles( withStyles(
class extends Component<PluginPresetSelectorProps, PluginPresetSelectorState> { class extends Component<PluginPresetSelectorProps, PluginPresetSelectorState> {
@@ -85,7 +92,7 @@ const PluginPresetSelector =
this.model = PiPedalModelFactory.getInstance(); this.model = PiPedalModelFactory.getInstance();
this.state = { this.state = {
presets: new PluginUiPresets(), presets: new PluginUiPresets(),
enabled: false, isVisible: this.isVisible(props.pedalboardItem),
presetChanged: false, presetChanged: false,
showPresetsDialog: false, showPresetsDialog: false,
showEditPresetsDialog: false, showEditPresetsDialog: false,
@@ -94,26 +101,44 @@ const PluginPresetSelector =
renameDialogDefaultName: "", renameDialogDefaultName: "",
renameDialogActionName: "", renameDialogActionName: "",
renameDialogOnOk: undefined, renameDialogOnOk: undefined,
saveAsName: "" saveAsName: "",
hasPresets: this.hasPresets(this.props.pedalboardItem),
isPastePluginEnabled: pluginClipboardContents !== null
}; };
this.handleDialogClose = this.handleDialogClose.bind(this); this.handleDialogClose = this.handleDialogClose.bind(this);
this.handlePresetsMenuClose = this.handlePresetsMenuClose.bind(this); this.handlePresetsMenuClose = this.handlePresetsMenuClose.bind(this);
} }
handleLoadPluginPreset(instanceId: number) hasPresets(pedalboardItem: PedalboardItem | null): boolean {
{ if (pedalboardItem === null) return false;
this.handlePresetsMenuClose(); if (!pedalboardItem.uri) return false;
this.model.loadPluginPreset(this.props.instanceId,instanceId); if (pedalboardItem.isStart() || pedalboardItem.isEnd()
let presetName = this.getPresetName(instanceId); || pedalboardItem.isEmpty() || pedalboardItem.isSplit()) {
this.setState({saveAsName: presetName}); return false;
}
return true;
} }
getPresetName(instanceId: number) isVisible(pedalboardItem: PedalboardItem | null): boolean {
{ if (pedalboardItem === null) return false;
for (let preset of this.state.presets.presets) if (!pedalboardItem.uri) return false;
{ if (pedalboardItem.isStart() || pedalboardItem.isEnd()
if (preset.instanceId === instanceId) || pedalboardItem.isSplit()) {
{ return false;
}
return true;
}
handleLoadPluginPreset(instanceId: number) {
this.handlePresetsMenuClose();
this.model.loadPluginPreset(this.props.instanceId, instanceId);
let presetName = this.getPresetName(instanceId);
this.setState({ saveAsName: presetName });
}
getPresetName(instanceId: number) {
for (let preset of this.state.presets.presets) {
if (preset.instanceId === instanceId) {
return preset.label; return preset.label;
} }
} }
@@ -135,8 +160,8 @@ const PluginPresetSelector =
this.renameDialogOpen(name, "Save As") this.renameDialogOpen(name, "Save As")
.then((newName) => { .then((newName) => {
this.setState({saveAsName: newName}); this.setState({ saveAsName: newName });
return this.model.saveCurrentPluginPresetAs(this.props.instanceId,newName); return this.model.saveCurrentPluginPresetAs(this.props.instanceId, newName);
}) })
.then((newInstanceId) => { .then((newInstanceId) => {
// s'fine. dealt with by updates, but we do need error handling. // s'fine. dealt with by updates, but we do need error handling.
@@ -170,60 +195,62 @@ const PluginPresetSelector =
return result; return result;
} }
loadPresets():void { loadPresets(): void {
if (!this.currentUri) return; if (this.props.pedalboardItem === null) return;
let captureUri: string = this.currentUri; if (!this.hasPresets(this.props.pedalboardItem)) return;
let captureUri: string = this.props.pedalboardItem.uri;
this.model.getPluginPresets(captureUri) this.model.getPluginPresets(captureUri)
.then((presets: PluginUiPresets) => { .then((presets: PluginUiPresets) => {
if (captureUri === this.currentUri) if (captureUri === this.props.pedalboardItem?.uri) {
{ this.setState({ presets: presets });
this.setState({presets: presets}); }
} })
}) .catch(error => {
.catch(error => { if (captureUri === this.props.pedalboardItem?.uri) {
if (captureUri === this.currentUri) this.setState({ presets: new PluginUiPresets() });
{ }
this.setState({presets: new PluginUiPresets()}); });
}
});
} }
onPluginPresetsChanged(pluginUri: string): void { onPluginPresetsChanged(pluginUri: string): void {
if (pluginUri === this.props.pluginUri) if (pluginUri === this.props.pedalboardItem?.uri) {
{
this.loadPresets(); this.loadPresets();
} }
} }
_pluginPresetsChangedHandle?: PluginPresetsChangedHandle; _pluginPresetsChangedHandle?: PluginPresetsChangedHandle;
componentDidMount() componentDidMount() {
{
this._pluginPresetsChangedHandle = this.model.addPluginPresetsChangedListener( this._pluginPresetsChangedHandle = this.model.addPluginPresetsChangedListener(
(pluginUri: string) => { (pluginUri: string) => {
this.onPluginPresetsChanged(pluginUri); this.onPluginPresetsChanged(pluginUri);
} }
); );
} }
componentWillUnmount() componentWillUnmount() {
{ if (this._pluginPresetsChangedHandle) {
if (this._pluginPresetsChangedHandle)
{
this.model.removePluginPresetsChangedListener(this._pluginPresetsChangedHandle); this.model.removePluginPresetsChangedListener(this._pluginPresetsChangedHandle);
this._pluginPresetsChangedHandle = undefined; this._pluginPresetsChangedHandle = undefined;
} }
} }
currentUri?: string = ""; currentPedalboard?: PedalboardItem;
componentDidUpdate() componentDidUpdate(
prevProps: Readonly<PluginPresetSelectorProps>,
prevState: Readonly<PluginPresetSelectorState>,
snapshot?: any): void
{ {
if (this.currentUri !== this.props.pluginUri) if (this.props.pedalboardItem !== prevProps.pedalboardItem) {
{ this.setState({
this.currentUri = this.props.pluginUri; isVisible: this.isVisible(this.props.pedalboardItem),
if (!this.props.pluginUri) hasPresets: this.hasPresets(this.props.pedalboardItem) });
{ if (this.props.pedalboardItem) {
this.setState({presets: new PluginUiPresets()}); if (this.props.pedalboardItem.isEmpty())
} else { {
this.loadPresets(); this.setState({ presets: new PluginUiPresets() });
} else
{
this.loadPresets();
}
} }
} }
} }
@@ -244,8 +271,38 @@ const PluginPresetSelector =
} }
isMenuCopyPluginEnabled(): boolean {
return this.state.hasPresets;
}
handleMenuCopyPlugin(): void {
this.handlePresetsMenuClose();
let pedalboard: Pedalboard = this.model.pedalboard.get();;
handleMenuEditPluginPresets(): void {
let pedalboardItem: PedalboardItem | null = pedalboard.tryGetItem(this.props.instanceId);
if (pedalboardItem === null) {
pluginClipboardContents = null;
this.setState({ isPastePluginEnabled: false });
return;
}
pluginClipboardContents = pedalboardItem.clone();
this.setState({ isPastePluginEnabled: true });
}
private isMenuPastePluginEnabled(): boolean {
return this.state.isPastePluginEnabled;
}
private handleMenuPastePlugin(): void {
this.handlePresetsMenuClose();
if (pluginClipboardContents) {
this.model.replacePedalboarditem(this.props.instanceId, pluginClipboardContents.clone());
}
}
private handleMenuEditPluginPresets(): void {
this.handlePresetsMenuClose(); this.handlePresetsMenuClose();
this.showEditPluginPresetsDialog(true); this.showEditPluginPresetsDialog(true);
} }
@@ -282,21 +339,50 @@ const PluginPresetSelector =
//this.model.loadPreset(event.target.value as number); //this.model.loadPreset(event.target.value as number);
} }
buildMenuItems() {
let result : React.ReactNode[] = [];
if (this.state.hasPresets) {
result.push((<MenuItem key="menuSaveAs" onClick={(e) => this.handlePluginPresetsMenuSaveAs(e)}>Save plugin preset...</MenuItem>));
result.push((<MenuItem key="menuEdit" onClick={(e) => this.handleMenuEditPluginPresets()}>Manage plugin presets...</MenuItem>));
result.push((<Divider key="divider1" />));
}
let hasPresets = false;
for (let preset of this.state.presets.presets) {
result.push((
<MenuItem key={preset.instanceId}
onClick={(e) => this.handleLoadPluginPreset(preset.instanceId)}
>
<PluginPresetIcon className={this.props.classes?.pluginMenuIcon ?? ""} />
{preset.label}</MenuItem>
));
hasPresets = true;
}
if (hasPresets) {
result.push((<Divider key="divider2" />));
}
result.push(
<MenuItem key="menuCopy" disabled={!this.isMenuCopyPluginEnabled()} onClick={(e) => this.handleMenuCopyPlugin()}>Copy plugin</MenuItem>
);
result.push(
<MenuItem key="menuPaste" disabled={!this.isMenuPastePluginEnabled()} style={{ opacity: this.isMenuPastePluginEnabled() ? 1 : 0.4 }} onClick={(e) => this.handleMenuPastePlugin()}>Paste plugin</MenuItem>
);
return result;
}
render() { render() {
const classes = withStyles.getClasses(this.props); const classes = withStyles.getClasses(this.props);
//const classes = withStyles.getClasses(this.props); //const classes = withStyles.getClasses(this.props);
//const classes = withStyles.getClasses(this.props); //const classes = withStyles.getClasses(this.props);
if ((!this.props.pluginUri)) if (!this.state.isVisible) {
{ return (<div />);
return (<div/>);
} }
return ( return (
<div > <div >
<IconButtonEx <IconButtonEx
tooltip="Plugin presets" tooltip="Plugin presets"
onClick={(e)=> this.handlePresetMenuClick(e)} size="large"> onClick={(e) => this.handlePresetMenuClick(e)} size="large">
<PluginPresetsIcon className={classes.pluginIcon}/> <PluginPresetsIcon className={classes.pluginIcon} />
</IconButtonEx> </IconButtonEx>
<Menu <Menu
id="edit-plugin-presets-menu" id="edit-plugin-presets-menu"
@@ -306,34 +392,23 @@ const PluginPresetSelector =
TransitionComponent={Fade} TransitionComponent={Fade}
MenuListProps={ MenuListProps={
{ {
style: {minWidth: 180} style: { minWidth: 180 }
} }
} }
> >
<MenuItem onClick={(e) => this.handlePluginPresetsMenuSaveAs(e)}>Save plugin preset...</MenuItem>
<MenuItem onClick={(e) => this.handleMenuEditPluginPresets()}>Manage plugin presets...</MenuItem>
{ this.state.presets.presets.length !== 0 &&
(<Divider/>)
}
{ {
this.state.presets.presets.map((preset) => { this.buildMenuItems()
return (<MenuItem key={preset.instanceId}
onClick={(e) => this.handleLoadPluginPreset(preset.instanceId)}
>
<PluginPresetIcon className={classes.pluginMenuIcon}/>
{preset.label}</MenuItem>);
})
} }
</Menu> </Menu>
<PluginPresetsDialog {this.state.hasPresets && this.state.showPresetsDialog && (
<PluginPresetsDialog
instanceId={this.props.instanceId} instanceId={this.props.instanceId}
presets={this.state.presets} presets={this.state.presets}
show={this.state.showPresetsDialog} show={this.state.showPresetsDialog}
isEditDialog={this.state.showEditPresetsDialog} isEditDialog={this.state.showEditPresetsDialog}
onDialogClose={() => this.handleDialogClose()} /> onDialogClose={() => this.handleDialogClose()} />
)}
<RenameDialog open={this.state.renameDialogOpen} <RenameDialog open={this.state.renameDialogOpen}
title="Rename" title="Rename"
defaultName={this.state.renameDialogDefaultName} defaultName={this.state.renameDialogDefaultName}
@@ -346,6 +421,6 @@ const PluginPresetSelector =
} }
}, },
styles); styles);
export default PluginPresetSelector; export default PluginPresetSelector;