Sync.
This commit is contained in:
@@ -38,13 +38,78 @@ function getVersionSuffix(filePath: string): string | null {
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
export function safeFilenameDecode(filename: string): string {
|
||||
let originalFilename = filename;
|
||||
let pos = filename.indexOf('%');
|
||||
if (pos < 0) {
|
||||
return filename;
|
||||
}
|
||||
let result = "";
|
||||
while (true) {
|
||||
if (pos < 0) {
|
||||
result += filename;
|
||||
return result;
|
||||
}
|
||||
result += filename.substring(0, pos);
|
||||
if (pos+3 >= filename.length) {
|
||||
// invalid encoding. just use the original
|
||||
return originalFilename;
|
||||
}
|
||||
let hex = filename.charAt(pos+1) + filename.charAt(pos+2);
|
||||
let byte = parseInt(hex, 16);
|
||||
|
||||
// Handle UTF-8 to UTF-16 conversion
|
||||
if (byte < 0x80) {
|
||||
// Single-byte character (ASCII)
|
||||
result += String.fromCharCode(byte);
|
||||
filename = filename.substring(pos+3);
|
||||
} else if ((byte & 0xE0) === 0xC0) {
|
||||
// 2-byte sequence
|
||||
if (pos+6 > filename.length || filename.charAt(pos+3) !== '%') {
|
||||
return originalFilename; // invalid encoding
|
||||
}
|
||||
let byte2 = parseInt(filename.charAt(pos+4) + filename.charAt(pos+5), 16);
|
||||
let codePoint = ((byte & 0x1F) << 6) | (byte2 & 0x3F);
|
||||
result += String.fromCharCode(codePoint);
|
||||
filename = filename.substring(pos+6);
|
||||
} else if ((byte & 0xF0) === 0xE0) {
|
||||
// 3-byte sequence
|
||||
if (pos+9 > filename.length || filename.charAt(pos+3) !== '%' || filename.charAt(pos+6) !== '%') {
|
||||
return originalFilename; // invalid encoding
|
||||
}
|
||||
let byte2 = parseInt(filename.charAt(pos+4) + filename.charAt(pos+5), 16);
|
||||
let byte3 = parseInt(filename.charAt(pos+7) + filename.charAt(pos+8), 16);
|
||||
let codePoint = ((byte & 0x0F) << 12) | ((byte2 & 0x3F) << 6) | (byte3 & 0x3F);
|
||||
result += String.fromCharCode(codePoint);
|
||||
filename = filename.substring(pos+9);
|
||||
} else if ((byte & 0xF8) === 0xF0) {
|
||||
// 4-byte sequence (surrogate pair needed)
|
||||
if (pos+12 > filename.length || filename.charAt(pos+3) !== '%' || filename.charAt(pos+6) !== '%' || filename.charAt(pos+9) !== '%') {
|
||||
return originalFilename; // invalid encoding
|
||||
}
|
||||
let byte2 = parseInt(filename.charAt(pos+4) + filename.charAt(pos+5), 16);
|
||||
let byte3 = parseInt(filename.charAt(pos+7) + filename.charAt(pos+8), 16);
|
||||
let byte4 = parseInt(filename.charAt(pos+10) + filename.charAt(pos+11), 16);
|
||||
let codePoint = ((byte & 0x07) << 18) | ((byte2 & 0x3F) << 12) | ((byte3 & 0x3F) << 6) | (byte4 & 0x3F);
|
||||
// Convert to UTF-16 surrogate pair
|
||||
codePoint -= 0x10000;
|
||||
result += String.fromCharCode(0xD800 + (codePoint >> 10));
|
||||
result += String.fromCharCode(0xDC00 + (codePoint & 0x3FF));
|
||||
filename = filename.substring(pos+12);
|
||||
} else {
|
||||
// Invalid UTF-8 start byte
|
||||
return originalFilename;
|
||||
}
|
||||
pos = filename.indexOf('%');
|
||||
}
|
||||
}
|
||||
|
||||
export function getTrackTitle(pathname: string, metadata: AudioFileMetadata | null | undefined): string {
|
||||
if (!metadata) {
|
||||
return pathFileName(pathname);
|
||||
return safeFilenameDecode(pathFileName(pathname));
|
||||
}
|
||||
if (metadata.title === "") {
|
||||
return pathFileName(pathname);
|
||||
return safeFilenameDecode(pathFileName(pathname));
|
||||
}
|
||||
let trackDisplay = "";
|
||||
if (metadata.track > 0) {
|
||||
@@ -56,7 +121,7 @@ export function getTrackTitle(pathname: string, metadata: AudioFileMetadata | nu
|
||||
}
|
||||
let result = trackDisplay + metadata.title;
|
||||
if (result === "") {
|
||||
result = pathFileName(pathname);
|
||||
result = safeFilenameDecode(pathFileName(pathname));
|
||||
}
|
||||
let versionSuffix = getVersionSuffix(pathname);
|
||||
if (versionSuffix) {
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
import { Component, SyntheticEvent } from 'react';
|
||||
import { Theme } from '@mui/material/styles';
|
||||
import { css } from '@emotion/react';
|
||||
import ToolTipEx from './ToolTipEx';
|
||||
|
||||
import { UiFileProperty } from './Lv2Plugin';
|
||||
import Typography from '@mui/material/Typography';
|
||||
@@ -33,15 +34,16 @@ import ButtonBase from '@mui/material/ButtonBase'
|
||||
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
|
||||
import { PedalboardItem } from './Pedalboard';
|
||||
import { isDarkMode } from './DarkMode';
|
||||
|
||||
|
||||
export const StandardItemSize = { width: 80, height: 140 }
|
||||
import {safeFilenameDecode} from './AudioFileMetadata';
|
||||
|
||||
|
||||
import { withStyles } from "tss-react/mui";
|
||||
import WithStyles from './WithStyles';
|
||||
import { withTheme } from './WithStyles';
|
||||
|
||||
|
||||
export const StandardItemSize = { width: 80, height: 140 }
|
||||
|
||||
const styles = (theme: Theme) => {
|
||||
return {
|
||||
frame: css({
|
||||
@@ -209,11 +211,13 @@ const FilePropertyControl =
|
||||
let fileProperty = this.props.fileProperty;
|
||||
|
||||
let value = "\u00A0";
|
||||
let toolTipText: string | null = null;
|
||||
if (this.state.hasValue) {
|
||||
if (this.state.value.length === 0) {
|
||||
value = "<none>";
|
||||
} else {
|
||||
value = this.fileNameOnly(this.state.value);
|
||||
value = safeFilenameDecode(this.fileNameOnly(this.state.value));
|
||||
toolTipText = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +237,7 @@ const FilePropertyControl =
|
||||
{/* CONTROL SECTION */}
|
||||
|
||||
<div className={classes.midSection} style={{ width: "100%", paddingLeft: 8 }}>
|
||||
|
||||
<ToolTipEx title={toolTipText}>
|
||||
<ButtonBase style={{ width: "100%", borderRadius: "4px 4px 0px 0px", overflow: "hidden" }} onClick={() => { this.onFileClick() }} >
|
||||
<div style={{
|
||||
width: "100%", background:
|
||||
@@ -249,6 +253,7 @@ const FilePropertyControl =
|
||||
<div style={{ height: "1px", width: "100%", background: this.props.theme.palette.text.secondary }}> </div>
|
||||
</div>
|
||||
</ButtonBase>
|
||||
</ToolTipEx>
|
||||
</div>
|
||||
|
||||
{/* LABEL/EDIT SECTION*/}
|
||||
|
||||
@@ -21,8 +21,6 @@
|
||||
import React from 'react';
|
||||
import { createStyles } from './WithStyles';
|
||||
|
||||
import Tone3000Dialog from './Tone3000Dialog';
|
||||
|
||||
import TextInfoDialog from './TextInfoDialog';
|
||||
import Tone3000HelpDialog from './Tone3000HelpDialog';
|
||||
import GuitarMLHelpDialog from './GuitarMlHelpDialog';
|
||||
@@ -69,6 +67,7 @@ import OkCancelDialog from './OkCancelDialog';
|
||||
import HomeIcon from '@mui/icons-material/Home';
|
||||
import FilePropertyDirectorySelectDialog from './FilePropertyDirectorySelectDialog';
|
||||
import { getAlbumArtUri, getTrackTitle } from './AudioFileMetadata';
|
||||
import { Tone3000DownloadDialog } from './Tone3000Dialog';
|
||||
|
||||
|
||||
const ToobNamModelFileUrl = "http://two-play.com/plugins/toob-nam#modelFile";
|
||||
@@ -705,7 +704,7 @@ export default withStyles(
|
||||
if (this.state.previousSelection == selectedItem) {
|
||||
return;
|
||||
}
|
||||
if (!this.isLicenseFile(selectedItem) && !this.isFolderArtwork(selectedItem)) {
|
||||
if (!this.isTextFile(selectedItem) && !this.isFolderArtwork(selectedItem)) {
|
||||
this.props.onApply(fileProperty, selectedItem);
|
||||
}
|
||||
this.setState({ previousSelection: selectedItem });
|
||||
@@ -1071,9 +1070,7 @@ export default withStyles(
|
||||
handleTone3000Dialog(e: React.MouseEvent<HTMLButtonElement>) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
this.setState({ openTone3000Dialog: true });
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1743,14 +1740,13 @@ export default withStyles(
|
||||
)
|
||||
}
|
||||
{this.state.openTone3000Dialog && (
|
||||
<Tone3000Dialog
|
||||
open={this.state.openTone3000Dialog}
|
||||
onCancel={() => this.setState({ openTone3000Dialog: false })}
|
||||
onDownload={(toneUrl: string) => {
|
||||
alert("Donload tone url: " + toneUrl);
|
||||
<Tone3000DownloadDialog
|
||||
onClose={() => this.setState({ openTone3000Dialog: false })}
|
||||
downloadPath={this.state.currentDirectory}
|
||||
onDownloadComplete={() => {
|
||||
this.setState({ openTone3000Dialog: false });
|
||||
}
|
||||
}
|
||||
this.requestFiles(this.state.navDirectory);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{this.state.openTone3000Help && (
|
||||
@@ -1774,15 +1770,11 @@ export default withStyles(
|
||||
</DialogEx>
|
||||
);
|
||||
}
|
||||
isLicenseFile(fileName: string) {
|
||||
isTextFile(fileName: string) {
|
||||
let extension = pathExtension(fileName);
|
||||
if (extension === ".txt" || extension === ".md") {
|
||||
return true;
|
||||
}
|
||||
let fileNameOnly = pathFileNameOnly(fileName);
|
||||
if (fileNameOnly.toUpperCase() === "LICENSE" || fileNameOnly.toUpperCase() === "README") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
@@ -1801,7 +1793,7 @@ export default withStyles(
|
||||
this.requestFiles(this.state.selectedFile);
|
||||
this.setState({ navDirectory: this.state.selectedFile });
|
||||
} else {
|
||||
if (this.isLicenseFile(this.state.selectedFile)) {
|
||||
if (this.isTextFile(this.state.selectedFile)) {
|
||||
this.handleShowTextFile(this.state.selectedFile);
|
||||
} else {
|
||||
this.props.onOk(this.props.fileProperty, this.state.selectedFile);
|
||||
|
||||
@@ -27,6 +27,7 @@ import { Pedalboard, PedalboardItem, ControlValue, Snapshot } from './Pedalboard
|
||||
import PluginClass from './PluginClass';
|
||||
import ScreenOrientation from './ScreenOrientation';
|
||||
import PiPedalSocket, { PiPedalMessageHeader } from './PiPedalSocket';
|
||||
import {Tone3000DownloadHandler} from './Tone3000Dialog';
|
||||
import { nullCast } from './Utility'
|
||||
import { JackConfiguration, JackChannelSelection } from './Jack';
|
||||
import { BankIndex } from './Banks';
|
||||
@@ -60,7 +61,7 @@ export enum State {
|
||||
HotspotChanging,
|
||||
};
|
||||
|
||||
function getErrorMessage(error: any) {
|
||||
export function getErrorMessage(error: any) {
|
||||
if (error instanceof Error) {
|
||||
return (error as Error).message;
|
||||
}
|
||||
@@ -494,7 +495,6 @@ export class PiPedalModel //implements PiPedalModel
|
||||
static getInstance(): PiPedalModel {
|
||||
return PiPedalModelFactory.getInstance();
|
||||
}
|
||||
hasTone3000Auth: ObservableProperty<boolean> = new ObservableProperty<boolean>(false);
|
||||
|
||||
canKeepScreenOn: boolean = false;
|
||||
keepScreenOn: ObservableProperty<boolean> = new ObservableProperty<boolean>(false);
|
||||
@@ -552,6 +552,8 @@ export class PiPedalModel //implements PiPedalModel
|
||||
|
||||
uiPluginsByUri: Map<string, UiPlugin> = new Map<string, UiPlugin>();
|
||||
|
||||
private tone3000DownloadHandler: Tone3000DownloadHandler | null = null;
|
||||
|
||||
svgImgUrl(svgImage: string): string {
|
||||
// return this.varServerUrl + "img/" + svgImage;
|
||||
return "img/" + svgImage;
|
||||
@@ -651,6 +653,53 @@ export class PiPedalModel //implements PiPedalModel
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
async downloadModelsFromTone3000(
|
||||
tone3000DownloadUrl: string,
|
||||
downloadPath: string
|
||||
): Promise<void> {
|
||||
try {
|
||||
if (!this.webSocket) {
|
||||
return;
|
||||
}
|
||||
this.webSocket.send("downloadModelsFromTone3000",
|
||||
{
|
||||
downloadPath: downloadPath,
|
||||
tone3000Url: tone3000DownloadUrl
|
||||
});
|
||||
} catch (error) {
|
||||
this.showAlert(getErrorMessage(error));
|
||||
}
|
||||
}
|
||||
|
||||
showTone3000DownloadDialog(
|
||||
downloadPath: string,
|
||||
onClosed: () => void,
|
||||
onDownloadStarted: () => void,
|
||||
onDownloadComplete: () => void
|
||||
|
||||
): void {
|
||||
if (this.tone3000DownloadHandler === null) {
|
||||
this.tone3000DownloadHandler = new Tone3000DownloadHandler(this);
|
||||
}
|
||||
this.tone3000DownloadHandler.launchTone3000Dialog(
|
||||
downloadPath,
|
||||
onClosed,
|
||||
(errorMessage: string) => {
|
||||
this.showAlert(errorMessage);
|
||||
onClosed();
|
||||
},
|
||||
onDownloadStarted,
|
||||
onDownloadComplete
|
||||
);
|
||||
|
||||
};
|
||||
closeTone3000DownloadDialog(): void {
|
||||
if (this.tone3000DownloadHandler) {
|
||||
this.tone3000DownloadHandler.closeTone3000Dialog();
|
||||
}
|
||||
}
|
||||
|
||||
private updateEnabledItems(pedalboard: Pedalboard) {
|
||||
for (let item of pedalboard.itemsGenerator()) {
|
||||
this.updatePedalboardItemEnabled(item.instanceId, item.isEnabled);
|
||||
@@ -862,10 +911,7 @@ export class PiPedalModel //implements PiPedalModel
|
||||
} else if (message === "onErrorMessage") {
|
||||
this.showAlert(body as string);
|
||||
|
||||
} else if (message == "onTone3000AuthChanged") {
|
||||
this.hasTone3000Auth.set(body as boolean);
|
||||
}
|
||||
else if (message === "onLv2PluginsChanging") {
|
||||
} else if (message === "onLv2PluginsChanging") {
|
||||
this.onLv2PluginsChanging();
|
||||
} else if (message === "onUpdateStatusChanged") {
|
||||
let updateStatus = new UpdateStatus().deserialize(body);
|
||||
@@ -1256,9 +1302,6 @@ export class PiPedalModel //implements PiPedalModel
|
||||
this.alsaSequencerConfiguration.set(new AlsaSequencerConfiguration().deserialize(
|
||||
await this.getWebSocket().request<any>("getAlsaSequencerConfiguration")
|
||||
));
|
||||
this.hasTone3000Auth.set(
|
||||
await this.getWebSocket().request<boolean>("getHasTone3000Auth")
|
||||
);
|
||||
this.banks.set(new BankIndex().deserialize(await this.getWebSocket().request<any>("getBankIndex")));
|
||||
|
||||
this.favorites.set(await this.getWebSocket().request<FavoritesList>("getFavorites"));
|
||||
@@ -1539,8 +1582,7 @@ export class PiPedalModel //implements PiPedalModel
|
||||
this.webSocket?.send("setSnapshot", index);
|
||||
}
|
||||
|
||||
private pruneSnapshotValues(pedalboard: Pedalboard)
|
||||
{
|
||||
private pruneSnapshotValues(pedalboard: Pedalboard) {
|
||||
let validPluginIds: Set<number> = new Set<number>();
|
||||
|
||||
let it = pedalboard.itemsGenerator();
|
||||
@@ -1572,7 +1614,7 @@ export class PiPedalModel //implements PiPedalModel
|
||||
pedalboard.selectedSnapshot = selectedSnapshot;
|
||||
}
|
||||
this.pruneSnapshotValues(pedalboard);
|
||||
|
||||
|
||||
this.setModelPedalboard(pedalboard);
|
||||
|
||||
this.webSocket?.send("setSnapshots", { snapshots: pedalboard.snapshots, selectedSnapshot: selectedSnapshot });
|
||||
@@ -2752,6 +2794,29 @@ export class PiPedalModel //implements PiPedalModel
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
|
||||
async uploadTone3000File(url: string) : Promise<boolean> {
|
||||
try {
|
||||
let response = await fetch(
|
||||
url,
|
||||
{
|
||||
method: "GET",
|
||||
}
|
||||
);
|
||||
|
||||
let json = await response.json();
|
||||
|
||||
if (json.success === true) {
|
||||
return true;
|
||||
}
|
||||
if (json.errorMessage !== undefined)
|
||||
{
|
||||
throw new Error(json.errorMessage);
|
||||
}
|
||||
throw new Error("Invalid server response.");
|
||||
} catch (error) {
|
||||
throw new Error("Upload to PiPedal server failed. " + getErrorMessage(error));
|
||||
}
|
||||
}
|
||||
uploadUserFile(uploadPage: string, file: File, contentType: string = "application/octet-stream", abortController?: AbortController): Promise<string> {
|
||||
let result = new Promise<string>((resolve, reject) => {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
|
||||
.text-info-dialog-content #user-content-tone3000_float {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
align-items: justify;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.text-info-dialog-content #user-content-tone3000_float_content {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
|
||||
}
|
||||
|
||||
.text-info-dialog-content #user-content-tone3000_title {
|
||||
margin-top: 0;
|
||||
margin-bottom: 12px;
|
||||
padding: 0;
|
||||
}
|
||||
.text-info-dialog-content #user-content-tone3000_subtitle {
|
||||
font-size: 0.8em;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.text-info-dialog-content #user-content-cc_img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
.text-info-dialog-content #user-content-tone3000_thumbnail {
|
||||
width: 130px;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
margin-right: 24px;
|
||||
border: 1px solid var(--pd-border-color);
|
||||
border-radius: 6px;
|
||||
align-self: flex-start;
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
.text-info-dialog-content #user-content-tone3000_thumbnail {
|
||||
width: 80px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
|
||||
import React, { SyntheticEvent } from 'react';
|
||||
import './TextInfoDialog.css';
|
||||
import IconButtonEx from './IconButtonEx';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { PiPedalModel, PiPedalModelFactory, State } from './PiPedalModel';
|
||||
@@ -27,7 +28,10 @@ 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 rehypeRaw from 'rehype-raw';
|
||||
import rehypeSanitize, {defaultSchema} from 'rehype-sanitize';
|
||||
import rehypeExternalLinks from 'rehype-external-links'
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import { PiPedalError } from './PiPedalError';
|
||||
import DialogEx from './DialogEx';
|
||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||
@@ -139,7 +143,6 @@ const TextInfoDialog = class extends ResizeResponsiveComponent<TextInfoDialogPro
|
||||
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={{
|
||||
@@ -168,8 +171,14 @@ const TextInfoDialog = class extends ResizeResponsiveComponent<TextInfoDialogPro
|
||||
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'}]]}>
|
||||
<div className="text-info-dialog-content" style={{ flex: "1 1 auto",maxWidth: 650 }} >
|
||||
<ReactMarkdown rehypePlugins={
|
||||
[
|
||||
[remarkGfm],
|
||||
[rehypeExternalLinks, {target: '_blank'}],
|
||||
rehypeRaw,
|
||||
[rehypeSanitize, defaultSchema]
|
||||
]}>
|
||||
{this.state.textFileContent}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
|
||||
+244
-108
@@ -1,125 +1,261 @@
|
||||
import { JSX } from "@emotion/react/jsx-dev-runtime";
|
||||
import { PiPedalModel, getErrorMessage } from "./PiPedalModel";
|
||||
import DialogEx from "./DialogEx";
|
||||
import DialogContent from "@mui/material/DialogContent";
|
||||
import DialogAction from "@mui/material/DialogActions";
|
||||
import { Button, Typography } from "@mui/material";
|
||||
import { useEffect, useState } from "react";
|
||||
import LinearProgress from "@mui/material/LinearProgress";
|
||||
|
||||
|
||||
import React from 'react';
|
||||
import AppBar from '@mui/material/AppBar';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||
import DialogEx from './DialogEx';
|
||||
import Toolbar from '@mui/material/Toolbar';
|
||||
import IconButtonEx from './IconButtonEx';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
export type OnTone3000DownloadHandler = (tone3000DownloadUrl: string) => void;
|
||||
|
||||
|
||||
// used to check for online status.
|
||||
let TONE3000_PING_URL = "https://www.tone3000.com/robots.txt";
|
||||
|
||||
export class Tone3000DownloadHandler {
|
||||
|
||||
export interface Tone3000DialogProps {
|
||||
open: boolean;
|
||||
onCancel: () => void;
|
||||
onDownload: (toneUrl: string) => void;
|
||||
}
|
||||
private async handleTone3000Download(tone3000DownloadUrl: string): Promise<void> {
|
||||
try {
|
||||
// Dialog can close. We'll use the following await to manage
|
||||
// lifecycle from here onward.
|
||||
this.stopTone3000DialogMonitor();
|
||||
|
||||
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) {
|
||||
const { onCancel,onDownload } = props;
|
||||
|
||||
function RedirectUrl() {
|
||||
let baseUrl = new URL(window.location.href);
|
||||
let result = "http://" + baseUrl.hostname + ":" + baseUrl.port + "/public/handleTone3000download.html";
|
||||
return result;
|
||||
}
|
||||
const appId = 'pipedal_app';
|
||||
const redirectUrl = RedirectUrl();
|
||||
|
||||
let selectUrl = `https://www.tone3000.com/api/v1/select?app_id=${appId}&redirect_url=${redirectUrl}`;
|
||||
|
||||
const model: PiPedalModel = PiPedalModelFactory.getInstance();
|
||||
if (model) {
|
||||
this.handleTone3000DownloadComplete();
|
||||
|
||||
await this.model.downloadModelsFromTone3000(tone3000DownloadUrl,this.downloadPath);
|
||||
return;
|
||||
} catch (e) {
|
||||
this.handleTone3000DownloadError(getErrorMessage(e));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
React.useEffect(()=> {
|
||||
private messageEventListener: ((event: MessageEvent) => void) | null = null;
|
||||
|
||||
let messagehandler = (event: MessageEvent) => {
|
||||
// Handle messages received from the iframe
|
||||
if (event.origin !== "https://www.tone3000.com") {
|
||||
private model: PiPedalModel;
|
||||
|
||||
public constructor(model: PiPedalModel) {
|
||||
this.model = model;
|
||||
this.messageEventListener = (event: MessageEvent) => {
|
||||
if (event.data.type !== "tone3000Download") {
|
||||
return;
|
||||
}
|
||||
const data = event.data;
|
||||
if (data.type === "tone3000_selection") {
|
||||
onDownload(data.toneUrl);
|
||||
this.handleTone3000Download(event.data.toneUrl as string);
|
||||
};
|
||||
|
||||
window.addEventListener("message", this.messageEventListener);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this.messageEventListener) {
|
||||
window.removeEventListener("message", this.messageEventListener);
|
||||
this.messageEventListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
private popupWindow: Window | null = null;
|
||||
|
||||
private appId: string = "pipedal_app";
|
||||
private redirectUrl(): string {
|
||||
let hostname = window.location.hostname;
|
||||
let port = window.location.port;
|
||||
let protocol = window.location.protocol;
|
||||
let serverUrl: string;
|
||||
if (protocol === "http:" && (port === "80" || port === "")) {
|
||||
serverUrl = `${protocol}//${hostname}`;
|
||||
} else if (protocol === "https:" && (port === "443" || port === "")) {
|
||||
serverUrl = `${protocol}//${hostname}`;
|
||||
} else {
|
||||
serverUrl = `${protocol}//${hostname}:${port}`;
|
||||
}
|
||||
return `${serverUrl}/public/handleTone3000download.html`;
|
||||
}
|
||||
|
||||
private tone3000SelectUrl(): string {
|
||||
return `https://www.tone3000.com/api/v1/select?app_id=${this.appId}&redirect_url=${this.redirectUrl()}`;
|
||||
}
|
||||
|
||||
|
||||
private handleTone3000DialogClosed() {
|
||||
let t = this.onClosedCallback;
|
||||
this.onClosedCallback = undefined;
|
||||
this.onErrorCallback = undefined;
|
||||
this.onDownloadCompleteCallback = undefined;
|
||||
if (t) {
|
||||
t();
|
||||
}
|
||||
this.stopTone3000DialogMonitor();
|
||||
}
|
||||
private handleTone3000Downloadstarted() {
|
||||
if (this.onDownloadStartedCallback) {
|
||||
this.onDownloadStartedCallback();
|
||||
}
|
||||
}
|
||||
private handleTone3000DownloadComplete() {
|
||||
let t = this.onDownloadCompleteCallback;
|
||||
this.onClosedCallback = undefined;
|
||||
this.onErrorCallback = undefined;
|
||||
this.onDownloadCompleteCallback = undefined;
|
||||
if (t) {
|
||||
t();
|
||||
}
|
||||
this.stopTone3000DialogMonitor();
|
||||
}
|
||||
private handleTone3000DownloadError(errorMessage: string) {
|
||||
let t = this.onErrorCallback;
|
||||
this.onClosedCallback = undefined;
|
||||
this.onErrorCallback = undefined;
|
||||
this.onDownloadCompleteCallback = undefined;
|
||||
if (t) {
|
||||
t(errorMessage);
|
||||
}
|
||||
this.stopTone3000DialogMonitor();
|
||||
}
|
||||
|
||||
private tone3000DialogInterval: number | undefined = undefined;
|
||||
|
||||
private stopTone3000DialogMonitor() {
|
||||
if (this.tone3000DialogInterval !== undefined) {
|
||||
clearInterval(this.tone3000DialogInterval);
|
||||
this.tone3000DialogInterval = undefined;
|
||||
}
|
||||
}
|
||||
private startTone3000DialogMonitor() {
|
||||
this.tone3000DialogInterval = setInterval(() => {
|
||||
if (this.popupWindow == null || this.popupWindow.closed) {
|
||||
this.stopTone3000DialogMonitor();
|
||||
this.popupWindow = null;
|
||||
this.handleTone3000DialogClosed();
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
private downloadPath: string = "";
|
||||
private onClosedCallback: (() => void) | undefined = undefined;
|
||||
private onErrorCallback: ((errorMessage: string) => void) | undefined = undefined;
|
||||
private onDownloadStartedCallback: (() => void) | undefined = undefined;
|
||||
private onDownloadCompleteCallback: (() => void) | undefined = undefined;
|
||||
|
||||
public launchTone3000Dialog(
|
||||
downloadPath: string,
|
||||
onClosed: () => void,
|
||||
onError: (errorMessage: string) => void,
|
||||
onDownloadStarted: () => void,
|
||||
onDownloadComplete: () => void
|
||||
) {
|
||||
this.closeTone3000Dialog();
|
||||
this.downloadPath = downloadPath;
|
||||
this.onClosedCallback = onClosed;
|
||||
this.onDownloadCompleteCallback = onDownloadComplete;
|
||||
this.onDownloadStartedCallback = onDownloadStarted;
|
||||
this.onErrorCallback = onError;
|
||||
|
||||
fetch(TONE3000_PING_URL, { cache: "no-cache" })
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
this.handleTone3000DownloadError(`Unable to connect to Tone3000: ${response.status}${response.statusText}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// online
|
||||
let popupWidth = Math.floor(window.innerWidth * 0.8);
|
||||
let popupHeight = Math.floor(window.innerHeight * 0.8);
|
||||
if (window.innerWidth < 410) {
|
||||
popupWidth = 400;
|
||||
}
|
||||
|
||||
this.popupWindow =
|
||||
window.open(
|
||||
this.tone3000SelectUrl(),
|
||||
"popup",
|
||||
`innerWidth=${popupWidth},innerHeight=${popupHeight},scrollbars=yes`
|
||||
);
|
||||
this.startTone3000DialogMonitor();
|
||||
|
||||
if (!this.popupWindow) {
|
||||
console.error("Failed to open Tone3000 dialog popup window.");
|
||||
this.handleTone3000DownloadError("Cannot open popup window.");
|
||||
return;
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
// offline
|
||||
this.handleTone3000DownloadError("Not online. Unable to connect to Tone3000 servers.");
|
||||
return;
|
||||
});
|
||||
|
||||
}
|
||||
public closeTone3000Dialog(): void {
|
||||
this.handleTone3000DialogClosed();
|
||||
this.stopTone3000DialogMonitor();
|
||||
if (this.popupWindow) {
|
||||
try {
|
||||
|
||||
let t = this.popupWindow;
|
||||
this.popupWindow = null;
|
||||
t.close();
|
||||
} catch (e) {
|
||||
console.error("Error closing Tone3000 dialog popup:", e);
|
||||
}
|
||||
}
|
||||
window.addEventListener("message", messagehandler);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("message", messagehandler);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return (
|
||||
<DialogEx open={props.open} onClose={()=>{ onCancel()}} fullScreen={true} tag="tone3000Dlg" onEnterKey={()=>{}} >
|
||||
|
||||
<div style={{display: "flex", flexDirection: "column", width: "100%", height: "100%"}}>
|
||||
<div style={{ flex: "0 0 auto" }}>
|
||||
<AppBar style={{
|
||||
position: 'relative',
|
||||
top: 0, left: 0
|
||||
}} >
|
||||
<Toolbar>
|
||||
<IconButtonEx tooltip="Back" edge="start" color="inherit" onClick={()=> onCancel()}
|
||||
aria-label="back"
|
||||
>
|
||||
<ArrowBackIcon />
|
||||
</IconButtonEx>
|
||||
<Typography noWrap variant="h6"
|
||||
sx={{ marginLeft: 2, flex: 1 }}
|
||||
>
|
||||
Tone3000 Direct Download
|
||||
</Typography>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
</div>
|
||||
|
||||
<iframe style={{flex: "1 1 1px", border: "none"}}
|
||||
src={ selectUrl}
|
||||
>
|
||||
</iframe>
|
||||
</div>
|
||||
</DialogEx>
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Tone3000Dialog;
|
||||
export interface Tone3000DownloadDialogProps {
|
||||
|
||||
onClose: () => void,
|
||||
onDownloadComplete: () => void,
|
||||
downloadPath: string
|
||||
}
|
||||
export function Tone3000DownloadDialog(props: Tone3000DownloadDialogProps): JSX.Element {
|
||||
const model = PiPedalModel.getInstance();
|
||||
let { onClose, onDownloadComplete, downloadPath } = props;
|
||||
let [downloading, setDownloading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
model.showTone3000DownloadDialog(
|
||||
downloadPath,
|
||||
() => {
|
||||
onClose();
|
||||
},
|
||||
() => {
|
||||
setDownloading(true);
|
||||
},
|
||||
() => {
|
||||
onDownloadComplete();
|
||||
}
|
||||
);
|
||||
return () => {
|
||||
model.closeTone3000DownloadDialog();
|
||||
};
|
||||
});
|
||||
return (
|
||||
<DialogEx tag="tone3000-download"
|
||||
open={true}
|
||||
onClose={() => {
|
||||
onClose();
|
||||
}}
|
||||
onEnterKey={() => { }}
|
||||
>
|
||||
<DialogContent>
|
||||
<Typography variant="body2">Downloading from Tone3000...</Typography>
|
||||
{downloading &&
|
||||
<div style={{ display: "flex", justifyContent: "center", alignItems: "center" }}>
|
||||
<LinearProgress style={{ marginTop: 16, flexGrow: 1 }} />
|
||||
</div>
|
||||
}
|
||||
</DialogContent>
|
||||
<DialogAction>
|
||||
{!downloading && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}>
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
</DialogAction>
|
||||
</DialogEx>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user