Copy prests between banks; preset multi-select.

This commit is contained in:
Robin E. R. Davies
2025-09-21 15:08:55 -04:00
parent b25e90df7b
commit 6105dff931
16 changed files with 672 additions and 208 deletions
+5 -1
View File
@@ -228,7 +228,11 @@ namespace pipedal
{ {
newSelection = presets_[i]->instanceId(); newSelection = presets_[i]->instanceId();
} }
else if (presets_.size() > 1) else if (i == presets_.size() && i != 0) {
newSelection = presets_[i-1]->instanceId();
}
else if (presets_.size() >= 1)
{ {
newSelection = presets_[0]->instanceId(); newSelection = presets_[0]->instanceId();
} }
+10 -5
View File
@@ -1174,12 +1174,12 @@ int64_t PiPedalModel::DeleteBank(int64_t clientId, int64_t instanceId)
return newSelection; return newSelection;
} }
int64_t PiPedalModel::DeletePreset(int64_t clientId, int64_t instanceId) int64_t PiPedalModel::DeletePresets(int64_t clientId, const std::vector<int64_t> &presetInstanceIds)
{ {
std::lock_guard<std::recursive_mutex> guard{mutex}; std::lock_guard<std::recursive_mutex> guard{mutex};
int64_t oldSelection = storage.GetCurrentPresetId(); int64_t oldSelection = storage.GetCurrentPresetId();
int64_t newSelection = storage.DeletePreset(instanceId); int64_t newSelection = storage.DeletePresets(presetInstanceIds);
this->FirePresetsChanged(clientId); // fire now. this->FirePresetsChanged(clientId); // fire BEFORE we load a new preset.
if (oldSelection != newSelection) if (oldSelection != newSelection)
{ {
this->LoadPreset( this->LoadPreset(
@@ -3270,10 +3270,15 @@ int64_t PiPedalModel::ImportPresetsFromBank(int64_t bankInstanceId, const std::v
{ {
std::lock_guard<std::recursive_mutex> lock(mutex); std::lock_guard<std::recursive_mutex> lock(mutex);
uint64_t lastAdded = storage.ImportPresetsFromBank(bankInstanceId, presets); uint64_t lastAdded = storage.ImportPresetsFromBank(bankInstanceId, presets);
if (lastAdded != -1) {
}
FirePresetsChanged(-1); FirePresetsChanged(-1);
return lastAdded; return lastAdded;
} }
int64_t PiPedalModel::CopyPresetsToBank(int64_t bankInstanceId, const std::vector<int64_t> &presets)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
uint64_t lastAdded = storage.CopyPresetsToBank(bankInstanceId, presets);
return lastAdded;
}
+2 -1
View File
@@ -385,7 +385,7 @@ namespace pipedal
void LoadPreset(int64_t clientId, int64_t instanceId); void LoadPreset(int64_t clientId, int64_t instanceId);
bool UpdatePresets(int64_t clientId, const PresetIndex &presets); bool UpdatePresets(int64_t clientId, const PresetIndex &presets);
void UpdatePluginPresets(const PluginUiPresets &pluginPresets); void UpdatePluginPresets(const PluginUiPresets &pluginPresets);
int64_t DeletePreset(int64_t clientId, int64_t instanceId); int64_t DeletePresets(int64_t clientId, const std::vector<int64_t> &presetInstanceIds);
bool RenamePreset(int64_t clientId, int64_t instanceId, const std::string &name); bool RenamePreset(int64_t clientId, int64_t instanceId, const std::string &name);
int64_t CopyPreset(int64_t clientId, int64_t fromIndex, int64_t toIndex); int64_t CopyPreset(int64_t clientId, int64_t fromIndex, int64_t toIndex);
uint64_t CopyPluginPreset(const std::string &pluginUri, uint64_t presetId); uint64_t CopyPluginPreset(const std::string &pluginUri, uint64_t presetId);
@@ -496,6 +496,7 @@ namespace pipedal
std::vector<PresetIndexEntry> RequestBankPresets(int64_t bankInstanceId); std::vector<PresetIndexEntry> RequestBankPresets(int64_t bankInstanceId);
int64_t ImportPresetsFromBank(int64_t bankInstanceId, const std::vector<int64_t> &presets); int64_t ImportPresetsFromBank(int64_t bankInstanceId, const std::vector<int64_t> &presets);
int64_t CopyPresetsToBank(int64_t bankInstanceId, const std::vector<int64_t> &presets);
}; };
} // namespace pipedal. } // namespace pipedal.
+22 -5
View File
@@ -46,6 +46,17 @@
using namespace std; using namespace std;
using namespace pipedal; using namespace pipedal;
class CopyPresetsToBankBody {
public:
int64_t bankInstanceId_;
std::vector<int64_t> presets_;
DECLARE_JSON_MAP(CopyPresetsToBankBody);
};
JSON_MAP_BEGIN(CopyPresetsToBankBody)
JSON_MAP_REFERENCE(CopyPresetsToBankBody, bankInstanceId)
JSON_MAP_REFERENCE(CopyPresetsToBankBody, presets)
JSON_MAP_END()
class ImportPresetsFromBankBody { class ImportPresetsFromBankBody {
public: public:
@@ -1470,12 +1481,12 @@ public:
model.RequestShutdown(true); model.RequestShutdown(true);
this->Reply(replyTo, "restart"); this->Reply(replyTo, "restart");
} }
else if (message == "deletePresetItem") else if (message == "deletePresetItems")
{ {
int64_t instanceId = 0; std::vector<int64_t> items;
pReader->read(&instanceId); pReader->read(&items);
int64_t result = model.DeletePreset(this->clientId, instanceId); int64_t result = model.DeletePresets(this->clientId, items);
this->Reply(replyTo, "deletePresetItem", result); this->Reply(replyTo, "deletePresetItems", result);
} }
else if (message == "deleteBankItem") else if (message == "deleteBankItem")
{ {
@@ -1831,6 +1842,12 @@ public:
auto result = this->model.ImportPresetsFromBank(args.bankInstanceId_, args.presets_); auto result = this->model.ImportPresetsFromBank(args.bankInstanceId_, args.presets_);
this->Reply(replyTo,"importPresetsFromBank",result); this->Reply(replyTo,"importPresetsFromBank",result);
} }
else if (message == "copyPresetsToBank") {
CopyPresetsToBankBody args;
pReader->read(&args);
auto result = this->model.CopyPresetsToBank(args.bankInstanceId_, args.presets_);
this->Reply(replyTo,"copyPresetsToBank",result);
}
else else
{ {
Lv2Log::error("Unknown message received: %s", message.c_str()); Lv2Log::error("Unknown message received: %s", message.c_str());
+34 -2
View File
@@ -764,6 +764,35 @@ int64_t Storage::ImportPresetsFromBank(int64_t bankInstanceId, const std::vector
SaveCurrentBank(); SaveCurrentBank();
return lastPresetId; return lastPresetId;
} }
int64_t Storage::CopyPresetsToBank(int64_t bankInstanceId, const std::vector<int64_t> &presets)
{
if (bankIndex.selectedBank() == bankInstanceId) {
throw std::runtime_error("Can't copy to self.");
}
auto indexEntry = this->bankIndex.getBankIndexEntry(bankInstanceId);
BankFile bankFile;
LoadBankFile(indexEntry.name(),&bankFile);
std::set<int64_t> presetsSet { presets.begin(), presets.end()};
std::set<std::string> existingNames;
for (auto&preset: bankFile.presets()) {
existingNames.insert(preset->preset().name());
}
for (auto &presetEntry: this->currentBank.presets()) {
if (presetsSet.contains(presetEntry->instanceId())) {
std::string uniqueName = makeUniqueName(presetEntry->preset().name(),existingNames);
existingNames.insert(uniqueName);
Pedalboard t = presetEntry->preset();
t.name(uniqueName);
bankFile.addPreset(t);
}
}
SaveBankFile(indexEntry.name(),bankFile);
return -1;
}
std::vector<PresetIndexEntry> Storage::RequestBankPresets(int64_t bankInstanceId) std::vector<PresetIndexEntry> Storage::RequestBankPresets(int64_t bankInstanceId)
{ {
@@ -852,9 +881,12 @@ Pedalboard Storage::GetPreset(int64_t instanceId) const
throw PiPedalException("Not found."); throw PiPedalException("Not found.");
} }
int64_t Storage::DeletePreset(int64_t presetId) int64_t Storage::DeletePresets(const std::vector<int64_t> &presetInstanceIds)
{ {
int64_t newSelection = currentBank.deletePreset(presetId); int64_t newSelection = currentBank.selectedPreset();
for (auto presetId: presetInstanceIds) {
newSelection = currentBank.deletePreset(presetId);
}
SaveCurrentBank(); SaveCurrentBank();
return newSelection; return newSelection;
} }
+2 -1
View File
@@ -162,7 +162,7 @@ public:
bool LoadPreset(int64_t presetId); bool LoadPreset(int64_t presetId);
int64_t DeletePreset(int64_t presetId); int64_t DeletePresets(const std::vector<int64_t>& presetInstanceIds);
bool RenamePreset(int64_t presetId, const std::string&name); bool RenamePreset(int64_t presetId, const std::string&name);
int64_t CopyPreset(int64_t fromId, int64_t toId = -1); int64_t CopyPreset(int64_t fromId, int64_t toId = -1);
int64_t CreateNewPreset(); int64_t CreateNewPreset();
@@ -271,6 +271,7 @@ public:
std::vector<PresetIndexEntry> RequestBankPresets(int64_t bankInstanceId); std::vector<PresetIndexEntry> RequestBankPresets(int64_t bankInstanceId);
int64_t ImportPresetsFromBank(int64_t bankInstanceId, const std::vector<int64_t> &presets); int64_t ImportPresetsFromBank(int64_t bankInstanceId, const std::vector<int64_t> &presets);
int64_t CopyPresetsToBank(int64_t bankInstanceId, const std::vector<int64_t> &presets);
}; };
-1
View File
@@ -1,4 +1,3 @@
Did we fix Toob Tremolo stereo harmonic?
ls ls
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sudo /bin/bash
rm -rf /etc/pipedal/react
mkdir -p /etc/pipedal/react
cp -r dist/* /etc/pipedal/react/
+17
View File
@@ -39,6 +39,12 @@ declare module '@mui/material/styles' {
mainBackground?: React.CSSProperties['color']; mainBackground?: React.CSSProperties['color'];
toolbarColor?: React.CSSProperties['color']; toolbarColor?: React.CSSProperties['color'];
} }
interface Palette {
actionBar: Palette['primary'];
}
interface PaletteOptions {
actionBar: PaletteOptions['primary'];
}
} }
@@ -143,7 +149,12 @@ const theme = createTheme(
}, },
secondary: { secondary: {
main: "#FF6060" main: "#FF6060"
},
actionBar: {
main: '#130b22ff',
contrastText: '#FFFFFF'
} }
}, },
mainBackground: "#222", mainBackground: "#222",
toolbarColor: '#222' toolbarColor: '#222'
@@ -209,8 +220,14 @@ const theme = createTheme(
}, },
secondary: { secondary: {
main: "#FF6060" main: "#FF6060"
},
actionBar: {
main: '#130b22ff',
contrastText: '#FFFFFF'
} }
}, },
mainBackground: "#FFFFFF", mainBackground: "#FFFFFF",
toolbarColor: '#FFFFFF' toolbarColor: '#FFFFFF'
@@ -0,0 +1,180 @@
// 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 Toolbar from "@mui/material/Toolbar";
import IconButtonEx from "./IconButtonEx";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import Typography from "@mui/material/Typography";
import Select from '@mui/material/Select';
import Button from '@mui/material/Button';
import InputLabel from '@mui/material/InputLabel';
import DialogEx from './DialogEx';
import DialogTitle from '@mui/material/DialogTitle';
import MenuItem from '@mui/material/MenuItem';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
import { PiPedalModel } from './PiPedalModel';
import { BankIndexEntry } from './Banks';
export interface CopyPresetsToBankDialogProps {
open: boolean,
onOk: (bankInstanceId: number) => void,
onClose: () => void
};
export interface CopyPresetsToBankDialogState {
selectedBank: number;
banks: BankIndexEntry[];
fullScreen: boolean;
};
function setSavedDefaultBankSelection(bankInstanceId: number) {
localStorage.setItem('pipedal_copyTo_defaultBankSelection', bankInstanceId.toString());
}
function getSavedDefaultBankSelection() {
let defaultValue = localStorage.getItem('pipedal_copyTo_defaultBankSelection');
if (defaultValue) {
return parseInt(defaultValue);
}
return -1;
}
export default class CopyPresetsToBankDialog extends ResizeResponsiveComponent<CopyPresetsToBankDialogProps, CopyPresetsToBankDialogState> {
model: PiPedalModel;
constructor(props: CopyPresetsToBankDialogProps) {
super(props);
this.model = PiPedalModel.getInstance();
let banks = this.getBanks();
let selectedBank = this.getDefaultBankSelection(banks);
this.state = {
banks: banks,
selectedBank: selectedBank,
fullScreen: false,
};
}
private getBanks() {
let bankIndex = this.model.banks.get();
let result: BankIndexEntry[] = [];
for (let entry of bankIndex.entries) {
if (entry.instanceId === bankIndex.selectedBank) continue;
result.push(entry);
}
return result;
}
private getDefaultBankSelection(banks: BankIndexEntry[]) {
if (banks.length === 0) return -1;
let defaultSelection = getSavedDefaultBankSelection();
for (let bank of banks) {
if (bank.instanceId === defaultSelection) return defaultSelection;
}
return banks[0].instanceId;
}
componentDidMount() {
super.componentDidMount();
}
componentWillUnmount() {
super.componentWillUnmount();
}
componentDidUpdate() {
}
handleBankChanged(bankId: number) {
setSavedDefaultBankSelection(bankId);
this.setState({ selectedBank: bankId });
}
handleOk() {
this.props.onOk(this.state.selectedBank);
}
render() {
let props = this.props;
let { open, onClose } = props;
const handleClose = () => {
onClose();
};
return (
<DialogEx tag="importPreset" open={open} fullWidth maxWidth="xs" onClose={handleClose} aria-labelledby="Rename-dialog-title"
fullScreen={this.state.fullScreen}
style={{ userSelect: "none" }}
onEnterKey={() => { }}
>
<div style={{ display: "flex", flexFlow: "column nowrap", height: this.state.fullScreen ? "100vh" : undefined }}>
{!this.state.fullScreen && (
<DialogTitle style={{ paddingTop: 8, paddingBottom: 0 }}>
<Toolbar style={{ padding: 0 }}>
<IconButtonEx
tooltip="Close"
edge="start"
color="inherit"
aria-label="cancel"
style={{ opacity: 0.6 }}
onClick={() => { onClose(); }}
>
<ArrowBackIcon style={{ width: 24, height: 24 }} />
</IconButtonEx>
<Typography noWrap component="div" sx={{ flexGrow: 1 }}>
Copy Presets to Bank
</Typography>
</Toolbar>
</DialogTitle>
)}
<DialogContent style={{ flex: "0 0 auto", paddingTop: 8, paddingBottom: 8, }}>
<InputLabel style={{ fontSize: "0.75rem", fontWeight: 400, marginTop: 0 }}>Bank</InputLabel>
<Select variant="standard" fullWidth value={this.state.selectedBank} style={{ marginBottom: 8 }}
onChange={(e) => { this.handleBankChanged(e.target.value as number); }}>
{this.state.banks.map((bankEntry) => {
return (
<MenuItem key={bankEntry.instanceId} value={bankEntry.instanceId}
selected={bankEntry.instanceId === this.state.selectedBank}
>
{bankEntry.name}
</MenuItem>
);
})}
</Select>
</DialogContent>
<DialogActions style={{ flex: "0 0 auto" }}>
<Button onClick={handleClose} variant="dialogSecondary" >
Cancel
</Button>
<Button
onClick={() => { this.handleOk(); }} variant="dialogPrimary"
disabled={false}
>
OK
</Button>
</DialogActions>
</div>
</DialogEx>
);
}
}
+14 -4
View File
@@ -82,7 +82,7 @@ export interface DraggableGridProps
defaultSelectedIndex?: number; defaultSelectedIndex?: number;
onLongPress?: (itemIndex: number) => void; onLongPress?: (event: PointerEvent<HTMLDivElement>, itemIndex: number) => void;
canDrag?: boolean; canDrag?: boolean;
@@ -209,7 +209,7 @@ const DraggableGrid =
} }
bringSelectedItemIntoView() { bringSelectedItemIntoView() {
if (this.state.indexToSelect && this.state.indexToSelect >= 0) if (this.state.indexToSelect !== undefined && this.state.indexToSelect >= 0)
{ {
let grid = this.refGrid.current; let grid = this.refGrid.current;
if (grid) if (grid)
@@ -295,6 +295,7 @@ const DraggableGrid =
startClientX: number = 0; startClientX: number = 0;
startClientY: number = 0; startClientY: number = 0;
dragStarted: boolean = false; dragStarted: boolean = false;
dragThresholdWasExceeded: boolean = false;
savedIndex: string = ""; savedIndex: string = "";
savedBackground: string = ""; savedBackground: string = "";
lastPointerDown: number = 0; lastPointerDown: number = 0;
@@ -529,6 +530,11 @@ const DraggableGrid =
if (this.startIndex !== this.currentIndex) { if (this.startIndex !== this.currentIndex) {
this.props.moveElement(this.startIndex, this.currentIndex); this.props.moveElement(this.startIndex, this.currentIndex);
} }
if (!this.dragThresholdWasExceeded) {
if (this.props.onLongPress) {
this.props.onLongPress(e,this.startIndex);
}
}
} }
this.cancelDrag(); this.cancelDrag();
} }
@@ -693,6 +699,7 @@ const DraggableGrid =
///window.addEventListener("touchstart",this.handleTouchStart, {passive: false}); ///window.addEventListener("touchstart",this.handleTouchStart, {passive: false});
this.dragStarted = false; this.dragStarted = false;
this.dragThresholdWasExceeded = false;
this.startX = e.clientX; this.startX = e.clientX;
this.startY = e.clientY; this.startY = e.clientY;
this.startClientX = e.clientX; this.startClientX = e.clientX;
@@ -756,8 +763,11 @@ const DraggableGrid =
handlePointerMove(e: PointerEvent<HTMLDivElement>): boolean { handlePointerMove(e: PointerEvent<HTMLDivElement>): boolean {
if (this.isCapturedPointer(e)) { if (this.isCapturedPointer(e)) {
if (!this.dragStarted && this.dragThresholdExceeded(e)) { if (this.dragThresholdExceeded(e)) {
this.startDrag(); if (!this.dragStarted) {
this.startDrag();
}
this.dragThresholdWasExceeded = true;
} }
if (this.dragStarted) { if (this.dragStarted) {
this.lastX = e.clientX; this.lastX = e.clientX;
+69 -35
View File
@@ -158,44 +158,78 @@ export default class ImportPresetFromBankDialog extends ResizeResponsiveComponen
onEnterKey={() => { }} onEnterKey={() => { }}
> >
<div style={{ display: "flex", flexFlow: "column nowrap", height: this.state.fullScreen ? "100vh" : "80vh" }}> <div style={{ display: "flex", flexFlow: "column nowrap", height: this.state.fullScreen ? "100vh" : "80vh" }}>
{!this.state.fullScreen && ( {!this.state.fullScreen ? (
<DialogTitle style={{ paddingTop: 8, paddingBottom: 0 }}> <>
<Toolbar style={{ padding: 0 }}> <DialogTitle style={{ paddingTop: 8, paddingBottom: 0 }}>
<IconButtonEx <Toolbar style={{ padding: 0 }}>
tooltip="Close" <IconButtonEx
edge="start" tooltip="Close"
color="inherit" edge="start"
aria-label="cancel" color="inherit"
style={{ opacity: 0.6 }} aria-label="cancel"
onClick={() => { onClose(); }} style={{ opacity: 0.6 }}
> onClick={() => { onClose(); }}
<ArrowBackIcon style={{ width: 24, height: 24 }} /> >
</IconButtonEx> <ArrowBackIcon style={{ width: 24, height: 24 }} />
</IconButtonEx>
<Typography noWrap component="div" sx={{ flexGrow: 1 }}>
Import Presets from Bank
</Typography>
</Toolbar>
</DialogTitle>
<DialogContent style={{ flex: "0 0 auto", paddingTop: 8, paddingBottom: 8, }}>
<InputLabel style={{ fontSize: "0.75rem", fontWeight: 400, marginTop: 0 }}>Bank</InputLabel>
<Select variant="standard" fullWidth value={this.state.selectedBank} style={{ marginBottom: 8 }}
onChange={(e) => { this.handleBankChanged(e.target.value as number); }}>
{this.state.banks.map((bankEntry) => {
return (
<MenuItem key={bankEntry.instanceId} value={bankEntry.instanceId}
selected={bankEntry.instanceId === this.state.selectedBank}
>
{bankEntry.name}
</MenuItem>
);
})}
</Select>
</DialogContent>
</>
) : (
<div style={{ display: "flex", flexFlow: "row nowrap", alignItems: "top", flex: "0 0 auto", paddingTop: 8, paddingBottom: 8, paddingLeft: 16, paddingRight: 16 }}>
<IconButtonEx
tooltip="Close"
edge="start"
color="inherit"
aria-label="cancel"
style={{ opacity: 0.6 }}
onClick={() => { onClose(); }}
>
<ArrowBackIcon style={{ width: 24, height: 24 }} />
</IconButtonEx>
<div style={{ marginLeft: 24, display: "flex", flex: "0 0 auto", flexFlow: "column nowrap", justifyContent: "start" }}>
<InputLabel style={{ fontSize: "0.75rem", fontWeight: 400, marginTop: 0 }}>Bank</InputLabel>
<Select variant="standard" fullWidth value={this.state.selectedBank} style={{ marginBottom: 8 }}
onChange={(e) => { this.handleBankChanged(e.target.value as number); }}>
{this.state.banks.map((bankEntry) => {
return (
<MenuItem key={bankEntry.instanceId} value={bankEntry.instanceId}
selected={bankEntry.instanceId === this.state.selectedBank}
>
{bankEntry.name}
</MenuItem>
);
})}
</Select>
</div>
</div>
<Typography noWrap component="div" sx={{ flexGrow: 1 }}>
Import Presets from Bank
</Typography>
</Toolbar>
</DialogTitle>
)} )}
<DialogContent style={{ flex: "0 0 auto", paddingTop: 8, paddingBottom: 8, }}>
<InputLabel style={{ fontSize: "0.75rem", fontWeight: 400, marginTop: 0 }}>Bank</InputLabel>
<Select variant="standard" fullWidth value={this.state.selectedBank} style={{ marginBottom: 8 }}
onChange={(e) => { this.handleBankChanged(e.target.value as number); }}>
{this.state.banks.map((bankEntry) => {
return (
<MenuItem key={bankEntry.instanceId} value={bankEntry.instanceId}
selected={bankEntry.instanceId === this.state.selectedBank}
>
{bankEntry.name}
</MenuItem>
);
})}
</Select>
</DialogContent>
<Divider /> <Divider />
<List sx={{ flex: "1 1 auto", width: '100%', bgColor: 'background.paper', overflowX: 'auto' }}> <List sx={{ flex: "1 1 auto", width: '100%', bgColor: 'background.paper', overflowX: 'auto' }}>
{this.state.bankPresets?.map((presetEntry) => { {this.state.bankPresets?.map((presetEntry) => {
@@ -242,7 +276,7 @@ export default class ImportPresetFromBankDialog extends ResizeResponsiveComponen
</Button> </Button>
</DialogActions> </DialogActions>
</div> </div>
</DialogEx> </DialogEx >
); );
} }
} }
-2
View File
@@ -30,7 +30,6 @@ import { PiPedalModel, PiPedalModelFactory, PresetIndex } from './PiPedalModel';
import ResizeResponsiveComponent from './ResizeResponsiveComponent'; import ResizeResponsiveComponent from './ResizeResponsiveComponent';
import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import IconButtonEx from './IconButtonEx'; import IconButtonEx from './IconButtonEx';
import AppBar from '@mui/material/AppBar';
import SnapshotPanel from './SnapshotPanel'; import SnapshotPanel from './SnapshotPanel';
import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos'; import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos'; import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
@@ -250,7 +249,6 @@ export const PerformanceView =
render() { render() {
const classes = withStyles.getClasses(this.props); const classes = withStyles.getClasses(this.props);
let wrapSelects = this.state.wrapSelects;
let presets = this.state.presets; let presets = this.state.presets;
let banks = this.state.banks; let banks = this.state.banks;
let appBarIconSize: "large" | undefined = this.state.largeAppBar ? undefined : "large"; let appBarIconSize: "large" | undefined = this.state.largeAppBar ? undefined : "large";
+5 -2
View File
@@ -2192,8 +2192,8 @@ export class PiPedalModel //implements PiPedalModel
} }
deletePresetItem(instanceId: number): Promise<number> { deletePresetItems(instanceIds: Set<number>): Promise<number> {
return nullCast(this.webSocket).request<number>("deletePresetItem", instanceId); return nullCast(this.webSocket).request<number>("deletePresetItems", Array.from(instanceIds));
} }
deleteBankItem(instanceId: number): Promise<number> { deleteBankItem(instanceId: number): Promise<number> {
@@ -3549,6 +3549,9 @@ export class PiPedalModel //implements PiPedalModel
importPresetsFromBank(bankInstanceId: number, presets: number[]): Promise<number> { importPresetsFromBank(bankInstanceId: number, presets: number[]): Promise<number> {
return nullCast(this.webSocket).request<number>("importPresetsFromBank", {bankInstanceId: bankInstanceId, presets: presets}); return nullCast(this.webSocket).request<number>("importPresetsFromBank", {bankInstanceId: bankInstanceId, presets: presets});
} }
copyPresetsToBank(bankInstanceId: number, presets: number[]): Promise<number> {
return nullCast(this.webSocket).request<number>("copyPresetsToBank", {bankInstanceId: bankInstanceId, presets: presets});
}
}; };
let instance: PiPedalModel | undefined = undefined; let instance: PiPedalModel | undefined = undefined;
+242 -137
View File
@@ -17,8 +17,9 @@
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // 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. // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import React, { SyntheticEvent, Component } from 'react'; import React, { SyntheticEvent } from 'react';
import ImportPresetFromBankDialog from './ImportPresetFromBankDialog'; import ImportPresetFromBankDialog from './ImportPresetFromBankDialog';
import CopyPresetsToBankDialog from './CopyPresetsToBankDialog';
import Icon from '@mui/material/Icon'; import Icon from '@mui/material/Icon';
import Divider from '@mui/material/Divider'; import Divider from '@mui/material/Divider';
import { css } from '@emotion/react'; import { css } from '@emotion/react';
@@ -27,23 +28,22 @@ import IconButtonEx from './IconButtonEx';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import { PiPedalModel, PiPedalModelFactory, PresetIndexEntry, PresetIndex } from './PiPedalModel'; import { PiPedalModel, PiPedalModelFactory, PresetIndexEntry, PresetIndex } from './PiPedalModel';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import ButtonBase from "@mui/material/ButtonBase";
import DialogEx from './DialogEx'; import DialogEx from './DialogEx';
import AppBar from '@mui/material/AppBar'; import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar'; import Toolbar from '@mui/material/Toolbar';
import DraggableGrid, { ScrollDirection } from './DraggableGrid'; import DraggableGrid, { ScrollDirection } from './DraggableGrid';
import CloseIcon from '@mui/icons-material/Close';
import MoreVertIcon from '@mui/icons-material/MoreVert'; import MoreVertIcon from '@mui/icons-material/MoreVert';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText'; import ListItemText from '@mui/material/ListItemText';
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 UploadPresetDialog from './UploadPresetDialog'; import UploadPresetDialog from './UploadPresetDialog';
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
import SelectHoverBackground from './SelectHoverBackground';
import CloseIcon from '@mui/icons-material/Close';
import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import EditIcon from '@mui/icons-material/Edit';
import RenameDialog from './RenameDialog'; import RenameDialog from './RenameDialog';
import Slide, { SlideProps } from '@mui/material/Slide'; import Slide, { SlideProps } from '@mui/material/Slide';
@@ -56,10 +56,13 @@ import { withStyles } from "tss-react/mui";
import DownloadIcon from './svg/file_download_black_24dp.svg?react'; import DownloadIcon from './svg/file_download_black_24dp.svg?react';
import UploadIcon from './svg/file_upload_black_24dp.svg?react'; import UploadIcon from './svg/file_upload_black_24dp.svg?react';
// function isTouchUi() {
// return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
// }
interface PresetDialogProps extends WithStyles<typeof styles> { interface PresetDialogProps extends WithStyles<typeof styles> {
show: boolean; show: boolean;
isEditDialog: boolean;
onDialogClose: () => void; onDialogClose: () => void;
}; };
@@ -67,13 +70,16 @@ interface PresetDialogProps extends WithStyles<typeof styles> {
interface PresetDialogState { interface PresetDialogState {
presets: PresetIndex; presets: PresetIndex;
showActionBar: boolean; multiSelect: boolean;
showTouchActionBar: boolean;
currentItem: number; currentItem: number;
selectedItems: Set<number>; selectedItems: Set<number>;
renameOpen: boolean; renameOpen: boolean;
importOpen: boolean; importOpen: boolean;
copyToOpen: boolean;
moreMenuAnchorEl: HTMLElement | null; moreMenuAnchorEl: HTMLElement | null;
openUploadPresetDialog: boolean; openUploadPresetDialog: boolean;
@@ -93,7 +99,8 @@ const styles = (theme: Theme) => createStyles({
dialogActionBar: css({ dialogActionBar: css({
position: 'relative', position: 'relative',
top: 0, left: 0, top: 0, left: 0,
background: "black" background: theme.palette.actionBar.main,
color: theme.palette.actionBar.contrastText
}), }),
dialogTitle: css({ dialogTitle: css({
marginLeft: theme.spacing(2), marginLeft: theme.spacing(2),
@@ -111,7 +118,7 @@ const styles = (theme: Theme) => createStyles({
alignItems: "center", alignItems: "center",
textAlign: "left", textAlign: "left",
justifyContent: "center", justifyContent: "center",
paddingLeft: 8 paddibomngLeft: 8
}), }),
iconFrame: css({ iconFrame: css({
flex: "0 0 auto", flex: "0 0 auto",
@@ -136,7 +143,7 @@ const Transition = React.forwardRef(function Transition(
const PresetDialog = withStyles( const PresetDialog = withStyles(
class extends Component<PresetDialogProps, PresetDialogState> { class extends ResizeResponsiveComponent<PresetDialogProps, PresetDialogState> {
model: PiPedalModel; model: PiPedalModel;
@@ -150,32 +157,49 @@ const PresetDialog = withStyles(
let presets = this.model.presets.get(); let presets = this.model.presets.get();
this.state = { this.state = {
presets: presets, presets: presets,
showActionBar: false, multiSelect: false,
showTouchActionBar: false,
currentItem: presets.selectedInstanceId, currentItem: presets.selectedInstanceId,
selectedItems: new Set<number>(), selectedItems: new Set<number>([presets.selectedInstanceId]),
renameOpen: false, renameOpen: false,
importOpen: false, importOpen: false,
copyToOpen: false,
moreMenuAnchorEl: null, moreMenuAnchorEl: null,
openUploadPresetDialog: false openUploadPresetDialog: false
}; };
this.handlePresetsChanged = this.handlePresetsChanged.bind(this); this.handlePresetsChanged = this.handlePresetsChanged.bind(this);
}
onWindowSizeChanged(width: number, height: number) {
super.onWindowSizeChanged(width, height);
}
handleSelectionUpdated(instanceIds: Set<number>) {
this.setState({
multiSelect: instanceIds.size > 1
})
}
setSelections(instanceIds: Set<number>) {
this.setState({
selectedItems: instanceIds,
});
this.handleSelectionUpdated(instanceIds);
} }
setSelection(instanceId: number) { setSelection(instanceId: number) {
this.setState({ let selectedItems = new Set<number>([instanceId]);
this.setState({
currentItem: instanceId, currentItem: instanceId,
selectedItems: new Set<number>([instanceId]) selectedItems: selectedItems
}); });
this.handleSelectionUpdated(selectedItems);
} }
selectItemAtIndex(index: number) { selectItemAtIndex(index: number) {
let instanceId = this.state.presets.presets[index].instanceId; let instanceId = this.state.presets.presets[index].instanceId;
this.setSelection(instanceId); this.setSelection(instanceId);
} }
isEditMode() {
return this.state.showActionBar || this.props.isEditDialog;
}
onMoreClick(e: SyntheticEvent): void { onMoreClick(e: SyntheticEvent): void {
this.setState({ moreMenuAnchorEl: e.currentTarget as HTMLElement }) this.setState({ moreMenuAnchorEl: e.currentTarget as HTMLElement })
@@ -203,9 +227,10 @@ const PresetDialog = withStyles(
{ {
// if we don't have a valid selection, then use the current preset. // if we don't have a valid selection, then use the current preset.
if (this.state.presets.getItem(this.state.currentItem) == null) { if (this.state.presets.getItem(this.state.currentItem) == null) {
this.setState({ presets: presets, currentItem: presets.selectedInstanceId, this.setState({
presets: presets, currentItem: presets.selectedInstanceId,
selectedItems: new Set<number>([presets.selectedInstanceId]) selectedItems: new Set<number>([presets.selectedInstanceId])
}); });
} else { } else {
this.setState({ presets: presets }); this.setState({ presets: presets });
} }
@@ -222,8 +247,10 @@ const PresetDialog = withStyles(
this.model.presets.removeOnChangedHandler(this.handlePresetsChanged); this.model.presets.removeOnChangedHandler(this.handlePresetsChanged);
} }
getSelectedIndex() { getSelectedIndex(instanceId?: number): number {
let instanceId = this.isEditMode() ? this.state.currentItem : this.state.presets.selectedInstanceId; if (instanceId === undefined) {
instanceId = this.state.currentItem;
}
let presets = this.state.presets; let presets = this.state.presets;
for (let i = 0; i < presets.presets.length; ++i) { for (let i = 0; i < presets.presets.length; ++i) {
if (presets.presets[i].instanceId === instanceId) return i; if (presets.presets[i].instanceId === instanceId) return i;
@@ -232,33 +259,59 @@ const PresetDialog = withStyles(
} }
handleDeleteClick() { handleDeleteClick() {
if (this.state.currentItem === -1) return; if (this.state.selectedItems.size === 0) {
let currentItem = this.state.currentItem; return;
if (currentItem !== -1) {
this.model.deletePresetItem(currentItem)
.then((currentItem: number) => {
this.setSelection(currentItem);
})
.catch((error) => {
this.model.showAlert(error);
});
} }
this.model.deletePresetItems(this.state.selectedItems)
.then((currentItem: number) => {
this.setSelection(currentItem);
})
.catch((error) => {
this.model.showAlert(error);
});
} }
handleDialogClose() { handleDialogClose() {
this.props.onDialogClose(); this.props.onDialogClose();
} }
handleItemClick(instanceId: number): void { handleItemClick(e: React.MouseEvent<HTMLElement>, instanceId: number): void {
if (this.isEditMode()) { e.stopPropagation();
this.setSelection(instanceId); if (e.ctrlKey || this.state.showTouchActionBar) {
let selectedItems = new Set<number>(this.state.selectedItems);
if (selectedItems.has(instanceId)) {
selectedItems.delete(instanceId);
this.setSelections(selectedItems);
} else {
selectedItems.add(instanceId);
this.setState({
selectedItems: selectedItems,
currentItem: instanceId
});
this.handleSelectionUpdated(selectedItems);
}
} else if (e.shiftKey) {
let presets = this.state.presets;
let startIndex = this.getSelectedIndex();
let endIndex = this.getSelectedIndex(instanceId);
if (startIndex === -1 || endIndex === -1) return; // should not happen.
let selectedItems = new Set<number>();
if (endIndex < startIndex) {
let t = startIndex;
startIndex = endIndex;
endIndex = t;
}
for (let i = startIndex; i <= endIndex; ++i) {
selectedItems.add(presets.presets[i].instanceId);
}
this.setSelections(selectedItems);
} else { } else {
this.model.loadPreset(instanceId); this.setSelection(instanceId);
this.props.onDialogClose();
} }
} }
showActionBar(show: boolean): void { showTouchActionBar(show: boolean): void {
this.setState({ showActionBar: show }); this.setState({ showTouchActionBar: show });
} }
@@ -266,29 +319,23 @@ const PresetDialog = withStyles(
mapElement(el: any): React.ReactNode { mapElement(el: any): React.ReactNode {
let presetEntry = el as PresetIndexEntry; let presetEntry = el as PresetIndexEntry;
const classes = withStyles.getClasses(this.props); const classes = withStyles.getClasses(this.props);
let selected = let selected = this.state.selectedItems.has(presetEntry.instanceId);
this.isEditMode() ? this.state.selectedItems.has(presetEntry.instanceId) :
(presetEntry.instanceId === this.state.presets.selectedInstanceId);
return ( return (
<div key={presetEntry.instanceId} id={"psetdlgItem_" + presetEntry.instanceId} className="itemBackground"> <div key={presetEntry.instanceId} id={"psetdlgItem_" + presetEntry.instanceId} className="itemBackground">
<ListItemButton
<ButtonBase style={{ width: "100%", height: 48 }} selected={selected}
onClick={() => this.handleItemClick(presetEntry.instanceId)} onClick={(e) => this.handleItemClick(e, presetEntry.instanceId)}
style={{ height: 48 }}
> >
<SelectHoverBackground selected={selected} showHover={true} /> <ListItemIcon>
<div className={classes.itemFrame}> <img
<div className={classes.iconFrame}> src={isDarkMode() ? "img/ic_presets_white.svg" : "img/ic_presets.svg"}
<img className={classes.itemIcon} alt="" />
src={isDarkMode() ? "img/ic_presets_white.svg" : "img/ic_presets.svg"} </ListItemIcon>
className={classes.itemIcon} alt="" /> <ListItemText primary={presetEntry.name}
</div> />
<div className={classes.itemLabel}>
<Typography> </ListItemButton>
{presetEntry.name}
</Typography>
</div>
</div>
</ButtonBase>
</div> </div>
); );
@@ -343,10 +390,10 @@ const PresetDialog = withStyles(
if (!item) return; if (!item) return;
this.model.duplicatePreset(this.state.currentItem) this.model.duplicatePreset(this.state.currentItem)
.then((newId) => { .then((newId) => {
this.setState({ this.setState({
currentItem: newId, currentItem: newId,
selectedItems: new Set<number>([newId]) selectedItems: new Set<number>([newId])
}); });
}).catch((error) => { }).catch((error) => {
this.onError(error); this.onError(error);
}); });
@@ -356,8 +403,15 @@ const PresetDialog = withStyles(
this.model?.showAlert(error); this.model?.showAlert(error);
} }
handleImportPresetsFromBank() handleCloseActionBar(e: SyntheticEvent) {
{ e.stopPropagation();
this.setState({
showTouchActionBar: false,
selectedItems: new Set<number>([this.state.currentItem]),
multiSelect: false
});
}
handleImportPresetsFromBank() {
this.handleMoreClose(); this.handleMoreClose();
this.setState({ importOpen: true }); this.setState({ importOpen: true });
} }
@@ -372,21 +426,40 @@ const PresetDialog = withStyles(
if (el) { if (el) {
el.scrollIntoView({ behavior: "smooth", block: "center" }); el.scrollIntoView({ behavior: "smooth", block: "center" });
} }
},0); }, 0);
} }
}) })
.catch((error) => { .catch((error) => {
this.model.showAlert(error); this.model.showAlert(error);
}); });
} }
handleCopyToBankDialogOk(bankInstanceId: number): void {
this.setState({ copyToOpen: false });
let selectedItems: number[] = [];
for (let i = 0; i < this.state.presets.presets.length; ++i) {
let preset = this.state.presets.presets[i];
if (this.state.selectedItems.has(preset.instanceId)) {
selectedItems.push(preset.instanceId);
}
}
this.model.copyPresetsToBank(bankInstanceId, selectedItems)
.then((instanceId) => {
})
.catch((error) => {
this.model.showAlert(error);
});
}
handleCopyPresetsToBank() {
this.handleMoreClose();
this.setState({ copyToOpen: true });
}
render() { render() {
const classes = withStyles.getClasses(this.props); const classes = withStyles.getClasses(this.props);
let actionBarClass = this.props.isEditDialog ? classes.dialogAppBar : classes.dialogActionBar; const showActionBar = this.state.multiSelect || this.state.showTouchActionBar;
let defaultSelectedIndex = this.getSelectedIndex();
return ( return (
<DialogEx tag="preset" fullScreen open={this.props.show} <DialogEx tag="preset" fullScreen open={this.props.show}
onClose={() => { this.handleDialogClose() }} TransitionComponent={Transition} onClose={() => { this.handleDialogClose() }} TransitionComponent={Transition}
@@ -395,37 +468,37 @@ const PresetDialog = withStyles(
> >
<div style={{ display: "flex", flexDirection: "column", flexWrap: "nowrap", width: "100%", height: "100%", overflow: "hidden" }}> <div style={{ display: "flex", flexDirection: "column", flexWrap: "nowrap", width: "100%", height: "100%", overflow: "hidden" }}>
<div style={{ flex: "0 0 auto" }}> <div style={{ flex: "0 0 auto" }}>
<AppBar className={classes.dialogAppBar} style={{ display: this.isEditMode() ? "none" : "block" }} > <AppBar className={classes.dialogActionBar} style={{ display: showActionBar ? "block" : "none" }}
>
<Toolbar> <Toolbar>
<IconButtonEx tooltip="Back" edge="start" color="inherit" onClick={this.handleDialogClose} aria-label="back" <IconButtonEx tooltip="Back" edge="start" color="inherit" aria-label="back"
disabled={this.isEditMode()} onClick={(e) => { this.handleCloseActionBar(e); }}
> >
<ArrowBackIcon /> <CloseIcon />
</IconButtonEx> </IconButtonEx>
<Typography variant="h6" className={classes.dialogTitle}> <Typography variant="h6" className={classes.dialogTitle} noWrap>
Presets {this.state.selectedItems.size.toString() + " items selected"}
</Typography> </Typography>
<IconButtonEx tooltip="Edit" color="inherit" onClick={(e) => this.showActionBar(true)} > <Button color="inherit"
<EditIcon /> onClick={() => {
this.handleCopyPresetsToBank();
}} >
Copy to
</Button>
<IconButtonEx tooltip="Delete" color="inherit" onClick={(e) => { this.handleDeleteClick(); }} >
<img src="/img/old_delete_outline_white_24dp.svg" alt="Delete" style={{ width: 24, height: 24, opacity: 0.6 }} />
</IconButtonEx> </IconButtonEx>
</Toolbar> </Toolbar>
</AppBar> </AppBar>
<AppBar className={actionBarClass} style={{ display: this.isEditMode() ? "block" : "none" }} <AppBar className={classes.dialogAppBar} style={{ display: showActionBar ? "none" : "block" }}
onClick={(e) => { e.stopPropagation(); e.preventDefault(); }} onClick={(e) => { e.stopPropagation(); e.preventDefault(); }}
> >
<Toolbar> <Toolbar>
{(!this.props.isEditDialog) ? ( <IconButtonEx edge="start" tooltip="Back" color="inherit" onClick={this.handleDialogClose} aria-label="back"
<IconButtonEx tooltip="Close" edge="start" color="inherit" onClick={(e) => this.showActionBar(false)} aria-label="close"> >
<CloseIcon /> <ArrowBackIcon />
</IconButtonEx> </IconButtonEx>
) : ( <Typography variant="h6" className={classes.dialogTitle} noWrap>
<IconButtonEx edge="start" tooltip="Back" color="inherit" onClick={this.handleDialogClose} aria-label="back"
>
<ArrowBackIcon />
</IconButtonEx>
)}
<Typography variant="h6" className={classes.dialogTitle}>
Presets Presets
</Typography> </Typography>
{(this.state.presets.getItem(this.state.currentItem) != null) {(this.state.presets.getItem(this.state.currentItem) != null)
@@ -435,26 +508,39 @@ const PresetDialog = withStyles(
flex: "0 0 auto", display: "flex", flexFlow: "row nowrap", alignItems: "center" flex: "0 0 auto", display: "flex", flexFlow: "row nowrap", alignItems: "center"
}}> }}>
<Button color="inherit" onClick={(e) => this.handleCopy()}> {this.state.selectedItems.size === 1 && (
Copy <Button color="inherit" onClick={(e) => this.handleCopy()}
</Button> >
<Button color="inherit" onClick={() => this.handleRenameClick()}> Copy
Rename </Button>
</Button> )}
<RenameDialog {this.state.selectedItems.size === 1 && (
title="Rename"
open={this.state.renameOpen} <Button color="inherit" onClick={() => this.handleRenameClick()}
defaultName={this.getSelectedName()} >
acceptActionName={"Rename"} Rename
onClose={() => { this.setState({ renameOpen: false }) }} </Button>
onOk={(text: string) => { )}
this.handleRenameOk(text); {this.state.renameOpen && (
} <RenameDialog
} title="Rename"
/> open={this.state.renameOpen}
<IconButtonEx tooltip="Delete" color="inherit" onClick={(e) => this.handleDeleteClick()} > defaultName={this.getSelectedName()}
<img src="/img/old_delete_outline_white_24dp.svg" alt="Delete" style={{ width: 24, height: 24, opacity: 0.6 }} /> acceptActionName={"Rename"}
</IconButtonEx> onClose={() => { this.setState({ renameOpen: false }) }}
onOk={(text: string) => {
this.handleRenameOk(text);
}
}
/>
)}
{this.state.selectedItems.size === 1 && (
<IconButtonEx tooltip="Delete" color="inherit" onClick={(e) => this.handleDeleteClick()}
style={{ opacity: this.state.selectedItems.size !== 1 ? 0.5 : 1.0 }}
>
<img src="/img/old_delete_outline_white_24dp.svg" alt="Delete" style={{ width: 24, height: 24, opacity: 0.6 }} />
</IconButtonEx>
)}
<IconButtonEx tooltip="More..." color="inherit" onClick={(e) => { this.onMoreClick(e) }} > <IconButtonEx tooltip="More..." color="inherit" onClick={(e) => { this.onMoreClick(e) }} >
<MoreVertIcon /> <MoreVertIcon />
</IconButtonEx> </IconButtonEx>
@@ -466,15 +552,21 @@ const PresetDialog = withStyles(
onClose={() => this.handleMoreClose()} onClose={() => this.handleMoreClose()}
TransitionComponent={Fade} TransitionComponent={Fade}
> >
<MenuItem onClick={() => { this.handleDownloadPreset(); }} > {this.state.selectedItems.size !== 0 && (
<ListItemIcon> <MenuItem onClick={() => {
<Icon /> this.handleMoreClose();
</ListItemIcon> this.handleCopyPresetsToBank();
<ListItemText> }}
Copy to bank... >
</ListItemText> <ListItemIcon>
<Icon />
</ListItemIcon>
<ListItemText>
Copy to bank...
</ListItemText>
</MenuItem> </MenuItem>
)}
<MenuItem onClick={() => { this.handleImportPresetsFromBank(); }} > <MenuItem onClick={() => { this.handleImportPresetsFromBank(); }} >
<ListItemIcon> <ListItemIcon>
<Icon /> <Icon />
@@ -486,15 +578,17 @@ const PresetDialog = withStyles(
</MenuItem> </MenuItem>
<Divider /> <Divider />
<MenuItem onClick={() => { this.handleDownloadPreset(); }} > {this.state.selectedItems.size !== 0 && (
<ListItemIcon> <MenuItem onClick={() => { this.handleDownloadPreset(); }} >
<DownloadIcon className={classes.listIcon} /> <ListItemIcon>
</ListItemIcon> <DownloadIcon className={classes.listIcon} />
<ListItemText> </ListItemIcon>
Download preset <ListItemText>
</ListItemText> Download preset
</ListItemText>
</MenuItem> </MenuItem>
)}
<MenuItem onClick={() => { this.handleUploadPreset() }}> <MenuItem onClick={() => { this.handleUploadPreset() }}>
<ListItemIcon> <ListItemIcon>
<UploadIcon className={classes.listIcon} /> <UploadIcon className={classes.listIcon} />
@@ -516,12 +610,16 @@ const PresetDialog = withStyles(
</div> </div>
<div style={{ flex: "1 1 auto", position: "relative", overflow: "hidden" }} > <div style={{ flex: "1 1 auto", position: "relative", overflow: "hidden" }} >
<DraggableGrid <DraggableGrid
onLongPress={(item) => this.showActionBar(true)} onLongPress={(e,item) => {
canDrag={this.isEditMode()} if (e.pointerType === "touch")
{
this.showTouchActionBar(true);
}
}}
canDrag={!this.state.multiSelect && !this.state.showTouchActionBar}
onDragStart={(index, x, y) => { this.selectItemAtIndex(index) }} onDragStart={(index, x, y) => { this.selectItemAtIndex(index) }}
moveElement={(from, to) => { this.moveElement(from, to); }} moveElement={(from, to) => { this.moveElement(from, to); }}
scroll={ScrollDirection.Y} scroll={ScrollDirection.Y}
defaultSelectedIndex={defaultSelectedIndex}
> >
{ {
this.state.presets.presets.map((element) => { this.state.presets.presets.map((element) => {
@@ -536,16 +634,23 @@ const PresetDialog = withStyles(
extension='.piPreset' extension='.piPreset'
uploadPage='uploadPreset' uploadPage='uploadPreset'
onUploaded={(instanceId) => this.setSelection(instanceId)} onUploaded={(instanceId) => this.setSelection(instanceId)}
uploadAfter={this.state.currentItem } uploadAfter={this.state.currentItem}
open={this.state.openUploadPresetDialog} open={this.state.openUploadPresetDialog}
onClose={() => { this.setState({ openUploadPresetDialog: false }) }} /> onClose={() => { this.setState({ openUploadPresetDialog: false }) }} />
{this.state.importOpen && ( {this.state.importOpen && (
<ImportPresetFromBankDialog <ImportPresetFromBankDialog
open={this.state.importOpen} open={this.state.importOpen}
onClose={() => this.setState({ importOpen: false })} onClose={() => this.setState({ importOpen: false })}
onOk={(bankInstanceId, presets) => this.handleImportDialogOk(bankInstanceId, presets)} onOk={(bankInstanceId, presets) => this.handleImportDialogOk(bankInstanceId, presets)}
/> />
)} )}
{this.state.copyToOpen && (
<CopyPresetsToBankDialog
open={this.state.copyToOpen}
onClose={() => this.setState({ copyToOpen: false })}
onOk={(bankInstanceId) => this.handleCopyToBankDialogOk(bankInstanceId)}
/>
)}
</DialogEx> </DialogEx>
+66 -12
View File
@@ -17,7 +17,8 @@
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // 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. // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import { SyntheticEvent, Component } from 'react'; import { SyntheticEvent } from 'react';
import ArrowRightIcon from '@mui/icons-material/ArrowRight';
import IconButtonEx from './IconButtonEx'; import IconButtonEx from './IconButtonEx';
import { PiPedalModel, PiPedalModelFactory, PresetIndex } from './PiPedalModel'; import { PiPedalModel, PiPedalModelFactory, PresetIndex } from './PiPedalModel';
import SaveIconOutline from '@mui/icons-material/Save'; import SaveIconOutline from '@mui/icons-material/Save';
@@ -39,6 +40,7 @@ import ImportPresetFromBankDialog from './ImportPresetFromBankDialog';
import Select from '@mui/material/Select'; import Select from '@mui/material/Select';
import UploadPresetDialog from './UploadPresetDialog'; import UploadPresetDialog from './UploadPresetDialog';
import { isDarkMode } from './DarkMode'; import { isDarkMode } from './DarkMode';
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
interface PresetSelectorProps extends WithStyles<typeof styles> { interface PresetSelectorProps extends WithStyles<typeof styles> {
@@ -51,6 +53,8 @@ interface PresetSelectorState {
showPresetsDialog: boolean; showPresetsDialog: boolean;
showEditPresetsDialog: boolean; showEditPresetsDialog: boolean;
presetsMenuAnchorRef: HTMLElement | null; presetsMenuAnchorRef: HTMLElement | null;
presetsSubmenuAnchorRef: HTMLElement | null;
compactHorizontalLayoutMenu: boolean;
saveAsDialogOpen: boolean; saveAsDialogOpen: boolean;
importDialogOpen: boolean; importDialogOpen: boolean;
@@ -87,8 +91,8 @@ const styles = (theme: Theme) => createStyles({
const PresetSelector = const PresetSelector =
withStyles( withStyles(
class extends Component<PresetSelectorProps, PresetSelectorState> { class extends ResizeResponsiveComponent<PresetSelectorProps, PresetSelectorState> {
private MENU_THRESHOLD: number = 570;
model: PiPedalModel; model: PiPedalModel;
constructor(props: PresetSelectorProps) { constructor(props: PresetSelectorProps) {
@@ -98,9 +102,11 @@ const PresetSelector =
presets: presets:
this.model.presets.get(), this.model.presets.get(),
enabled: false, enabled: false,
compactHorizontalLayoutMenu: this.windowSize.height < this.MENU_THRESHOLD,
showPresetsDialog: false, showPresetsDialog: false,
showEditPresetsDialog: false, showEditPresetsDialog: false,
presetsMenuAnchorRef: null, presetsMenuAnchorRef: null,
presetsSubmenuAnchorRef: null,
renameDialogOpen: false, renameDialogOpen: false,
saveAsDialogOpen: false, saveAsDialogOpen: false,
importDialogOpen: false, importDialogOpen: false,
@@ -117,12 +123,28 @@ const PresetSelector =
this.handlePresetsMenuClose = this.handlePresetsMenuClose.bind(this); this.handlePresetsMenuClose = this.handlePresetsMenuClose.bind(this);
} }
onWindowSizeChanged(width: number, height: number): void {
super.onWindowSizeChanged(width,height);
this.setState({ compactHorizontalLayoutMenu: height < this.MENU_THRESHOLD });
}
handlePresetMenuClick(event: SyntheticEvent): void { handlePresetsMenuClick(event: SyntheticEvent): void {
this.setState({ presetsMenuAnchorRef: (event.currentTarget as HTMLElement) }); this.setState({ presetsMenuAnchorRef: (event.currentTarget as HTMLElement) });
} }
handlePresetsSubmenuClick(event: SyntheticEvent): void {
this.setState({ presetsSubmenuAnchorRef: (event.currentTarget as HTMLElement) });
}
handlePresetsMenuClose(): void { handlePresetsMenuClose(): void {
this.setState({ presetsMenuAnchorRef: null }); this.setState({
presetsMenuAnchorRef: null,
presetsSubmenuAnchorRef: null,
});
}
handlePresetsSubmenuClose(): void {
this.setState({
presetsSubmenuAnchorRef: null,
});
} }
handlePresetsMenuImport(e: SyntheticEvent): void { handlePresetsMenuImport(e: SyntheticEvent): void {
@@ -284,11 +306,13 @@ const PresetSelector =
this.updatePresetState(); this.updatePresetState();
} }
componentDidMount() { componentDidMount() {
super.componentDidMount();
this.model.presets.addOnChangedHandler(this.handlePresetsChanged); this.model.presets.addOnChangedHandler(this.handlePresetsChanged);
this.updatePresetState(); this.updatePresetState();
} }
componentWillUnmount() { componentWillUnmount() {
this.model.presets.removeOnChangedHandler(this.handlePresetsChanged) this.model.presets.removeOnChangedHandler(this.handlePresetsChanged)
super.componentWillUnmount();
} }
showPresetDialog(show: boolean) { showPresetDialog(show: boolean) {
this.setState({ this.setState({
@@ -368,7 +392,7 @@ const PresetSelector =
<IconButtonEx <IconButtonEx
tooltip="More..." tooltip="More..."
style={{ flex: "0 0 auto", color: "#FFFFFF" }} style={{ flex: "0 0 auto", color: "#FFFFFF" }}
onClick={(e) => this.handlePresetMenuClick(e)} onClick={(e) => this.handlePresetsMenuClick(e)}
size="large" size="large"
> >
<MoreVertIcon style={{ opacity: 0.75 }} color="inherit" /> <MoreVertIcon style={{ opacity: 0.75 }} color="inherit" />
@@ -381,10 +405,24 @@ const PresetSelector =
TransitionComponent={Fade} TransitionComponent={Fade}
> >
<MenuItem onClick={(e) => this.handlePresetsMenuSave(e)}>Save preset</MenuItem> <MenuItem onClick={(e) => this.handlePresetsMenuSave(e)}>Save preset</MenuItem>
<MenuItem onClick={(e) => this.handlePresetsMenuSaveAs(e)}>Save preset as...</MenuItem>
<MenuItem onClick={(e) => this.handlePresetsMenuRename(e)}>Rename...</MenuItem> {this.state.compactHorizontalLayoutMenu ? (
<MenuItem onClick={(e) => this.handlePresetsMenuImport(e)}>Import from bank...</MenuItem> <MenuItem key="a" onClick={(e) => this.handlePresetsSubmenuClick(e)}
<MenuItem onClick={(e) => this.handlePresetsMenuNew(e)}>New...</MenuItem> style={{ display: 'flex', flexFlow: "row nowrap", alignItems: 'center', justifyContent: 'space-between' }}
>
Edit
<ArrowRightIcon/>
</MenuItem>
)
:(
[
(<MenuItem key="b" onClick={(e) => this.handlePresetsMenuSaveAs(e)}>Save preset as...</MenuItem>),
(<MenuItem key="c" onClick={(e) => this.handlePresetsMenuRename(e)}>Rename...</MenuItem>),
(<MenuItem key="d" onClick={(e) => this.handlePresetsMenuImport(e)}>Import from bank...</MenuItem>),
(<MenuItem key="e" onClick={(e) => this.handlePresetsMenuNew(e)}>New...</MenuItem>),
]
)}
<Divider /> <Divider />
<MenuItem onClick={(e) => { this.handleDownloadPreset(e); }} >Download preset</MenuItem> <MenuItem onClick={(e) => { this.handleDownloadPreset(e); }} >Download preset</MenuItem>
<MenuItem onClick={(e) => { this.handleUploadPreset(e) }}>Upload preset</MenuItem> <MenuItem onClick={(e) => { this.handleUploadPreset(e) }}>Upload preset</MenuItem>
@@ -392,8 +430,24 @@ const PresetSelector =
<MenuItem onClick={(e) => this.handleMenuEditPresets()}>Manage presets...</MenuItem> <MenuItem onClick={(e) => this.handleMenuEditPresets()}>Manage presets...</MenuItem>
</Menu> </Menu>
</div> </div>
<PresetDialog show={this.state.showPresetsDialog} isEditDialog={this.state.showEditPresetsDialog} onDialogClose={() => this.handleDialogClose()}
/> {/* Submenu */}
<Menu key="b" onClose={() => this.handlePresetsSubmenuClose()}
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
anchorPosition={{left: 48, top: 0}}
anchorEl={this.state.presetsSubmenuAnchorRef}
open={this.state.presetsSubmenuAnchorRef !== null} >
<MenuItem onClick={(e) => this.handlePresetsMenuSaveAs(e)}>Save preset as...</MenuItem>
<MenuItem onClick={(e) => this.handlePresetsMenuRename(e)}>Rename...</MenuItem>
<MenuItem onClick={(e) => this.handlePresetsMenuImport(e)}>Import from bank...</MenuItem>
<MenuItem onClick={(e) => this.handlePresetsMenuNew(e)}>New...</MenuItem>
</Menu>
{this.state.showPresetsDialog&& (
<PresetDialog show={this.state.showPresetsDialog} onDialogClose={() => this.handleDialogClose()}
/>
)}
{this.state.saveAsDialogOpen && ( {this.state.saveAsDialogOpen && (
<SavePresetAsDialog open={this.state.saveAsDialogOpen} <SavePresetAsDialog open={this.state.saveAsDialogOpen}
defaultName={presets.getItem(presets.selectedInstanceId)?.name ?? "My Preset"} defaultName={presets.getItem(presets.selectedInstanceId)?.name ?? "My Preset"}