Tone3000 dialog (waiting for iframe fix)

This commit is contained in:
Robin E. R. Davies
2026-01-23 21:47:31 -05:00
parent f26e3bf102
commit f0c3995a8f
4 changed files with 275 additions and 261 deletions
+26
View File
@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tone3000 Download Received</title>
</head>
<body>
<script>
// Parse URL parameters
const urlParams = new URLSearchParams(window.location.search);
const toneUrl = urlParams.get('tone_url');
// Send the data back to the opener window
if (window.opener) {
window.opener.postMessage({
type: 'tone3000Download',
toneUrl: toneUrl
}, '*');
}
// Close the window after sending the data
window.close();
</script>
</body>
</html>
+22
View File
@@ -0,0 +1,22 @@
<html>
<head>
<title>Test Tone3000 Select</title>
<script>
function onPageLoad() {
console.log("Test Tone3000 Select Page Loaded");
let iframe = document.getElementById("tone3000SelectIframe");
iframe.src = "https://www.tone3000.com/api/v1/select?app_id=pipedal_app&redirect_url=http://localhost:5173/public/handleTone3000download.html";
}
</script>
</head>
<body onload="onPageLoad()">
<h1>Test Tone3000 Select Page</h1>
<p>This is a test page for Tone3000 select functionality.</p>
<iframe
style="width: 680px; height: 400px; border: 2px solid red;"
id="tone3000SelectIframe"
style="width:100%; height:600px; border:none;">
</body>
</html>
+138 -134
View File
@@ -21,7 +21,7 @@
import React from 'react'; import React from 'react';
import { createStyles } from './WithStyles'; import { createStyles } from './WithStyles';
// import Tone3000Dialog from './Tone3000Dialog'; import Tone3000Dialog from './Tone3000Dialog';
import TextInfoDialog from './TextInfoDialog'; import TextInfoDialog from './TextInfoDialog';
import Tone3000HelpDialog from './Tone3000HelpDialog'; import Tone3000HelpDialog from './Tone3000HelpDialog';
@@ -172,11 +172,11 @@ export interface FilePropertyDialogState {
previousSelection: string; previousSelection: string;
multiSelect: boolean, multiSelect: boolean,
selectedFiles: string[], selectedFiles: string[],
//openTone3000Dialog: boolean, openTone3000Dialog: boolean,
openTone3000Help: boolean, openTone3000Help: boolean,
openGuitarMlHelp: boolean, openGuitarMlHelp: boolean,
textFileName? : string; textFileName?: string;
}; };
@@ -249,7 +249,7 @@ export default withStyles(
previousSelection: this.props.selectedFile, previousSelection: this.props.selectedFile,
multiSelect: false, multiSelect: false,
selectedFiles: [], selectedFiles: [],
//openTone3000Dialog: false, openTone3000Dialog: false,
openTone3000Help: false, openTone3000Help: false,
openGuitarMlHelp: false openGuitarMlHelp: false
}; };
@@ -701,15 +701,14 @@ export default withStyles(
} }
this.openSelectedFile(); this.openSelectedFile();
} }
handleApply(fileProperty: UiFileProperty, selectedItem: string) handleApply(fileProperty: UiFileProperty, selectedItem: string) {
{
if (this.state.previousSelection == selectedItem) { if (this.state.previousSelection == selectedItem) {
return; return;
} }
if (!this.isLicenseFile(selectedItem) && !this.isFolderArtwork(selectedItem)) { if (!this.isLicenseFile(selectedItem) && !this.isFolderArtwork(selectedItem)) {
this.props.onApply(fileProperty, selectedItem); this.props.onApply(fileProperty, selectedItem);
} }
this.setState({previousSelection: selectedItem}); this.setState({ previousSelection: selectedItem });
} }
@@ -1069,13 +1068,13 @@ export default withStyles(
() => { this.handleAutoScrollTick() }, AUTOSCROLL_TICK_DELAY); () => { this.handleAutoScrollTick() }, AUTOSCROLL_TICK_DELAY);
} }
// handleTone3000Dialog(e: React.MouseEvent<HTMLButtonElement>) { handleTone3000Dialog(e: React.MouseEvent<HTMLButtonElement>) {
// e.stopPropagation(); e.stopPropagation();
// e.preventDefault(); e.preventDefault();
// this.setState({ openTone3000Dialog: true }); this.setState({ openTone3000Dialog: true });
// } }
handleGuitarMlHelp(e: React.MouseEvent<HTMLButtonElement>) { handleGuitarMlHelp(e: React.MouseEvent<HTMLButtonElement>) {
@@ -1519,125 +1518,126 @@ export default withStyles(
{(!this.state.reordering && !this.state.multiSelect) && ( {(!this.state.reordering && !this.state.multiSelect) && (
<> <>
<DialogActions style={{ justifyContent: "stretch", width: "100%" }}> <DialogActions style={{ justifyContent: "stretch", width: "100%" }}>
<div style={{display: "flex", flexFlow: "column nowrap", width: "100%", alignItems: "stretch", }}> <div style={{ display: "flex", flexFlow: "column nowrap", width: "100%", alignItems: "stretch", }}>
{isToobNamModelFile && ( {isToobNamModelFile && (
<div style={{ <div style={{
display: "flex", flexFlow: "row nowrap", justifyContent: "center", display: "flex", flexFlow: "row nowrap", justifyContent: "center",
alignItems: "center", width: "100%" alignItems: "center", width: "100%"
}}> }}>
<Typography variant="body2" > <Button
Download model files from <LinkEx onClick={(e) => { this.handleTone3000Dialog(e); }}
href="https://www.tone3000.com/search" target="_blank">TONE3000</LinkEx> >
</Typography> Download model files from Tone3000
<IconButtonEx tooltip="Help" </Button>
onClick={(e) => { this.handleTone3000Help(e); }} aria-label="help" edge="end" color="inherit" style={{ opacity: 0.6, marginLeft: 8 }} <IconButtonEx tooltip="Help"
> onClick={(e) => { this.handleTone3000Help(e); }} aria-label="help" edge="end" color="inherit" style={{ opacity: 0.6, marginLeft: 8 }}
<HelpOutlineIcon /> >
</IconButtonEx> <HelpOutlineIcon />
</div>
)}
{isToobMLModelFile && (
<div style={{
display: "flex", flexFlow: "row nowrap", justifyContent: "center", width: "100%",
alignItems: "center"
}}>
<Typography variant="body2" >
Download model files from <LinkEx
href="https://github.com/GuitarML/ToneLibrary/releases/download/v1.0/Proteus_Tone_Packs.zip" target="_blank">GuitarML</LinkEx>
</Typography>
<IconButtonEx tooltip="Help"
onClick={(e) => { this.handleGuitarMlHelp(e); }} aria-label="help" edge="end" color="inherit" style={{ opacity: 0.6, marginLeft: 8 }}
>
<HelpOutlineIcon />
</IconButtonEx>
</div>
)}
{this.state.windowWidth > 500 ? (
<div style={{ display: "flex", width: "100%", alignItems: "center", flexFlow: "column nowrap" }}>
<div style={{ flex: "1 1 100%", display: "flex", width: "100%", alignItems: "center", flexFlow: "row nowrap" }}>
<IconButtonEx style={{ visibility: (this.state.hasSelection ? "visible" : "hidden") }} aria-label="delete" component="label" color="primary"
tooltip="Delete selected file or folder"
disabled={!this.state.hasSelection || this.state.selectedFile === "" || protectedItem}
onClick={() => this.handleDelete()} >
<OldDeleteIcon fontSize='small' />
</IconButtonEx> </IconButtonEx>
<ButtonEx tooltip="Upload file" style={{ flex: "0 0 auto" }} aria-label="upload" variant="text" startIcon={<FileUploadIcon />}
onClick={() => { this.setState({ openUploadFileDialog: true }) }} disabled={protectedDirectory}
>
<div>Upload</div>
</ButtonEx>
<ButtonEx tooltip="Download file" style={{ flex: "0 0 auto" }} aria-label="upload" variant="text" startIcon={<FileDownloadIcon />}
onClick={() => { this.handleDownloadFile(); }} disabled={protectedDirectory || !this.state.hasFileSelection}
>
<div>Download</div>
</ButtonEx>
<div style={{ flex: "1 1 auto" }}>&nbsp;</div>
<Button variant="dialogSecondary" onClick={() => {
this.handleApply(this.props.fileProperty, this.state.initialSelection);
this.props.onCancel();
}} aria-label="cancel">
Cancel
</Button>
<Button variant="dialogPrimary" style={{ flex: "0 0 auto" }}
onClick={() => { this.openSelectedFile(); }}
disabled={(!canSelectFile)
} aria-label="select"
>
{okButtonText}
</Button>
</div> </div>
</div>
) : ( )}
<div style={{ width: "100%" }}> {isToobMLModelFile && (
<div style={{ display: "flex", width: "100%", alignItems: "center", flexFlow: "row nowrap" }}> <div style={{
<IconButtonEx tooltip="Delete" style={{ visibility: (this.state.hasSelection ? "visible" : "hidden") }} aria-label="delete" component="label" color="primary" display: "flex", flexFlow: "row nowrap", justifyContent: "center", width: "100%",
disabled={!this.state.hasSelection || this.state.selectedFile === "" || protectedItem} alignItems: "center"
onClick={() => this.handleDelete()} > }}>
<OldDeleteIcon fontSize='small' /> <Typography variant="body2" >
Download model files from <LinkEx
href="https://github.com/GuitarML/ToneLibrary/releases/download/v1.0/Proteus_Tone_Packs.zip" target="_blank">GuitarML</LinkEx>
</Typography>
<IconButtonEx tooltip="Help"
onClick={(e) => { this.handleGuitarMlHelp(e); }} aria-label="help" edge="end" color="inherit" style={{ opacity: 0.6, marginLeft: 8 }}
>
<HelpOutlineIcon />
</IconButtonEx> </IconButtonEx>
<Button style={{ flex: "0 0 auto" }} aria-label="upload" variant="text" startIcon={<FileUploadIcon />}
onClick={() => { this.setState({ openUploadFileDialog: true }) }} disabled={protectedDirectory}
>
<div>Upload</div>
</Button>
<Button style={{ flex: "0 0 auto" }} aria-label="upload" variant="text" startIcon={<FileDownloadIcon />}
onClick={() => { this.handleDownloadFile(); }} disabled={protectedDirectory || !this.state.hasFileSelection}
>
<div>Download</div>
</Button>
<div style={{ flex: "1 1 auto" }}>&nbsp;</div>
</div> </div>
<div style={{ display: "flex", width: "100%", alignItems: "center", flexFlow: "row nowrap" }}>
<div style={{ flex: "1 1 auto" }}>&nbsp;</div>
<Button variant="dialogSecondary" onClick={() => { this.props.onCancel(); }} aria-label="cancel"> )}
Cancel
</Button>
<Button variant="dialogPrimary" style={{ flex: "0 0 auto" }}
onClick={() => { this.openSelectedFile(); }}
disabled={!canSelectFile}
aria-label="select" {this.state.windowWidth > 500 ? (
> <div style={{ display: "flex", width: "100%", alignItems: "center", flexFlow: "column nowrap" }}>
{okButtonText}
</Button> <div style={{ flex: "1 1 100%", display: "flex", width: "100%", alignItems: "center", flexFlow: "row nowrap" }}>
<IconButtonEx style={{ visibility: (this.state.hasSelection ? "visible" : "hidden") }} aria-label="delete" component="label" color="primary"
tooltip="Delete selected file or folder"
disabled={!this.state.hasSelection || this.state.selectedFile === "" || protectedItem}
onClick={() => this.handleDelete()} >
<OldDeleteIcon fontSize='small' />
</IconButtonEx>
<ButtonEx tooltip="Upload file" style={{ flex: "0 0 auto" }} aria-label="upload" variant="text" startIcon={<FileUploadIcon />}
onClick={() => { this.setState({ openUploadFileDialog: true }) }} disabled={protectedDirectory}
>
<div>Upload</div>
</ButtonEx>
<ButtonEx tooltip="Download file" style={{ flex: "0 0 auto" }} aria-label="upload" variant="text" startIcon={<FileDownloadIcon />}
onClick={() => { this.handleDownloadFile(); }} disabled={protectedDirectory || !this.state.hasFileSelection}
>
<div>Download</div>
</ButtonEx>
<div style={{ flex: "1 1 auto" }}>&nbsp;</div>
<Button variant="dialogSecondary" onClick={() => {
this.handleApply(this.props.fileProperty, this.state.initialSelection);
this.props.onCancel();
}} aria-label="cancel">
Cancel
</Button>
<Button variant="dialogPrimary" style={{ flex: "0 0 auto" }}
onClick={() => { this.openSelectedFile(); }}
disabled={(!canSelectFile)
} aria-label="select"
>
{okButtonText}
</Button>
</div>
</div> </div>
</div> ) : (
)} <div style={{ width: "100%" }}>
<div style={{ display: "flex", width: "100%", alignItems: "center", flexFlow: "row nowrap" }}>
<IconButtonEx tooltip="Delete" style={{ visibility: (this.state.hasSelection ? "visible" : "hidden") }} aria-label="delete" component="label" color="primary"
disabled={!this.state.hasSelection || this.state.selectedFile === "" || protectedItem}
onClick={() => this.handleDelete()} >
<OldDeleteIcon fontSize='small' />
</IconButtonEx>
<Button style={{ flex: "0 0 auto" }} aria-label="upload" variant="text" startIcon={<FileUploadIcon />}
onClick={() => { this.setState({ openUploadFileDialog: true }) }} disabled={protectedDirectory}
>
<div>Upload</div>
</Button>
<Button style={{ flex: "0 0 auto" }} aria-label="upload" variant="text" startIcon={<FileDownloadIcon />}
onClick={() => { this.handleDownloadFile(); }} disabled={protectedDirectory || !this.state.hasFileSelection}
>
<div>Download</div>
</Button>
<div style={{ flex: "1 1 auto" }}>&nbsp;</div>
</div>
<div style={{ display: "flex", width: "100%", alignItems: "center", flexFlow: "row nowrap" }}>
<div style={{ flex: "1 1 auto" }}>&nbsp;</div>
<Button variant="dialogSecondary" onClick={() => { this.props.onCancel(); }} aria-label="cancel">
Cancel
</Button>
<Button variant="dialogPrimary" style={{ flex: "0 0 auto" }}
onClick={() => { this.openSelectedFile(); }}
disabled={!canSelectFile}
aria-label="select"
>
{okButtonText}
</Button>
</div>
</div>
)}
</div> </div>
</DialogActions> </DialogActions>
</> </>
@@ -1742,12 +1742,17 @@ export default withStyles(
) )
) )
} }
{/* {this.state.openTone3000Dialog && ( {this.state.openTone3000Dialog && (
<Tone3000Dialog <Tone3000Dialog
open={this.state.openTone3000Dialog} open={this.state.openTone3000Dialog}
onClose={() => this.setState({ openTone3000Dialog: false })} onCancel={() => this.setState({ openTone3000Dialog: false })}
onDownload={(toneUrl: string) => {
alert("Donload tone url: " + toneUrl);
this.setState({ openTone3000Dialog: false });
}
}
/> />
)} */} )}
{this.state.openTone3000Help && ( {this.state.openTone3000Help && (
<Tone3000HelpDialog <Tone3000HelpDialog
open={this.state.openTone3000Help} open={this.state.openTone3000Help}
@@ -1761,22 +1766,21 @@ export default withStyles(
/> />
)} )}
{this.state.textFileName !== undefined && ( {this.state.textFileName !== undefined && (
<TextInfoDialog open={true} <TextInfoDialog open={true}
title={pathFileNameOnly(this.state.textFileName)} title={pathFileNameOnly(this.state.textFileName)}
fileName={this.state.textFileName} onClose={() => this.setState({ textFileName: undefined })} fileName={this.state.textFileName} onClose={() => this.setState({ textFileName: undefined })}
/> />
)} )}
</DialogEx> </DialogEx>
); );
} }
isLicenseFile(fileName: string) { isLicenseFile(fileName: string) {
let extension = pathExtension(fileName); let extension = pathExtension(fileName);
if (extension === ".txt" || extension === ".md"){ if (extension === ".txt" || extension === ".md") {
return true; return true;
} }
let fileNameOnly = pathFileNameOnly(fileName); let fileNameOnly = pathFileNameOnly(fileName);
if (fileNameOnly.toUpperCase() === "LICENSE" || fileNameOnly.toUpperCase() === "README") if (fileNameOnly.toUpperCase() === "LICENSE" || fileNameOnly.toUpperCase() === "README") {
{
return true; return true;
} }
return false; return false;
+89 -127
View File
@@ -1,15 +1,10 @@
import React from 'react'; import React from 'react';
import Dialog from '@mui/material/Dialog'; import AppBar from '@mui/material/AppBar';
import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent';
import DialogActions from '@mui/material/DialogActions';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel'; import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
import DialogEx from './DialogEx'; import DialogEx from './DialogEx';
import Link from '@mui/material/Link';
import Toolbar from '@mui/material/Toolbar'; import Toolbar from '@mui/material/Toolbar';
import IconButtonEx from './IconButtonEx'; import IconButtonEx from './IconButtonEx';
import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ArrowBackIcon from '@mui/icons-material/ArrowBack';
@@ -19,142 +14,109 @@ import ArrowBackIcon from '@mui/icons-material/ArrowBack';
export interface Tone3000DialogProps { export interface Tone3000DialogProps {
open: boolean; open: boolean;
onClose: () => void; onCancel: () => void;
onDownload: (toneUrl: string) => void;
} }
interface Tone3000AuthState {
authToken: string;
refreshToken: string;
expiry: number;
};
// function getAuthToken(): Tone3000AuthState | null {
// let authToken = window.localStorage.getItem("tone300_auth_token");
// if (authToken === null) {
// return null;
// }
// let refreshToken = window.localStorage.getItem("tone3000_refresh_token");
// if (refreshToken === null) {
// return null;
// }
// let expiry = window.localStorage.getTime("tone3000_auth_expiry");
// return { authToken: authToken, refreshToken: refreshToken, expiry: expiry };
// }
// function setAuthToken(authToken: string, refreshToken: string, expirySeconds: number) {
// window.localStorage.setItem("tone3000_auth_token", authToken);
// window.localStorage.setItem("tone3000_refresh_token", refreshToken);
// window.localStorage.setItem("tone3000_auth_expiry", String(Date.now() + (expirySeconds * 1000)));
// }
// enum AuthState {
// OnBoarding,
// TokenExpired,
// Authenticated
// }
function Tone3000Dialog(props: Tone3000DialogProps) { function Tone3000Dialog(props: Tone3000DialogProps) {
const { open, onClose } = props; const { onCancel,onDownload } = props;
const model: PiPedalModel = PiPedalModelFactory.getInstance();
let [openAuthDialog, setOpenAuthDialog] = React.useState(!model.hasTone3000Auth.get());
React.useEffect(()=> {
let authListener = (value: boolean) => {
setOpenAuthDialog(!value);
}
model.hasTone3000Auth.addOnChangedHandler(authListener);
return () => {
model.hasTone3000Auth.removeOnChangedHandler(authListener);
}
});
function RedirectUrl() { function RedirectUrl() {
let baseUrl = new URL(window.location.href); let baseUrl = new URL(window.location.href);
let result = "http://" + baseUrl.hostname + ":" + baseUrl.port + "/"; let result = "http://" + baseUrl.hostname + ":" + baseUrl.port + "/public/handleTone3000download.html";
return result; return result;
} }
function AuthUrl() { const appId = 'pipedal_app';
//return "https://www.tone3000.com/api/v1/auth?redirect_url=" + encodeURIComponent(RedirectUrl()) + "&opt_only=true"; const redirectUrl = RedirectUrl();
return "https://www.tone3000.com/api/v1/auth?redirect_url=" + encodeURIComponent(RedirectUrl()) + "&otp_only=true";
let selectUrl = `https://www.tone3000.com/api/v1/select?app_id=${appId}&redirect_url=${redirectUrl}`;
const model: PiPedalModel = PiPedalModelFactory.getInstance();
if (model) {
} }
React.useEffect(()=> {
let messagehandler = (event: MessageEvent) => {
// Handle messages received from the iframe
if (event.origin !== "https://www.tone3000.com") {
return;
}
const data = event.data;
if (data.type === "tone3000_selection") {
onDownload(data.toneUrl);
}
}
window.addEventListener("message", messagehandler);
return () => {
window.removeEventListener("message", messagehandler);
}
});
return ( return (
<DialogEx <DialogEx open={props.open} onClose={()=>{ onCancel()}} fullScreen={true} tag="tone3000Dlg" onEnterKey={()=>{}} >
tag="tone3000"
fullScreen={true}
open={open}
onEnterKey={() => { onClose(); }}
onClose={() => { onClose(); }}
aria-labelledby="tone3000-dialog-title"
aria-describedby="tone3000-dialog-description"
>
<DialogTitle id="tone3000-dialog-title">
<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 }}> <div style={{display: "flex", flexDirection: "column", width: "100%", height: "100%"}}>
TONE3000 Models <div style={{ flex: "0 0 auto" }}>
</Typography> <AppBar style={{
position: 'relative',
</Toolbar> top: 0, left: 0
</DialogTitle> }} >
<DialogContent> <Toolbar>
<Typography variant="body1"> <IconButtonEx tooltip="Back" edge="start" color="inherit" onClick={()=> onCancel()}
This is a placeholder for the Tone 3000 dialog. aria-label="back"
</Typography>
</DialogContent>
<DialogActions>
<Button onClick={onClose} color="primary">
Close
</Button>
</DialogActions>
{
openAuthDialog && (
<Dialog
fullScreen={true}
open={openAuthDialog}>
<DialogTitle id="tone3000-auth-dialog-title">
<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 }} /> <ArrowBackIcon />
</IconButtonEx> </IconButtonEx>
<Typography noWrap variant="h6"
<Typography noWrap component="div" sx={{ flexGrow: 1 }}> sx={{ marginLeft: 2, flex: 1 }}
TONE3000 Authorization >
Tone3000 Direct Download
</Typography> </Typography>
</Toolbar> </Toolbar>
</DialogTitle> </AppBar>
<DialogContent> </div>
<div style={{ display: "flex", flexFlow: "row nowrap", alignItems: "start" }}>
<div style={{ flex: "1 1 1px" }} /> <iframe style={{flex: "1 1 1px", border: "none"}}
<div style={{ src={ selectUrl}
maxWidth: 500, marginLeft: "auto", marginRight: "auto", >
display: "flex", flexFlow: "column nowrap", gap: 16, alignItems: "start", </iframe>
}}> </div>
<Typography variant="body1" component="div" display="block" >
The <Link href="https://www.tone3000.com" target="_blank">TONE3000 website</Link> provides an
online library of models for use with TooB Neural Amp Modeller. You can download
models from the TONE3000 website onto your local machine and then upload them to
PiPedal; or, more conveniently, you can browse the TONE3000 model database directly, and download models directly
to the PiPedal server from the TONE3000 model database in a single step.
</Typography>
<Typography variant="body1" component="div" display="block" >
In order to access the TONE3000 database, you must first obtain an access token from the
TONE3000 website. Clicking on the button will take you to an external website in
order to complete the authorization process.
</Typography>
<Button variant="contained" color="primary" style={{ alignSelf: "end", marginRight: 32 }}
onClick={() => {
window.open(AuthUrl(), "_blank");
}}
>
Continue to TONE3000
</Button>
<Typography variant="body2" component="div" display="block" style={{ marginTop: 32 }}>
Privacy statement: PiPedal will only have access to your authorization token
which does not contain personally identifying information, and which is stored locally on
your PiPedal server. Please refer to
the <Link href="https://www.tone3000.com/privacy" target="_blank">TONE3000 privacy policy</Link> for
information on how your data is used by TONE3000.
</Typography>
</div>
<div style={{ flex: "2 2 1px" }} />
</div>
</DialogContent>
</Dialog>
)}
</DialogEx> </DialogEx>
); );