Plugin properties rework.

This commit is contained in:
Robin E. R. Davies
2025-09-16 01:27:04 -04:00
parent 5dcfecf9f8
commit 9c18f7ab7c
19 changed files with 1603 additions and 1036 deletions
+1 -1
View File
@@ -22,7 +22,7 @@
// Resolved by CMake Tools: // Resolved by CMake Tools:
"program": "${command:cmake.launchTargetPath}", "program": "${command:cmake.launchTargetPath}",
"args": [ "args": [
"[ProfileThumbnails]" "--gplot-downloads"
], ],
"stopAtEntry": false, "stopAtEntry": false,
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
+4 -2
View File
@@ -153,12 +153,13 @@ bool Pedalboard::SetControlValue(int64_t pedalItemId, const std::string &symbol,
return item->SetControlValue(symbol,value); return item->SetControlValue(symbol,value);
} }
bool Pedalboard::SetItemTitle(int64_t pedalItemId, const std::string &title) bool Pedalboard::SetItemTitle(int64_t pedalItemId, const std::string &title, const std::string&iconColor)
{ {
PedalboardItem*item = GetItem(pedalItemId); PedalboardItem*item = GetItem(pedalItemId);
if (!item) return false; if (!item) return false;
if (item->title() == title) return false; // no change. if (item->title() == title && item->iconColor() == iconColor) return false; // no change.
item->title(title); item->title(title);
item->iconColor(iconColor);
return true; return true;
} }
@@ -532,6 +533,7 @@ JSON_MAP_BEGIN(PedalboardItem)
JSON_MAP_REFERENCE(PedalboardItem,pathProperties) JSON_MAP_REFERENCE(PedalboardItem,pathProperties)
JSON_MAP_REFERENCE(PedalboardItem,title) JSON_MAP_REFERENCE(PedalboardItem,title)
JSON_MAP_REFERENCE(PedalboardItem,useModUi) JSON_MAP_REFERENCE(PedalboardItem,useModUi)
JSON_MAP_REFERENCE(PedalboardItem,iconColor)
JSON_MAP_END() JSON_MAP_END()
+3 -1
View File
@@ -106,6 +106,7 @@ public:
std::map<std::string,std::string> pathProperties_; std::map<std::string,std::string> pathProperties_;
std::string title_; std::string title_;
bool useModUi_ = false; bool useModUi_ = false;
std::string iconColor_;
// non persistent state. // non persistent state.
PropertyMap patchProperties; PropertyMap patchProperties;
@@ -135,6 +136,7 @@ public:
GETTER_SETTER(stateUpdateCount) GETTER_SETTER(stateUpdateCount)
GETTER_SETTER_REF(lv2State) GETTER_SETTER_REF(lv2State)
GETTER_SETTER_REF(title) GETTER_SETTER_REF(title)
GETTER_SETTER_REF(iconColor)
GETTER_SETTER(useModUi) GETTER_SETTER(useModUi)
Lv2PluginState&lv2State() { return lv2State_; } // non-const version. Lv2PluginState&lv2State() { return lv2State_; } // non-const version.
@@ -232,7 +234,7 @@ public:
static constexpr int64_t INPUT_VOLUME_ID = -2; // synthetic PedalboardItem for input volume. static constexpr int64_t INPUT_VOLUME_ID = -2; // synthetic PedalboardItem for input volume.
static constexpr int64_t OUTPUT_VOLUME_ID = -3; // synthetic PedalboardItem for output volume. static constexpr int64_t OUTPUT_VOLUME_ID = -3; // synthetic PedalboardItem for output volume.
bool SetControlValue(int64_t pedalItemId, const std::string &symbol, float value); bool SetControlValue(int64_t pedalItemId, const std::string &symbol, float value);
bool SetItemTitle(int64_t pedalItemId, const std::string &title); bool SetItemTitle(int64_t pedalItemId, const std::string &title, const std::string&iconColor);
bool SetItemEnabled(int64_t pedalItemId, bool enabled); bool SetItemEnabled(int64_t pedalItemId, bool enabled);
bool SetItemUseModUi(int64_t pedalItemId, bool enabled); bool SetItemUseModUi(int64_t pedalItemId, bool enabled);
void SetCurrentSnapshotModified(bool modified); void SetCurrentSnapshotModified(bool modified);
+2 -2
View File
@@ -3226,10 +3226,10 @@ void PiPedalModel::MoveAudioFile(
AudioDirectoryInfo::Ptr dir = AudioDirectoryInfo::Create(directory); AudioDirectoryInfo::Ptr dir = AudioDirectoryInfo::Create(directory);
dir->MoveAudioFile(directory, fromPosition, toPosition); dir->MoveAudioFile(directory, fromPosition, toPosition);
} }
void PiPedalModel::SetPedalboardItemTitle(int64_t instanceId, const std::string &title) void PiPedalModel::SetPedalboardItemTitle(int64_t instanceId, const std::string &title, const std::string &colorKey)
{ {
std::lock_guard<std::recursive_mutex> lock(mutex); std::lock_guard<std::recursive_mutex> lock(mutex);
if (!this->pedalboard.SetItemTitle(instanceId, title)) if (!this->pedalboard.SetItemTitle(instanceId, title, colorKey))
{ {
return; return;
} }
+1 -1
View File
@@ -486,7 +486,7 @@ namespace pipedal
int32_t from, int32_t from,
int32_t to); int32_t to);
void SetPedalboardItemTitle(int64_t instanceId, const std::string &title); void SetPedalboardItemTitle(int64_t instanceId, const std::string &title, const std::string&iconColor);
void SetTone3000Auth(const std::string &apiKey); void SetTone3000Auth(const std::string &apiKey);
bool HasTone3000Auth() const; bool HasTone3000Auth() const;
+3 -1
View File
@@ -179,12 +179,14 @@ class SetPedalboardItemTitleBody
public: public:
uint64_t instanceId_; uint64_t instanceId_;
std::string title_; std::string title_;
std::string colorKey_;
DECLARE_JSON_MAP(SetPedalboardItemTitleBody); DECLARE_JSON_MAP(SetPedalboardItemTitleBody);
}; };
JSON_MAP_BEGIN(SetPedalboardItemTitleBody) JSON_MAP_BEGIN(SetPedalboardItemTitleBody)
JSON_MAP_REFERENCE(SetPedalboardItemTitleBody, instanceId) JSON_MAP_REFERENCE(SetPedalboardItemTitleBody, instanceId)
JSON_MAP_REFERENCE(SetPedalboardItemTitleBody, title) JSON_MAP_REFERENCE(SetPedalboardItemTitleBody, title)
JSON_MAP_REFERENCE(SetPedalboardItemTitleBody, colorKey)
JSON_MAP_END() JSON_MAP_END()
@@ -1569,7 +1571,7 @@ public:
{ {
SetPedalboardItemTitleBody body; SetPedalboardItemTitleBody body;
pReader->read(&body); pReader->read(&body);
model.SetPedalboardItemTitle(body.instanceId_, body.title_); model.SetPedalboardItemTitle(body.instanceId_, body.title_, body.colorKey_);
} }
else if (message == "getPatchProperty") else if (message == "getPatchProperty")
{ {
+129 -15
View File
@@ -43,10 +43,11 @@ std::string psystem(const std::string &command)
{ {
throw std::runtime_error("popen() failed!"); throw std::runtime_error("popen() failed!");
} }
Finally ff{[pipe]() { Finally ff{[pipe]()
pclose(pipe); {
}}; pclose(pipe);
while (fgets(buffer.data(), buffer.size()-1, pipe) != nullptr) }};
while (fgets(buffer.data(), buffer.size() - 1, pipe) != nullptr)
{ {
result += buffer.data(); result += buffer.data();
} }
@@ -68,7 +69,8 @@ void SignPackage()
{ {
packagePath = SS("build/pipedal_" << PROJECT_VER << "_amd64.deb"); packagePath = SS("build/pipedal_" << PROJECT_VER << "_amd64.deb");
packagePath = fs::absolute(packagePath); packagePath = fs::absolute(packagePath);
if (!fs::exists(packagePath)) { if (!fs::exists(packagePath))
{
packagePath = SS("build/pipedal_" << PROJECT_VER << "_*.deb"); packagePath = SS("build/pipedal_" << PROJECT_VER << "_*.deb");
throw std::runtime_error(SS("File does not exist: " << packagePath)); throw std::runtime_error(SS("File does not exist: " << packagePath));
} }
@@ -82,8 +84,8 @@ void SignPackage()
std::string signCmd = std::string signCmd =
SS("/usr/bin/gpg --pinentry-mode loopback --yes --default-key " << UPDATE_GPG_FINGERPRINT2 SS("/usr/bin/gpg --pinentry-mode loopback --yes --default-key " << UPDATE_GPG_FINGERPRINT2
<< " --armor --output " << packagePath << ".asc" << " --armor --output " << packagePath << ".asc"
<< " --detach-sign " << packagePath.c_str()); << " --detach-sign " << packagePath.c_str());
int result = system(signCmd.c_str()); int result = system(signCmd.c_str());
if (result != EXIT_SUCCESS) if (result != EXIT_SUCCESS)
{ {
@@ -226,6 +228,8 @@ public:
bool draft = false, prerelease = false; bool draft = false, prerelease = false;
std::vector<DownloadCountAsset> assets; std::vector<DownloadCountAsset> assets;
std::string published_at; std::string published_at;
double release_downloads = 0;
double average_daily_downloads = 0;
}; };
static std::string timePointToISO8601(const std::chrono::system_clock::time_point &tp) static std::string timePointToISO8601(const std::chrono::system_clock::time_point &tp)
@@ -233,11 +237,33 @@ static std::string timePointToISO8601(const std::chrono::system_clock::time_poin
auto tt = std::chrono::system_clock::to_time_t(tp); auto tt = std::chrono::system_clock::to_time_t(tp);
std::tm tm = *std::gmtime(&tt); std::tm tm = *std::gmtime(&tt);
std::stringstream ss; std::stringstream ss;
ss << std::put_time(&tm,"%Y-%m-%dT%H:%M:%S") << "Z"; ss << std::put_time(&tm, "%Y-%m-%dT%H:%M:%S") << "Z";
return ss.str(); return ss.str();
} }
static std::chrono::system_clock::time_point iso8601ToTimePoint(const std::string&date)
{
std::istringstream ss(date);
std::tm tm = {};
char zulu;
ss >> std::get_time(&tm, "%Y-%m-%dT%H:%M:%S") >> zulu;
if (ss.fail()) {
throw std::runtime_error("Failed to parse ISO8601 date: " + date);
}
return std::chrono::system_clock::from_time_t(std::mktime(&tm));
}
static bool endsWith(const std::string &value, const std::string &end)
{
if (end.length() > value.length())
{
return false;
}
return value.compare(value.length() - end.length(), end.length(), end) == 0;
}
void GetDownloadCounts(bool gplotDownloads) void GetDownloadCounts(bool gplotDownloads)
{ {
// error handling and temporary file use is different enough that it justifies // error handling and temporary file use is different enough that it justifies
@@ -321,15 +347,65 @@ void GetDownloadCounts(bool gplotDownloads)
return left.published_at < right.published_at; // date ascending return left.published_at < right.published_at; // date ascending
}); });
for (size_t i = 0; i < releases.size()-1; ++i) uint64_t cumulativeCount = 0;
for (auto i = releases.begin(); i != releases.end(); /**/)
{ {
releases[i].published_at = releases[i+1].published_at; if (i->name.find("Experimental") != string::npos
|| i->name.find("Retracted") != string::npos
|| i->name.find("Testing") != string::npos
)
{
i = releases.erase(i);
}
else
{
++i;
}
}
for (auto &release : releases)
{
size_t releaseDownloads = 0;
for (const auto &asset : release.assets)
{
if (asset.name.ends_with(".deb"))
{
releaseDownloads += asset.downloads;
}
}
release.release_downloads = releaseDownloads;
}
for (size_t i = 0; i < releases.size(); ++i)
{
std::chrono::system_clock::time_point nextTime;
if (i == releases.size()-1)
{
nextTime = std::chrono::system_clock::now();
} else {
nextTime = iso8601ToTimePoint(releases.at(i+1).published_at);
}
auto thisTime = iso8601ToTimePoint(releases.at(i).published_at);
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(nextTime-thisTime).count();
auto days = seconds/(60.0*60*24);
releases[i].average_daily_downloads = releases[i].release_downloads/
(days);
}
for (size_t i = 0; i < releases.size() - 1; ++i)
{
releases[i].published_at = releases[i + 1].published_at;
} }
if (releases.size() >= 1) if (releases.size() >= 1)
{ {
releases[releases.size()-1].published_at = timePointToISO8601(std::chrono::system_clock::now()); releases[releases.size() - 1].published_at = timePointToISO8601(std::chrono::system_clock::now());
} }
uint64_t cumulativeCount = 0;
for (const auto &release : releases) for (const auto &release : releases)
{ {
for (const auto &asset : release.assets) for (const auto &asset : release.assets)
@@ -339,7 +415,10 @@ void GetDownloadCounts(bool gplotDownloads)
cumulativeCount += asset.downloads; cumulativeCount += asset.downloads;
} }
} }
cout << release.published_at << " " << cumulativeCount << endl; cout << release.published_at << " " << cumulativeCount
<< " " << release.average_daily_downloads
<< " " << "\"" << release.name << "\""
<< endl;
} }
} }
else else
@@ -351,14 +430,49 @@ void GetDownloadCounts(bool gplotDownloads)
{ {
return left.published_at > right.published_at; // latest date first. return left.published_at > right.published_at; // latest date first.
}); });
size_t total = 0;
size_t amdTotal = 0;
size_t amdAscTotal = 0;
size_t aarch64Total = 0;
size_t aarch64AscTotal = 0;
size_t otherTotal = 0;
for (const auto &release : releases) for (const auto &release : releases)
{ {
cout << release.name << endl; cout << release.name << " " << release.published_at << endl;
for (const auto &asset : release.assets) for (const auto &asset : release.assets)
{ {
total += asset.downloads;
if (endsWith(asset.name, "arm64.deb"))
{
aarch64Total += asset.downloads;
}
else if (endsWith(asset.name, "amd64.deb"))
{
amdTotal += asset.downloads;
}
else if (endsWith(asset.name, "arm64.deb.asc"))
{
aarch64AscTotal += asset.downloads;
}
else if (endsWith(asset.name, "amd64.deb.asc"))
{
amdAscTotal += asset.downloads;
}
else
{
otherTotal += asset.downloads;
}
cout << " " << asset.name << ": " << asset.downloads << endl; cout << " " << asset.name << ": " << asset.downloads << endl;
} }
} }
cout << endl
<< "Total: " << total << endl;
cout << "arm64:" << aarch64Total << " asc: " << aarch64AscTotal << endl;
cout << "amd64:" << amdTotal << " asc: " << amdAscTotal << endl;
cout << "other:" << otherTotal << endl;
} }
} }
} }
+7 -2
View File
@@ -27,7 +27,7 @@ import { PiPedalStateError } from './PiPedalError';
import { css } from '@emotion/react'; import { css } from '@emotion/react';
const SELECT_SCALE = 1.5; const SELECT_SCALE = 1.2;
const AUTOSCROLL_TICK_DELAY = 30; const AUTOSCROLL_TICK_DELAY = 30;
const AUTOSCROLL_THRESHOLD = 48; const AUTOSCROLL_THRESHOLD = 48;
@@ -58,6 +58,7 @@ export interface DraggableProps extends WithStyles<typeof styles> {
children?: ReactNode | ReactNode[]; children?: ReactNode | ReactNode[];
draggable?: boolean; draggable?: boolean;
style?: React.CSSProperties;
} }
@@ -149,6 +150,7 @@ const Draggable =
startClientY: number = 0; startClientY: number = 0;
dragStarted: boolean = false; dragStarted: boolean = false;
savedIndex: string = ""; savedIndex: string = "";
savedOpacity: string = "";
lastPointerDown: number = 0; lastPointerDown: number = 0;
originalBounds?: DOMRect; originalBounds?: DOMRect;
captureElement?: HTMLDivElement; captureElement?: HTMLDivElement;
@@ -244,6 +246,8 @@ const Draggable =
this.pointerId = e.pointerId; this.pointerId = e.pointerId;
this.pointerType = e.pointerType; this.pointerType = e.pointerType;
this.savedIndex = e.currentTarget.style.zIndex; this.savedIndex = e.currentTarget.style.zIndex;
this.savedOpacity = e.currentTarget.style.opacity;
e.currentTarget.style.opacity = "0.9";
if (this.pointerType !== "touch") { if (this.pointerType !== "touch") {
this.dragTarget.style.transform = "scale(" + SELECT_SCALE + ")"; this.dragTarget.style.transform = "scale(" + SELECT_SCALE + ")";
this.dragTarget.style.zIndex = "3"; this.dragTarget.style.zIndex = "3";
@@ -254,6 +258,7 @@ const Draggable =
clearDragTransform(element: HTMLDivElement): void { clearDragTransform(element: HTMLDivElement): void {
element.style.transform = ""; element.style.transform = "";
element.style.zIndex = this.savedIndex; element.style.zIndex = this.savedIndex;
element.style.opacity = this.savedOpacity;
} }
onPointerCancel(e: PointerEvent<HTMLDivElement>) { onPointerCancel(e: PointerEvent<HTMLDivElement>) {
@@ -486,7 +491,7 @@ const Draggable =
render() { render() {
const classes = withStyles.getClasses(this.props); const classes = withStyles.getClasses(this.props);
return ( return (
<div className={classes.frame} style={{ transform: "" }} <div className={classes.frame} style={{ transform: "", ...this.props.style }}
onPointerDown={this.onPointerDown} onPointerDown={this.onPointerDown}
onPointerMove={this.onPointerMove} onPointerMove={this.onPointerMove}
onPointerCancel={this.onPointerCancel} onPointerCancel={this.onPointerCancel}
+30 -17
View File
@@ -21,13 +21,14 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
import React, { useState, useEffect } from 'react'; import React, { useState } from 'react';
import ButtonBase from '@mui/material/ButtonBase'; import ButtonBase from '@mui/material/ButtonBase';
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 { useTheme } from '@mui/material/styles'; import { useTheme } from '@mui/material/styles';
import { getIconColorSelections,getIconColor,IconColorSelect } from './PluginIcon'; import PluginIcon, { getIconColorSelections, getIconColor, IconColorSelect } from './PluginIcon';
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
import { PluginType } from './Lv2Plugin';
export enum DropdownAlignment { export enum DropdownAlignment {
@@ -37,6 +38,7 @@ export enum DropdownAlignment {
interface ColorListDropdownButtonProps { interface ColorListDropdownButtonProps {
colorKey: string; colorKey: string;
colorList?: IconColorSelect[]; colorList?: IconColorSelect[];
pluginType?: PluginType;
onColorChange: (selection: IconColorSelect) => void; onColorChange: (selection: IconColorSelect) => void;
dropdownAlignment: DropdownAlignment; dropdownAlignment: DropdownAlignment;
@@ -71,19 +73,30 @@ const IconColorDropdownButton: React.FC<ColorListDropdownButtonProps> = (props:
<ButtonBase <ButtonBase
onClick={handleClick} onClick={handleClick}
style={{ style={{
height: 48, padding: 12, borderRadius: 18 height: 48, padding: 12, borderRadius: 8, margin: 4
}} }}
> >
<div style={{ display: "flex", flexFlow: "row nowrap", alignItems: "center", gap: 0 }} > <div style={{ display: "flex", flexFlow: "row nowrap", alignItems: "center", gap: 0 }} >
<div style={{ {props.pluginType ? (
width: 24, height: 24, <div style={{ margin: 4 }}>
background: selectedColor, <PluginIcon
borderRadius: 6, pluginType={props.pluginType}
borderColor: theme.palette.text.secondary, color={selectedColor}
borderWidth: 1, size={24}
borderStyle: "solid" />
}} /> </div>
<ArrowDropDownIcon /> ) : (
<div style={{
width: 24, height: 24,
background: selectedColor,
borderRadius: 6,
borderColor: theme.palette.text.secondary,
borderWidth: 1,
borderStyle: "solid"
}} />
)}
<ArrowDropDownIcon />
</div> </div>
</ButtonBase> </ButtonBase>
@@ -93,20 +106,20 @@ const IconColorDropdownButton: React.FC<ColorListDropdownButtonProps> = (props:
onClose={handleClose} onClose={handleClose}
anchorOrigin={{ anchorOrigin={{
vertical: 'bottom', vertical: 'bottom',
horizontal: (dropdownAlignment === DropdownAlignment.SW ? 'right' : 'left') horizontal: (dropdownAlignment === DropdownAlignment.SW ? 'right' : 'left')
}} }}
transformOrigin={{ transformOrigin={{
vertical: 'top', vertical: 'top',
horizontal: (dropdownAlignment === DropdownAlignment.SW ? 'right' : 'left'), horizontal: (dropdownAlignment === DropdownAlignment.SW ? 'right' : 'left'),
}} }}
> >
<div style={{display: "flex", flexFlow: "row nowrap", columnGap: 8,padding: 8}}> <div style={{ display: "flex", flexFlow: "row nowrap", columnGap: 8, padding: 8 }}>
<MenuItem key={"default"} onClick={() => handleColorSelect({key: "default", value: undefined})} <MenuItem key={"default"} onClick={() => handleColorSelect({ key: "default", value: undefined })}
style={{ style={{
width: 48, height: 48, borderRadius: 6, margin: 4, width: 48, height: 48, borderRadius: 6, margin: 4,
borderStyle: "solid", borderStyle: "solid",
borderWidth: colorKey ==="default" ? 2 : 0.25, borderWidth: colorKey === "default" ? 2 : 0.25,
borderColor: colorKey == "default" ? borderColor: colorKey == "default" ?
theme.palette.text.primary theme.palette.text.primary
: theme.palette.text.secondary, : theme.palette.text.secondary,
+49 -19
View File
@@ -24,7 +24,7 @@ import { withStyles } from "tss-react/mui";
import IconButtonEx from './IconButtonEx'; import IconButtonEx from './IconButtonEx';
import ButtonEx from './ButtonEx'; import ButtonEx from './ButtonEx';
import ButtonBase from '@mui/material/ButtonBase'; import ButtonBase from '@mui/material/ButtonBase';
import RenameDialog from './RenameDialog'; import PluginIcon, { getIconColor } from './PluginIcon';
import ToolTipEx from './ToolTipEx'; import ToolTipEx from './ToolTipEx';
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel'; import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
@@ -64,9 +64,11 @@ import PipedalUiIcon from './svg/pp_ui.svg?react';
import SnapshotDialog from './SnapshotDialog'; import SnapshotDialog from './SnapshotDialog';
import { css } from '@emotion/react'; import { css } from '@emotion/react';
import { setDefaultModGuiPreference } from './ModGuiHost'; import { setDefaultModGuiPreference } from './ModGuiHost';
import PluginNameDialog from './PluginNameDialog';
const SPLIT_CONTROLBAR_THRESHHOLD = 750; const SPLIT_CONTROLBAR_THRESHHOLD = 750;
const SHOW_ICON_THRESHHOLD = 475;
const DISPLAY_AUTHOR_THRESHHOLD = 750; const DISPLAY_AUTHOR_THRESHHOLD = 750;
const DISPLAY_AUTHOR_SPLIT_THRESHOLD = 500; const DISPLAY_AUTHOR_SPLIT_THRESHOLD = 500;
const HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK = 500; const HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK = 500;
@@ -128,6 +130,7 @@ interface MainState {
splitControlBar: boolean; splitControlBar: boolean;
displayAuthor: boolean; displayAuthor: boolean;
horizontalScrollLayout: boolean; horizontalScrollLayout: boolean;
canDisplayPluginIcon: boolean;
showMidiBindingsDialog: boolean; showMidiBindingsDialog: boolean;
screenHeight: number; screenHeight: number;
displayNameDialogOpen: boolean; displayNameDialogOpen: boolean;
@@ -147,6 +150,9 @@ export const MainPage =
getSplitToolbar() { getSplitToolbar() {
return this.windowSize.width < SPLIT_CONTROLBAR_THRESHHOLD && this.windowSize.height >= HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK; return this.windowSize.width < SPLIT_CONTROLBAR_THRESHHOLD && this.windowSize.height >= HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK;
} }
getShowPluginIcon() {
return this.windowSize.width > SHOW_ICON_THRESHHOLD;
}
getDisplayAuthor() { getDisplayAuthor() {
if (this.getSplitToolbar()) { if (this.getSplitToolbar()) {
return this.windowSize.width >= DISPLAY_AUTHOR_SPLIT_THRESHOLD; return this.windowSize.width >= DISPLAY_AUTHOR_SPLIT_THRESHOLD;
@@ -173,6 +179,7 @@ export const MainPage =
addMenuAnchorEl: null, addMenuAnchorEl: null,
splitControlBar: this.getSplitToolbar(), splitControlBar: this.getSplitToolbar(),
displayAuthor: this.getDisplayAuthor(), displayAuthor: this.getDisplayAuthor(),
canDisplayPluginIcon: this.getShowPluginIcon(),
horizontalScrollLayout: this.windowSize.height < HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK, horizontalScrollLayout: this.windowSize.height < HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK,
showMidiBindingsDialog: false, showMidiBindingsDialog: false,
screenHeight: this.windowSize.height, screenHeight: this.windowSize.height,
@@ -304,6 +311,7 @@ export const MainPage =
this.setState({ this.setState({
splitControlBar: this.getSplitToolbar(), splitControlBar: this.getSplitToolbar(),
displayAuthor: this.getDisplayAuthor(), displayAuthor: this.getDisplayAuthor(),
canDisplayPluginIcon: this.getShowPluginIcon(),
horizontalScrollLayout: this.windowSize.height < HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK, horizontalScrollLayout: this.windowSize.height < HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK,
screenHeight: this.windowSize.height screenHeight: this.windowSize.height
}); });
@@ -415,6 +423,7 @@ export const MainPage =
return pedalboardItem.uri; return pedalboardItem.uri;
} }
titleBar(pedalboardItem: PedalboardItem | null, canShowModUi: boolean): React.ReactNode { titleBar(pedalboardItem: PedalboardItem | null, canShowModUi: boolean): React.ReactNode {
let uiPlugin = pedalboardItem ? this.model.getUiPlugin(pedalboardItem?.uri) : null;
let title = ""; let title = "";
let author = ""; let author = "";
let infoPluginUri = ""; let infoPluginUri = "";
@@ -489,21 +498,45 @@ export const MainPage =
this.handleEditPluginDisplayName(); this.handleEditPluginDisplayName();
}} }}
> >
<div style={{ flex: "0 1 auto" }}> <div style={{ flex: "0 1 auto", display: "flex", flexFlow: "row nowrap", alignItems: "center", overflow: "hidden" }}>
<span> {this.state.canDisplayPluginIcon && uiPlugin && (
<span className={classes.title}>{title}</span> <div style={{ flex: "0 0 auto", marginRight: 8 }}>
{this.state.displayAuthor && ( <PluginIcon
<span className={classes.author}>{author}</span> pluginType={uiPlugin.plugin_type}
)} color={getIconColor(pedalboardItem?.iconColor ?? "")}
</span> size={24}
/>
</div>
)}
<div style={{ flex: "0 1 auto", minWidth: 0, overflow: "hidden", textOverflow: "ellipsis" }}>
<span>
<span className={classes.title}>{title}</span>
{this.state.displayAuthor && (
<span className={classes.author}>{author}</span>
)}
</span>
</div>
</div> </div>
</ButtonBase>) </ButtonBase>)
: ( : (
<div style={{ flex: "0 1 auto", paddingLeft: 8, paddingRight: 8, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis" }}> <div style={{ flex: "0 1 auto", display: "flex", flexFlow: "row nowrap", alignItems: "center", overflow: "hidden" }}>
<span className={classes.title}>{title}</span> {this.state.canDisplayPluginIcon && uiPlugin && (
{this.state.displayAuthor && ( <div style={{ flex: "0 0 auto", marginRight: 8 }}>
<span className={classes.author}>{author}</span> <PluginIcon
pluginType={uiPlugin.plugin_type}
color={getIconColor(pedalboardItem?.iconColor ?? "")}
size={24}
/>
</div>
)} )}
<div style={{ flex: "0 1 auto", minWidth: 0, overflow: "hidden", textOverflow: "ellipsis" }}>
<span>
<span className={classes.title}>{title}</span>
{this.state.displayAuthor && (
<span className={classes.author}>{author}</span>
)}
</span>
</div>
</div> </div>
)} )}
@@ -770,17 +803,14 @@ export const MainPage =
<SnapshotDialog open={this.state.snapshotDialogOpen} onOk={() => this.onSnapshotDialogOk()} /> <SnapshotDialog open={this.state.snapshotDialogOpen} onOk={() => this.onSnapshotDialogOk()} />
)} )}
{(this.state.displayNameDialogOpen) && ( {(this.state.displayNameDialogOpen) && (
<RenameDialog open={this.state.displayNameDialogOpen} <PluginNameDialog open={this.state.displayNameDialogOpen}
pedalboardItem={pedalboardItem}
allowEmpty={true} allowEmpty={true}
defaultName={pedalboardItem?.title ?? ""}
title="Plugin Display Name"
acceptActionName="OK"
onClose={() => this.setState({ displayNameDialogOpen: false })} onClose={() => this.setState({ displayNameDialogOpen: false })}
onOk={(newName: string) => { onApply={(newName: string, color: string) => {
if (pedalboardItem) { if (pedalboardItem) {
this.model.setPedalboardItemTitle(pedalboardItem.instanceId, newName); this.model.setPedalboardItemTitle(pedalboardItem.instanceId, newName, color);
} }
this.setState({ displayNameDialogOpen: false });
}} }}
/> />
)} )}
+2
View File
@@ -80,6 +80,7 @@ export class PedalboardItem implements Deserializable<PedalboardItem> {
this.lilvPresetUri = input.lilvPresetUri; this.lilvPresetUri = input.lilvPresetUri;
this.pathProperties = input.pathProperties; this.pathProperties = input.pathProperties;
this.useModUi = input.useModUi ?? false; this.useModUi = input.useModUi ?? false;
this.iconColor = input.iconColor??"";
return this; return this;
} }
@@ -215,6 +216,7 @@ export class PedalboardItem implements Deserializable<PedalboardItem> {
lilvPresetUri: string = ""; lilvPresetUri: string = "";
pathProperties: {[Name: string]: string} = {}; pathProperties: {[Name: string]: string} = {};
useModUi: boolean = false; // true if this item should use the mod-ui. useModUi: boolean = false; // true if this item should use the mod-ui.
iconColor: string = "";
}; };
export class SnapshotValue { export class SnapshotValue {
File diff suppressed because it is too large Load Diff
+203 -90
View File
@@ -21,7 +21,7 @@
import { Theme } from '@mui/material/styles'; import { Theme } from '@mui/material/styles';
import SaveIconOutline from '@mui/icons-material/Save'; import SaveIconOutline from '@mui/icons-material/Save';
import WithStyles from './WithStyles'; import WithStyles from './WithStyles';
import {createStyles} from './WithStyles'; import { createStyles } from './WithStyles';
import { withStyles } from "tss-react/mui"; import { withStyles } from "tss-react/mui";
import { PiPedalModel, PiPedalModelFactory, PresetIndex } from './PiPedalModel'; import { PiPedalModel, PiPedalModelFactory, PresetIndex } from './PiPedalModel';
@@ -41,7 +41,7 @@ import { BankIndex } from './Banks';
import SnapshotEditor from './SnapshotEditor'; import SnapshotEditor from './SnapshotEditor';
import { Snapshot } from './Pedalboard'; import { Snapshot } from './Pedalboard';
import JackStatusView from './JackStatusView'; import JackStatusView from './JackStatusView';
import {IDialogStackable, popDialogStack, pushDialogStack} from './DialogStack'; import { IDialogStackable, popDialogStack, pushDialogStack } from './DialogStack';
import { css } from '@emotion/react'; import { css } from '@emotion/react';
@@ -75,7 +75,8 @@ interface PerformanceViewProps extends WithStyles<typeof styles> {
} }
interface PerformanceViewState { interface PerformanceViewState {
wrapSelects: boolean wrapSelects: boolean;
largeAppBar: boolean;
presets: PresetIndex; presets: PresetIndex;
banks: BankIndex; banks: BankIndex;
showSnapshotEditor: boolean; showSnapshotEditor: boolean;
@@ -86,7 +87,7 @@ interface PerformanceViewState {
export const PerformanceView = export const PerformanceView =
withStyles( withStyles(
class extends ResizeResponsiveComponent<PerformanceViewProps, PerformanceViewState> implements IDialogStackable { class extends ResizeResponsiveComponent<PerformanceViewProps, PerformanceViewState> implements IDialogStackable {
model: PiPedalModel; model: PiPedalModel;
@@ -100,6 +101,7 @@ export const PerformanceView =
presets: this.model.presets.get(), presets: this.model.presets.get(),
banks: this.model.banks.get(), banks: this.model.banks.get(),
wrapSelects: false, wrapSelects: false,
largeAppBar: this.updateAppBarZoom(),
showSnapshotEditor: false, showSnapshotEditor: false,
showStatusMonitor: this.model.showStatusMonitor.get(), showStatusMonitor: this.model.showStatusMonitor.get(),
snapshotEditorIndex: 0, snapshotEditorIndex: 0,
@@ -112,6 +114,9 @@ export const PerformanceView =
} }
updateAppBarZoom() {
return this.windowSize.width > 700;
}
showStatusMonitorHandler() { showStatusMonitorHandler() {
this.setState({ this.setState({
@@ -119,7 +124,7 @@ export const PerformanceView =
}); });
} }
getTag() { return "performView"} getTag() { return "performView" }
isOpen() { return this.props.open } isOpen() { return this.props.open }
onDialogStackClose() { onDialogStackClose() {
this.props.onClose(); this.props.onClose();
@@ -128,15 +133,13 @@ export const PerformanceView =
private hasHooks: boolean = false; private hasHooks: boolean = false;
updateHooks() : void { updateHooks(): void {
let wantHooks = this.mounted && this.props.open; let wantHooks = this.mounted && this.props.open;
if (wantHooks !== this.hasHooks) if (wantHooks !== this.hasHooks) {
{
this.hasHooks = wantHooks; this.hasHooks = wantHooks;
if (this.hasHooks) if (this.hasHooks) {
{
pushDialogStack(this); pushDialogStack(this);
} else { } else {
@@ -144,9 +147,12 @@ export const PerformanceView =
} }
} }
} }
onWindowSizeChanged(width: number, height: number): void { onWindowSizeChanged(width: number, height: number): void {
this.setState({ wrapSelects: width < 700 }); this.setState({
wrapSelects: width < 700,
largeAppBar: this.updateAppBarZoom(),
});
} }
onPresetsChanged(newValue: PresetIndex) { onPresetsChanged(newValue: PresetIndex) {
@@ -156,7 +162,7 @@ export const PerformanceView =
this.setState({ banks: this.model.banks.get() }) this.setState({ banks: this.model.banks.get() })
} }
onPresetChangedChanged(newValue: boolean) { onPresetChangedChanged(newValue: boolean) {
this.setState({ presetModified:newValue}); this.setState({ presetModified: newValue });
} }
private mounted: boolean = false; private mounted: boolean = false;
@@ -216,28 +222,23 @@ export const PerformanceView =
}); });
return true; return true;
} }
handleSnapshotEditOk(index: number, name: string, color: string,newSnapshots: (Snapshot|null)[]) handleSnapshotEditOk(index: number, name: string, color: string, newSnapshots: (Snapshot | null)[]) {
{
// results have been sent to the server. we would perform a short-cut commit to our local state, to avoid laggy display updates. // results have been sent to the server. we would perform a short-cut commit to our local state, to avoid laggy display updates.
// but we don't actually have that in our state. // but we don't actually have that in our state.
} }
handlePreviousBank() handlePreviousBank() {
{
this.model.previousBank(); this.model.previousBank();
} }
handleNextBank() handleNextBank() {
{
this.model.nextBank(); this.model.nextBank();
} }
handlePreviousPreset() handlePreviousPreset() {
{
this.model.previousPreset(); this.model.previousPreset();
} }
handleNextPreset() handleNextPreset() {
{
this.model.nextPreset(); this.model.nextPreset();
} }
@@ -246,54 +247,42 @@ export const PerformanceView =
let wrapSelects = this.state.wrapSelects; 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";
return ( return (
<div className={classes.frame} style={{ overflow: "clip", height: "100%" }} > <div className={classes.frame} style={{ overflow: "clip", height: "100%" }} >
<div className={classes.frame} style={{ overflow: "clip", height: "100%" }} > <div className={classes.frame} style={{ overflow: "clip", height: "100%" }} >
<AppBar id="select-plugin-dialog-title" <AppBar id="select-plugin-dialog-title"
style={{ style={{
position: "static", flex: "0 0 auto", height: wrapSelects ? 108 : 58, position: "static", flex: "0 0 auto", height: undefined,
display: this.state.showSnapshotEditor ? "none" : undefined display: this.state.showSnapshotEditor ? "none" : undefined
}} }}
> >
<div style={{ {this.state.largeAppBar ? (
display: "flex", flexFlow: "row nowrap", alignContent: "center",
paddingTop: 3, paddingBottom: 3, paddingRight: 8, paddingLeft: 8,
alignItems: "start"
}}>
<IconButtonEx tooltip="Back" aria-label="menu" color="inherit"
onClick={() => { this.props.onClose(); }}
style={{
position: "relative",top: 3,
flex: "0 0 auto" }} >
<ArrowBackIcon />
</IconButtonEx>
<div style={{ <div style={{
flex: "1 1 1px", display: "flex", display: "flex", flexFlow: "row nowrap", alignContent: "center",
flexFlow: wrapSelects ? "column nowrap" : "row nowrap", paddingTop: 3, paddingBottom: 3, paddingRight: 8, paddingLeft: 8,
alignItems: wrapSelects ? "stretch" : undefined, alignItems: "start", justifyContent: "space-between",
justifyContent: wrapSelects ? undefined : "space-evenly",
marginLeft: 8,
gap: 8
}}> }}>
{/********* BANKS *******************/} <IconButtonEx tooltip="Back" aria-label="menu" color="inherit"
<div style={{ flex: "1 1 1px", display: "flex", flexFlow: "row nowrap", maxWidth: 500 }}> onClick={() => { this.props.onClose(); }}
<IconButtonEx tooltip="Previous bank" style={{
aria-label="previous-bank" position: "relative", top: 3,
onClick={() => { this.handlePreviousBank(); }} flex: "0 0 auto"
size="medium" }} >
color="inherit" <ArrowBackIcon fontSize="large" />
style={{ </IconButtonEx>
borderTopRightRadius: "3px", borderBottomRightRadius: "3px", marginRight: 4
}}
>
<ArrowBackIosIcon style={{ opacity: 0.75 }} />
</IconButtonEx>
{/********* BANKS *******************/}
<div style={{
flex: "1 1 auto", display: "flex", flexFlow: "row nowrap", maxWidth: 300, alignItems: "center",
}}>
<Select variant="standard" <Select variant="standard"
className={classes.select} className={classes.select}
style={{ flex: "1 1 1px", width: "100%", position: "relative", top: 0, color: "#FFFFFF" }} disabled={false} style={{
flex: "1 1 1px", width: "100%", position: "relative", top: 0, color: "#FFFFFF",
fontSize: "2.0rem",
}} disabled={false}
displayEmpty displayEmpty
onClose={(e) => this.handleBankSelectClose(e)} onClose={(e) => this.handleBankSelectClose(e)}
value={banks.selectedBank === 0 ? undefined : banks.selectedBank} value={banks.selectedBank === 0 ? undefined : banks.selectedBank}
@@ -312,17 +301,6 @@ export const PerformanceView =
}) })
} }
</Select> </Select>
<IconButtonEx tooltip="Next bank"
aria-label="next-bank"
onClick={() => { this.handleNextBank(); }}
color="inherit"
style={{ borderTopLeftRadius: "3px", borderBottomLeftRadius: "3px", marginLeft: 4 }}
>
<ArrowForwardIosIcon style={{ opacity: 0.75 }} />
</IconButtonEx>
{/** spacer */} {/** spacer */}
<IconButtonEx <IconButtonEx
tooltip="Next bank" tooltip="Next bank"
@@ -330,29 +308,31 @@ export const PerformanceView =
onClick={() => { this.handleNextBank(); }} onClick={() => { this.handleNextBank(); }}
size="medium" size="medium"
color="inherit" color="inherit"
style={{visibility: "hidden"}} style={{ visibility: "hidden" }}
> >
<ArrowForwardIosIcon style={{ opacity: 0.75 }} /> <ArrowForwardIosIcon style={{ opacity: 0.75 }} fontSize="large" />
</IconButtonEx> </IconButtonEx>
</div> </div>
{/********* PRESETS *******************/} {/********* PRESETS *******************/}
<div style={{ flex: "1 1 1px", display: "flex", flexFlow: "row nowrap", maxWidth: 500 }}> <div style={{
flex: "2 2 auto", display: "flex", flexFlow: "row nowrap", alignItems: "center",
}}>
<IconButtonEx tooltip="Previous preset" <IconButtonEx tooltip="Previous preset"
aria-label="previous-preset" aria-label="previous-preset"
onClick={() => { this.handlePreviousPreset(); }} onClick={() => { this.handlePreviousPreset(); }}
color="inherit" color="inherit"
style={{ borderTopRightRadius: "3px", borderBottomRightRadius: "3px", marginRight: 4 }} style={{ borderTopRightRadius: "3px", borderBottomRightRadius: "3px", marginRight: 4 }}
> >
<ArrowBackIosIcon style={{ opacity: 0.75 }} /> <ArrowBackIosIcon style={{ opacity: 0.75 }} fontSize={appBarIconSize} />
</IconButtonEx> </IconButtonEx>
<Select variant="standard" <Select variant="standard"
className={classes.select} className={classes.select}
style={{ style={{
flex: "1 1 1px", width: "100%", color: "#FFFFFF" flex: "1 1 1px", width: "100%", color: "#FFFFFF",
fontSize: (this.state.largeAppBar ? "2.0rem" : undefined),
}} disabled={false} }} disabled={false}
displayEmpty displayEmpty
onClose={(e) => this.handlePresetSelectClose(e)} onClose={(e) => this.handlePresetSelectClose(e)}
@@ -367,8 +347,7 @@ export const PerformanceView =
{ {
presets.presets.map((preset) => { presets.presets.map((preset) => {
let name = preset.name; let name = preset.name;
if (this.state.presets.selectedInstanceId === preset.instanceId && this.state.presetModified) if (this.state.presets.selectedInstanceId === preset.instanceId && this.state.presetModified) {
{
name += "*"; name += "*";
} }
return ( return (
@@ -386,7 +365,7 @@ export const PerformanceView =
color="inherit" color="inherit"
style={{ borderTopLeftRadius: "3px", borderBottomLeftRadius: "3px", marginLeft: 4 }} style={{ borderTopLeftRadius: "3px", borderBottomLeftRadius: "3px", marginLeft: 4 }}
> >
<ArrowForwardIosIcon style={{ opacity: 0.75 }} /> <ArrowForwardIosIcon style={{ opacity: 0.75 }} fontSize={appBarIconSize} />
</IconButtonEx> </IconButtonEx>
<IconButtonEx tooltip="Save preset" <IconButtonEx tooltip="Save preset"
@@ -394,16 +373,150 @@ export const PerformanceView =
onClick={() => { this.model.saveCurrentPreset(); }} onClick={() => { this.model.saveCurrentPreset(); }}
size="medium" size="medium"
color="inherit" color="inherit"
style={{flexShrink: 0,visibility: this.state.presetModified? "visible": "hidden"}} style={{ flexShrink: 0, visibility: this.state.presetModified ? "visible" : "hidden" }}
> >
<SaveIconOutline style={{ opacity: 0.75 }} color="inherit" /> <SaveIconOutline style={{ opacity: 0.75 }} color="inherit" fontSize={appBarIconSize} />
</IconButtonEx> </IconButtonEx>
</div> </div>
</div> </div>
</div> ) : (
<div style={{
display: "flex", flexFlow: "row nowrap", alignContent: "center",
paddingTop: 3, paddingBottom: 3, paddingRight: 8, paddingLeft: 8,
alignItems: "start",
}}>
<IconButtonEx tooltip="Back" aria-label="menu" color="inherit"
onClick={() => { this.props.onClose(); }}
style={{
position: "relative", top: 3,
flex: "0 0 auto"
}} >
<ArrowBackIcon fontSize={appBarIconSize} />
</IconButtonEx>
<div style={{
flex: "1 1 1px", display: "flex",
flexFlow: wrapSelects ? "column nowrap" : "row nowrap",
alignItems: wrapSelects ? "stretch" : undefined,
justifyContent: wrapSelects ? undefined : "space-evenly",
marginLeft: 8,
gap: 8
}}>
{/********* BANKS *******************/}
<div style={{
flex: "1 1 auto", display: "flex", flexFlow: "row nowrap", maxWidth: 500,
}}>
<Select variant="standard"
className={classes.select}
style={{
flex: "1 1 1px", width: "100%", position: "relative", top: 0, color: "#FFFFFF",
fontSize: (this.state.largeAppBar ? "2.0rem" : undefined),
}} disabled={false}
displayEmpty
onClose={(e) => this.handleBankSelectClose(e)}
value={banks.selectedBank === 0 ? undefined : banks.selectedBank}
inputProps={{
classes: { icon: classes.select_icon },
'aria-label': "Select preset"
}}
>
{
banks.entries.map((entry) => {
return (
<MenuItem key={entry.instanceId} value={entry.instanceId} >
{entry.name}
</MenuItem>
);
})
}
</Select>
{/** spacer */}
<IconButtonEx
tooltip="Next bank"
aria-label="next-bank"
onClick={() => { this.handleNextBank(); }}
size="medium"
color="inherit"
style={{ visibility: "hidden" }}
>
<ArrowForwardIosIcon style={{ opacity: 0.75 }} fontSize={appBarIconSize} />
</IconButtonEx>
</div>
{/********* PRESETS *******************/}
<div style={{
flex: "2 2 auto", display: "flex", flexFlow: "row nowrap", maxWidth: 500,
}}>
<IconButtonEx tooltip="Previous preset"
aria-label="previous-preset"
onClick={() => { this.handlePreviousPreset(); }}
color="inherit"
style={{ borderTopRightRadius: "3px", borderBottomRightRadius: "3px", marginRight: 4 }}
>
<ArrowBackIosIcon style={{ opacity: 0.75 }} fontSize={appBarIconSize} />
</IconButtonEx>
<Select variant="standard"
className={classes.select}
style={{
flex: "1 1 1px", width: "100%", color: "#FFFFFF",
fontSize: (this.state.largeAppBar ? "2.0rem" : undefined),
}} disabled={false}
displayEmpty
onClose={(e) => this.handlePresetSelectClose(e)}
value={presets.selectedInstanceId === 0 ? '' : presets.selectedInstanceId}
inputProps={{
classes: { icon: classes.select_icon },
'aria-label': "Select preset"
}
}
>
{
presets.presets.map((preset) => {
let name = preset.name;
if (this.state.presets.selectedInstanceId === preset.instanceId && this.state.presetModified) {
name += "*";
}
return (
<MenuItem key={preset.instanceId} value={preset.instanceId} >
{name}
</MenuItem>
);
})
}
</Select>
<IconButtonEx tooltip="Next preset"
aria-label="next-preset"
onClick={() => { this.handleNextPreset(); }}
color="inherit"
style={{ borderTopLeftRadius: "3px", borderBottomLeftRadius: "3px", marginLeft: 4 }}
>
<ArrowForwardIosIcon style={{ opacity: 0.75 }} fontSize={appBarIconSize} />
</IconButtonEx>
<IconButtonEx tooltip="Save preset"
aria-label="save-preset"
onClick={() => { this.model.saveCurrentPreset(); }}
size="medium"
color="inherit"
style={{ flexShrink: 0, visibility: this.state.presetModified ? "visible" : "hidden" }}
>
<SaveIconOutline style={{ opacity: 0.75 }} color="inherit" fontSize={appBarIconSize} />
</IconButtonEx>
</div>
</div>
</div>
)}
</AppBar > </AppBar >
<div style={{ flex: "1 0 auto", display: "flex", marginTop: 16,marginBottom: 20 }}> <div style={{ flex: "1 0 auto", display: "flex", marginTop: 16, marginBottom: 20 }}>
<SnapshotPanel onEdit={(index) => { return this.handleOnEdit(index); }} /> <SnapshotPanel onEdit={(index) => { return this.handleOnEdit(index); }} />
</div> </div>
@@ -413,15 +526,15 @@ export const PerformanceView =
)} )}
</div> </div>
{this.state.showSnapshotEditor && ( {this.state.showSnapshotEditor && (
<SnapshotEditor snapshotIndex={this.state.snapshotEditorIndex} <SnapshotEditor snapshotIndex={this.state.snapshotEditorIndex}
onClose={() => { onClose={() => {
this.setState({ showSnapshotEditor: false }); this.setState({ showSnapshotEditor: false });
}} }}
onOk={(index,name,color,newSnapshots)=>{ onOk={(index, name, color, newSnapshots) => {
this.setState({ showSnapshotEditor: false}); this.setState({ showSnapshotEditor: false });
this.handleSnapshotEditOk(index,name,color,newSnapshots); this.handleSnapshotEditOk(index, name, color, newSnapshots);
}} }}
/> />
)} )}
</div > </div >
) )
+8 -4
View File
@@ -491,6 +491,9 @@ export class PiPedalModel //implements PiPedalModel
webSocket?: PiPedalSocket; webSocket?: PiPedalSocket;
static getInstance(): PiPedalModel {
return PiPedalModelFactory.getInstance();
}
hasTone3000Auth: ObservableProperty<boolean> = new ObservableProperty<boolean>(false); hasTone3000Auth: ObservableProperty<boolean> = new ObservableProperty<boolean>(false);
canKeepScreenOn: boolean = false; canKeepScreenOn: boolean = false;
@@ -3376,9 +3379,9 @@ export class PiPedalModel //implements PiPedalModel
this.cancelOnNetworkChanging(); this.cancelOnNetworkChanging();
}, },
30 * 1000); 30 * 1000);
} }
setPedalboardItemTitle(instanceId: number, title: string): void {
setPedalboardItemTitle(instanceId: number, title: string, iconColor: string): void {
let pedalboard = this.pedalboard.get(); let pedalboard = this.pedalboard.get();
if (!pedalboard) { if (!pedalboard) {
throw new PiPedalStateError("Pedalboard not loaded."); throw new PiPedalStateError("Pedalboard not loaded.");
@@ -3386,13 +3389,14 @@ export class PiPedalModel //implements PiPedalModel
let newPedalboard = pedalboard.clone(); let newPedalboard = pedalboard.clone();
this.updateVst3State(newPedalboard); this.updateVst3State(newPedalboard);
let item = newPedalboard.getItem(instanceId); let item = newPedalboard.getItem(instanceId);
if (item.title === title) { if (item.title === title && item.iconColor === iconColor) {
return; return;
} }
item.title = title; item.title = title;
item.iconColor = iconColor;
this.pedalboard.set(newPedalboard); this.pedalboard.set(newPedalboard);
// notify the server. // notify the server.
this.webSocket?.send("setPedalboardItemTitle", { instanceId: instanceId, title: title }); this.webSocket?.send("setPedalboardItemTitle", { instanceId: instanceId, title: title, colorKey: iconColor });
} }
setAlsaSequencerConfiguration(alsaSequencerConfiguration: AlsaSequencerConfiguration): void { setAlsaSequencerConfiguration(alsaSequencerConfiguration: AlsaSequencerConfiguration): void {
this.webSocket?.send("setAlsaSequencerConfiguration", alsaSequencerConfiguration); this.webSocket?.send("setAlsaSequencerConfiguration", alsaSequencerConfiguration);
+16 -14
View File
@@ -307,7 +307,7 @@ const PluginControl =
e.stopPropagation(); e.stopPropagation();
} }
isValidPointer(e: PointerEvent<SVGSVGElement>): boolean { isValidPointer(e: PointerEvent): boolean {
if (e.pointerType === "mouse") { if (e.pointerType === "mouse") {
return e.button === 0; return e.button === 0;
} else if (e.pointerType === "pen") { } else if (e.pointerType === "pen") {
@@ -347,7 +347,7 @@ const PluginControl =
pointerId: number = 0; pointerId: number = 0;
pointerType: string = ""; pointerType: string = "";
isCapturedPointer(e: PointerEvent<SVGSVGElement>): boolean { isCapturedPointer(e: PointerEvent): boolean {
return this.mouseDown return this.mouseDown
&& e.pointerId === this.pointerId && e.pointerId === this.pointerId
&& e.pointerType === this.pointerType; && e.pointerType === this.pointerType;
@@ -361,7 +361,7 @@ const PluginControl =
pointersDown: number = 0; pointersDown: number = 0;
captureElement?: SVGSVGElement = undefined; captureElement?: SVGSVGElement = undefined;
onPointerDown(e: PointerEvent<SVGSVGElement>): void { onPointerDown(e: PointerEvent): void {
if (!this.mouseDown && this.isValidPointer(e)) { if (!this.mouseDown && this.isValidPointer(e)) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -387,7 +387,7 @@ const PluginControl =
this.mouseDown = true; this.mouseDown = true;
if (this.pointersDown === 1) { if (this.pointersDown === 1) {
this.isTap = true; this.isTap = true;
this.tapStartMs = Date.now(); this.tapStartMs = e.timeStamp;
} else { } else {
this.isTap = false; this.isTap = false;
} }
@@ -429,7 +429,7 @@ const PluginControl =
} }
onPointerLostCapture(e: PointerEvent<SVGSVGElement>) { onPointerLostCapture(e: PointerEvent) {
if (this.isCapturedPointer(e)) { if (this.isCapturedPointer(e)) {
if (this.pointersDown !== 0) { if (this.pointersDown !== 0) {
--this.pointersDown; --this.pointersDown;
@@ -442,7 +442,7 @@ const PluginControl =
} }
updateRange(e: PointerEvent<SVGSVGElement>): number { updateRange(e: PointerEvent): number {
let ultraHigh = false; let ultraHigh = false;
let high = false; let high = false;
@@ -486,9 +486,11 @@ const PluginControl =
} }
} }
} }
onPointerTap() { onPointerTap(e: PointerEvent) {
let tapTime = Date.now(); let tapTime = e.timeStamp;
let dT = tapTime - this.lastTapMs; let dT = tapTime - this.lastTapMs;
this.lastTapMs = tapTime; this.lastTapMs = tapTime;
if (dT < 500) { if (dT < 500) {
@@ -497,7 +499,7 @@ const PluginControl =
} }
private tapStartMs: number = 0; private tapStartMs: number = 0;
onPointerUp(e: PointerEvent<SVGSVGElement>) { onPointerUp(e: PointerEvent) {
if (this.isCapturedPointer(e)) { if (this.isCapturedPointer(e)) {
if (this.pointersDown !== 0) { if (this.pointersDown !== 0) {
@@ -514,9 +516,9 @@ const PluginControl =
this.releaseCapture(e); this.releaseCapture(e);
if (this.isTap) { if (this.isTap) {
let ms = Date.now() - this.tapStartMs; let ms = e.timeStamp - this.tapStartMs;
if (ms < 200) { if (ms < 200) {
this.onPointerTap(); this.onPointerTap(e);
} }
} }
// prevent click from firing on other elements // prevent click from firing on other elements
@@ -531,7 +533,7 @@ const PluginControl =
} }
} }
releaseCapture(e: PointerEvent<SVGSVGElement>) { releaseCapture(e: PointerEvent) {
let img = this.imgRef.current; let img = this.imgRef.current;
if (img && img.style) { if (img && img.style) {
@@ -556,7 +558,7 @@ const PluginControl =
clickSlop() { clickSlop() {
return 5; // maybe larger on touch devices. return 5; // maybe larger on touch devices.
} }
onPointerMove(e: PointerEvent<SVGSVGElement>): void { onPointerMove(e: PointerEvent): void {
if (this.isCapturedPointer(e)) { if (this.isCapturedPointer(e)) {
e.preventDefault(); e.preventDefault();
let dRange = this.updateRange(e) let dRange = this.updateRange(e)
@@ -1138,7 +1140,7 @@ const PluginControl =
); );
} }
/* /*
isSamePointer(PointerEvent<SVGSVGElement> e): boolean isSamePointer(PointerEvent e): boolean
{ {
return e.pointerId === this.pointerId return e.pointerId === this.pointerId
&& e.pointerType === this.pointerType; && e.pointerType === this.pointerType;
+54 -10
View File
@@ -23,7 +23,6 @@ import WithStyles from './WithStyles';
import {createStyles} from './WithStyles'; import {createStyles} from './WithStyles';
import { withStyles } from "tss-react/mui"; import { withStyles } from "tss-react/mui";
import { PiPedalModelFactory } from "./PiPedalModel";
import { PluginType } from './Lv2Plugin'; import { PluginType } from './Lv2Plugin';
@@ -67,6 +66,7 @@ import FxEmptyIcon from './svg/fx_empty.svg?react';
import FxTerminalIcon from './svg/fx_terminal.svg?react'; import FxTerminalIcon from './svg/fx_terminal.svg?react';
import { isDarkMode } from './DarkMode'; import { isDarkMode } from './DarkMode';
import { PiPedalModel } from './PiPedalModel';
export interface IconColorSelect { export interface IconColorSelect {
key: string, key: string,
@@ -80,21 +80,65 @@ export const getIconColorSelections = (darkMode?: boolean): IconColorSelect[] =>
let l: number; let l: number;
let c: number; let c: number;
if (darkMode) { if (darkMode) {
l = 0.8; l = 0.8
c = 0.25; c = 0.25;
} else { } else {
l = 0.5; l = 0.5;
c = 0.25; c = 0.20;
} }
for (let angle = 0; angle < 360; angle += 22.5) { for (let angle = 0; angle < 360; angle += 22.5) {
result.push( if (angle === 270-22.5)
{ {
key: "oklch" + Math.round(angle*10).toString(), // for some reason, this one is useless. Patch in a more useful light blue. :-/
value: ("oklch(" + l.toString() + " " + c.toString() + " " + (angle).toString() + ")") let a2 = 270;
let l2: number;
let c2: number;
if (darkMode) {
l2 = 0.9
c2 = 0.25;
} else {
l2 = 0.7;
c2 = 0.20;
} }
); result.push(
{
key: "oklch" + Math.round(angle*10).toString(),
value: ("oklch(" + l2.toString() + " " + c2.toString() + " " + (a2).toString() + ")")
}
);
} else if (angle === 270-45)
{
// for some reason, this one is useless. Patch in a more useful light blue. :-/
let a2 = 180;
let l2: number;
let c2: number;
if (darkMode) {
l2 = 0.95
c2 = 0.25;
} else {
l2 = 0.75;
c2 = 0.20;
}
result.push(
{
key: "oklch" + Math.round(angle*10).toString(),
value: ("oklch(" + l2.toString() + " " + c2.toString() + " " + (a2).toString() + ")")
}
);
} else {
result.push(
{
key: "oklch" + Math.round(angle*10).toString(),
value: ("oklch(" + l.toString() + " " + c.toString() + " " + (angle).toString() + ")")
}
);
}
} }
return result; return result;
@@ -317,7 +361,7 @@ export function SelectBaseIcon(plugin_type: PluginType): string {
export function SelectIconUri(plugin_type: PluginType) { export function SelectIconUri(plugin_type: PluginType) {
let icon = SelectBaseIcon(plugin_type); let icon = SelectBaseIcon(plugin_type);
return PiPedalModelFactory.getInstance().svgImgUrl(icon); return PiPedalModel.getInstance().svgImgUrl(icon);
} }
const PluginIcon = withStyles((props: PluginIconProps) => { const PluginIcon = withStyles((props: PluginIconProps) => {
+190
View File
@@ -0,0 +1,190 @@
// 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 React from 'react';
import Typography from '@mui/material/Typography';
import IconButtonEx from './IconButtonEx';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import IconColorDropdownButton, { DropdownAlignment } from './IconColorDropdownButton';
import InputLabel from '@mui/material/InputLabel';
import DialogEx from './DialogEx';
import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogContent';
import { nullCast } from './Utility';
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
import { PiPedalModel } from './PiPedalModel';
//import TextFieldEx from './TextFieldEx';
import TextField from '@mui/material/TextField';
import { PedalboardItem } from './Pedalboard';
export interface PluginNameDialogProps {
open: boolean,
pedalboardItem: PedalboardItem | null,
allowEmpty?: boolean,
label?: string,
onApply: (text: string, color: string) => void,
onClose: () => void
};
export interface PluginNameDialogState {
iconColor: string;
};
function isTouchUi() {
return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
}
export default class PluginNameDialog extends ResizeResponsiveComponent<PluginNameDialogProps, PluginNameDialogState> {
refText: React.RefObject<HTMLInputElement | null>;
constructor(props: PluginNameDialogProps) {
super(props);
this.state = {
iconColor: this.props.pedalboardItem?.iconColor ?? ""
};
this.refText = React.createRef<HTMLInputElement>();
}
mounted: boolean = false;
onWindowSizeChanged(width: number, height: number): void {
}
componentDidMount() {
super.componentDidMount();
this.mounted = true;
}
componentWillUnmount() {
super.componentWillUnmount();
this.mounted = false;
}
componentDidUpdate() {
}
checkForIllegalCharacters(filename: string) {
if (filename.indexOf('/') !== -1) {
throw new Error("Illegal character: '/'");
}
}
render() {
let props = this.props;
let { open, onClose, onApply } = props;
let model = PiPedalModel.getInstance();
if (!open) return null;
let pedalboardItem = props.pedalboardItem;
if (!pedalboardItem) return null;
let uiPlugin = model.getUiPlugin(pedalboardItem.uri);
if (uiPlugin === null) {
return null;
}
let defaultName = props.pedalboardItem?.title ?? "";
const handleClose = () => {
onClose();
};
const handleOk = () => {
let text = nullCast(this.refText.current).value;
text = text.trim();
try {
this.checkForIllegalCharacters(text);
} catch (e: any) {
let model: PiPedalModel = PiPedalModel.getInstance();
model.showAlert(e.toString());
return;
}
if (text.length === 0 && props.allowEmpty !== true) return;
onApply(text, this.state.iconColor);
onClose();
}
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): void => {
// 'keypress' event misbehaves on mobile so we track 'Enter' key via 'keydown' event
if (event.key === 'Enter') {
event.preventDefault();
event.stopPropagation();
handleOk();
}
};
return (
<DialogEx tag="nameDialog" open={open} maxWidth="md" onClose={handleClose} aria-labelledby="Rename-dialog-title"
style={{ userSelect: "none" }}
onEnterKey={() => { }}
>
<DialogTitle style={{paddingBottom: 0, paddingTop: 8}}>
<div style={{display: "flex",flexFlow: "row nowrap", alignItems: "center"}} >
<IconButtonEx tooltip="Back" edge="start" color="inherit" onClick={() => onClose()} aria-label="back"
>
<ArrowBackIcon />
</IconButtonEx>
<Typography noWrap variant="h6" >
Plugin Properties
</Typography>
</div>
</DialogTitle>
<DialogContent >
<div style={{ display: "flex", flexFlow: "column nowrap", alignItems: "start", gap: 8, marginBottom: 8 }}>
<TextField
style={{ flex: "1 1 auto", width: 240 }}
autoFocus={!isTouchUi()}
onKeyDown={handleKeyDown}
variant="standard"
autoComplete="off"
autoCorrect="off"
onChange={(e) => { this.props.onApply(e.target.value.toString(), this.state.iconColor) }}
autoCapitalize="off"
slotProps={{
input: {
style: { scrollMargin: 24 }
},
inputLabel: {
shrink: true
}
}}
id="name"
type="text"
label="Plugin Display name"
placeholder={uiPlugin?.label ?? ""}
defaultValue={defaultName}
inputRef={this.refText}
/>
<div>
<InputLabel style={{ fontSize: "0.75rem", fontWeight: 400, marginTop: 16 }}>Icon color</InputLabel>
<IconColorDropdownButton colorKey={this.state.iconColor}
onColorChange={(selection) => {
if (this.props.pedalboardItem) {
this.setState({ iconColor: selection.key });
onApply(this.props.pedalboardItem.title, selection.key);
}
}}
pluginType={uiPlugin.plugin_type}
dropdownAlignment={DropdownAlignment.SE}
/>
</div>
</div>
</DialogContent>
</DialogEx>
);
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2022 Robin Davies // Copyright (c) Robin E. R. Davies
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy of // 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 // this software and associated documentation files (the "Software"), to deal in
@@ -57,6 +57,7 @@ interface HoverProps extends WithStyles<typeof styles> {
selected: boolean; selected: boolean;
showHover?: boolean; showHover?: boolean;
borderRadius?: number; borderRadius?: number;
clipChildren?: boolean;
children?: React.ReactNode; children?: React.ReactNode;
} }