Recover Info Dialog.

This commit is contained in:
Robin E. R. Davies
2025-09-08 06:57:06 -04:00
parent d87d8f83b7
commit c6b0f5f5a6
6 changed files with 2111 additions and 113 deletions
+7
View File
@@ -236,6 +236,7 @@ void Storage::MaybeCopyDefaultPresets()
fs::copy(presetsConfigDirectory / "Default+Bank.bank", presetsDirectory / "Default+Bank.bank");
}
}
void Storage::UpgradeFactoryPresets()
{
auto presetsDirectory = this->GetPresetsDirectory();
@@ -254,6 +255,12 @@ void Storage::UpgradeFactoryPresets()
// Maybe install or upgrade factory presets.
if (defaultConfigPresetsVersion.Version() > presetsVersion.Version() || defaultConfigPresetsVersion.Version() == 0)
{
// remove TooB ML README.md
fs::remove("/usr/lib/lv2/ToobAmp.lv2/models/tones/README.md");
fs::remove("/var/pipedal/audio_uploads/ToobMlModels/model.index");
fs::remove("/var/pipedal/audio_uploads/ToobMlModels/README.md");
fs::remove("/var/pipedal/audio_uploads/ToobMlModels/model.index");
std::string name = "Factory Presets";
BankFile newFactoryPresets;
+38 -10
View File
@@ -312,7 +312,14 @@ public:
auto mimeType = GetMimeType(path);
if (mimeType.empty())
{
throw PiPedalException("Can't download files of this type.");
if (isInfoFile(path))
{
mimeType = "text/plain";
}
else
{
throw PiPedalException("Can't download files of this type.");
}
}
res.set(HttpField::content_type, mimeType);
res.set(HttpField::cache_control, "no-cache");
@@ -403,6 +410,21 @@ public:
path));
}
bool isInfoFile(const fs::path &path)
{
auto extension = path.extension();
if (extension == ".md" || extension == ".txt")
{
return true;
}
auto filename = path.stem();
if (filename == "LICENSE" || filename == "README") {
return true;
}
return false;
}
virtual void get_response(
const uri &request_uri,
HttpRequest &req,
@@ -417,9 +439,6 @@ public:
{
fs::path path = request_uri.query("path");
bool t = this->model->IsInUploadsDirectory(path);
std::cout << (t ? "true" : "false") << std::endl;
(void)t;
if (!fs::exists(path) || !this->model->IsInUploadsDirectory(path) || HasDotDot(path))
{
throw PiPedalException("File not found.");
@@ -427,7 +446,14 @@ public:
auto mimeType = GetMimeType(path);
if (mimeType.empty())
{
throw PiPedalException("Can't download files of this type.");
if (isInfoFile(path))
{
mimeType = "text/plain";
}
else
{
throw PiPedalException("Can't download files of this type.");
}
}
res.set(HttpField::content_type, mimeType);
res.set(HttpField::cache_control, "no-cache");
@@ -739,17 +765,19 @@ public:
{
std::string segment = request_uri.segment(1);
if (segment == "Tone3000Auth") {
// https://www.tone3000.com/api/v1/auth?redirect_url=http://10.0.0.151:8080/var/Tone3000Auth&otp_only=true
if (segment == "Tone3000Auth")
{
// https://www.tone3000.com/api/v1/auth?redirect_url=http://10.0.0.151:8080/var/Tone3000Auth&otp_only=true
std::string apiKey = request_uri.query("api_key");
model->SetTone3000Auth(apiKey);
res.set(HttpField::content_type, "application/json");
res.set(HttpField::cache_control, "no-cache");
res.setBody("\"OK\"");
} else if (segment == "uploadPluginPresets")
}
else if (segment == "uploadPluginPresets")
{
PluginPresets presets;
fs::path filePath = req.get_body_temporary_file();
@@ -921,7 +949,7 @@ public:
{
fs::path inputPath{inputFile};
std::string extension = inputPath.extension();
if (extensionChecker.IsValidExtension(extension))
if (extensionChecker.IsValidExtension(extension) || isInfoFile(inputFile))
{
auto si = zipFile->GetFileInputStream(inputFile);
std::string path = this->model->UploadUserFile(directory, instanceId, patchProperty, inputFile, si, zipFile->GetFileSize(inputFile));
+1839 -100
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -20,9 +20,11 @@
"@types/react-window": "^1.8.8",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-markdown": "^10.1.0",
"react-remark": "^2.1.0",
"react-virtualized-auto-sizer": "^1.0.25",
"react-window": "^1.8.11",
"rehype-external-links": "^3.0.0",
"tss-react": "^4.9.15"
},
"devDependencies": {
+36 -3
View File
@@ -22,6 +22,8 @@ import React from 'react';
import { createStyles } from './WithStyles';
// import Tone3000Dialog from './Tone3000Dialog';
import TextInfoDialog from './TextInfoDialog';
import Tone3000HelpDialog from './Tone3000HelpDialog';
import GuitarMLHelpDialog from './GuitarMlHelpDialog';
import HelpOutlineIcon from '@mui/icons-material/HelpOutline';
@@ -172,7 +174,9 @@ export interface FilePropertyDialogState {
selectedFiles: string[],
//openTone3000Dialog: boolean,
openTone3000Help: boolean,
openGuitarMlHelp: boolean
openGuitarMlHelp: boolean,
textFileName? : string;
};
@@ -702,7 +706,9 @@ export default withStyles(
if (this.state.previousSelection == selectedItem) {
return;
}
this.props.onApply(fileProperty, selectedItem);
if (!this.isLicenseFile(selectedItem) && !this.isFolderArtwork(selectedItem)) {
this.props.onApply(fileProperty, selectedItem);
}
this.setState({previousSelection: selectedItem});
}
@@ -1754,9 +1760,32 @@ export default withStyles(
onClose={() => this.setState({ openGuitarMlHelp: false })}
/>
)}
{this.state.textFileName !== undefined && (
<TextInfoDialog open={true}
title={pathFileNameOnly(this.state.textFileName)}
fileName={this.state.textFileName} onClose={() => this.setState({ textFileName: undefined })}
/>
)}
</DialogEx>
);
}
isLicenseFile(fileName: string) {
let extension = pathExtension(fileName);
if (extension === ".txt" || extension === ".md"){
return true;
}
let fileNameOnly = pathFileNameOnly(fileName);
if (fileNameOnly === "LICENSE" || fileNameOnly === "README")
{
return true;
}
return false;
}
handleShowTextFile(fileName: string) {
this.setState({ textFileName: fileName });
}
openSelectedFile(): void {
if (this.state.multiSelect || this.state.reordering) {
return;
@@ -1768,7 +1797,11 @@ export default withStyles(
this.requestFiles(this.state.selectedFile);
this.setState({ navDirectory: this.state.selectedFile });
} else {
this.props.onOk(this.props.fileProperty, this.state.selectedFile);
if (this.isLicenseFile(this.state.selectedFile)) {
this.handleShowTextFile(this.state.selectedFile);
} else {
this.props.onOk(this.props.fileProperty, this.state.selectedFile);
}
}
}
+189
View File
@@ -0,0 +1,189 @@
// Copyright (c) 2025 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, { SyntheticEvent } from 'react';
import IconButtonEx from './IconButtonEx';
import Typography from '@mui/material/Typography';
import { PiPedalModel, PiPedalModelFactory, State } from './PiPedalModel';
import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import ReactMarkdown from 'react-markdown';
import rehypeExternalLinks from 'rehype-external-links'
import { PiPedalError } from './PiPedalError';
import DialogEx from './DialogEx';
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
import Slide, { SlideProps } from '@mui/material/Slide';
interface TextInfoDialogProps {
open: boolean;
fileName: string;
title: string;
onClose: () => void;
};
interface TextInfoDialogState {
textFileContent: string;
fullScreen: boolean;
};
const Transition = React.forwardRef(function Transition(
props: SlideProps, ref: React.Ref<unknown>
) {
return (<Slide direction="up" ref={ref} {...props} />);
});
const TextInfoDialog = class extends ResizeResponsiveComponent<TextInfoDialogProps, TextInfoDialogState> {
model: PiPedalModel;
refNotices: React.RefObject<HTMLDivElement | null >;
constructor(props: TextInfoDialogProps) {
super(props);
this.model = PiPedalModelFactory.getInstance();
this.refNotices = React.createRef();
this.handleDialogClose = this.handleDialogClose.bind(this);
this.state = {
textFileContent: "",
fullScreen: this.windowSize.width < 600
};
}
onWindowSizeChanged(width: number, height: number): void {
this.setState({fullScreen: width < 600});
}
mounted: boolean = false;
subscribed: boolean = false;
tick() {
if (this.model.state.get() === State.Ready) {
}
}
timerHandle?: number;
startTextFileRequest() {
let url = this.model.varServerUrl + "downloadMediaFile?path=" + encodeURIComponent(this.props.fileName);
if (this.state.textFileContent === "") {
fetch(url)
.then((request) => {
if (!request.ok) {
throw new PiPedalError("Info file request failed.");
}
return request.text();
})
.then((text) => {
if (this.mounted) {
this.setState({ textFileContent: text });
}
})
.catch((err) => {
// ok in debug builds. File doesn't get placed until install time.
console.log("Failed to fetch text file. " + err.toString());
this.setState({textFileContent: err.toString()});
});
}
}
componentDidMount() {
super.componentDidMount?.();
this.mounted = true;
this.startTextFileRequest();
}
componentWillUnmount() {
super.componentWillUnmount?.();
this.mounted = false;
}
componentDidUpdate(prevProps: Readonly<TextInfoDialogProps>, prevState: Readonly<TextInfoDialogState>, snapshot: any): void {
super.componentDidUpdate?.(prevProps, prevState, snapshot);
}
handleDialogClose(_e: SyntheticEvent) {
this.props.onClose();
}
render() {
return (
<DialogEx tag="info" fullScreen={this.state.fullScreen} open={this.props.open}
onClose={() => { this.props.onClose() }} TransitionComponent={Transition}
onEnterKey={() => { this.props.onClose() }}
style={{ userSelect: "none" }}
>
<div style={{ display: "flex", flexDirection: "column", flexWrap: "nowrap", width: "100%", height: "100%", overflow: "hidden" }}>
<div style={{ flex: "0 0 auto" }}>
<AppBar style={{
position: 'relative',
top: 0, left: 0
}} >
<Toolbar>
<IconButtonEx tooltip="Back" edge="start" color="inherit" onClick={this.handleDialogClose} aria-label="back"
>
<ArrowBackIcon />
</IconButtonEx>
<Typography noWrap variant="h6"
sx={{ marginLeft: 2, flex: 1 }}
>
{this.props.title}
</Typography>
</Toolbar>
</AppBar>
</div>
<div style={{
flex: "1 1 auto", position: "relative", overflow: "hidden",
overflowX: "hidden", overflowY: "auto", userSelect: "text"
}}
>
<div style={{ padding: "16px 32px",
display: "flex", flexDirection: "row", flexWrap: "nowrap", alignItems: "start",
}}>
<div style={{ flex: "1 1 0px" }} />
<div style={{ flex: "1 1 auto",maxWidth: 650 }} >
<ReactMarkdown rehypePlugins={[[rehypeExternalLinks, {target: '_blank'}]]}>
{this.state.textFileContent}
</ReactMarkdown>
</div>
<div style={{ flex: " 5 5 0px" }} />
</div>
</div>
</div>
</DialogEx >
);
}
};
export default TextInfoDialog;