Toob File Player checkpoint.
This commit is contained in:
@@ -0,0 +1,733 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Robin E. R. Davies
|
||||
* All rights reserved.
|
||||
|
||||
* 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, { useEffect } from 'react';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import Box from '@mui/material/Box';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Slider from '@mui/material/Slider';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Pause from '@mui/icons-material/Pause';
|
||||
import PlayArrow from '@mui/icons-material/PlayArrow';
|
||||
import FastForward from '@mui/icons-material/FastForward';
|
||||
import FastRewind from '@mui/icons-material/FastRewind';
|
||||
import { PiPedalModelFactory } from './PiPedalModel';
|
||||
import ButtonTooltip from './ButtonTooltip';
|
||||
import { pathFileNameOnly } from './FilePropertyDialog'
|
||||
import ButtonBase from '@mui/material/ButtonBase';
|
||||
import FilePropertyDialog from './FilePropertyDialog';
|
||||
import JsonAtom from './JsonAtom';
|
||||
import { UiFileProperty } from './Lv2Plugin';
|
||||
import { Divider } from '@mui/material';
|
||||
import useWindowSize from './UseWindowSize';
|
||||
|
||||
let Player__seek = "http://two-play.com/plugins/toob-player#seek"
|
||||
const AUDIO_FILE_PROPERTY_URI = "http://two-play.com/plugins/toob-player#audioFile";
|
||||
class PluginState {
|
||||
// Must match PluginState values in ToobPlayer.hpp
|
||||
static Idle = 0.0;
|
||||
static CuePlaying = 1;
|
||||
static CuePlayPaused = 2.0;
|
||||
static Pausing = 3;
|
||||
static Paused = 4;
|
||||
static Playing = 5;
|
||||
static Error = 6;
|
||||
};
|
||||
|
||||
const WallPaper = styled('div')({
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
top: 0,
|
||||
left: 0,
|
||||
overflow: 'hidden',
|
||||
background: 'linear-gradient(rgb(255, 38, 142) 0%, rgb(255, 105, 79) 100%)',
|
||||
transition: 'all 500ms cubic-bezier(0.175, 0.885, 0.32, 1.275) 0s',
|
||||
'&::before': {
|
||||
content: '""',
|
||||
width: '140%',
|
||||
height: '140%',
|
||||
position: 'absolute',
|
||||
top: '-40%',
|
||||
right: '-50%',
|
||||
background:
|
||||
'radial-gradient(at center center, rgb(62, 79, 249) 0%, rgba(62, 79, 249, 0) 64%)',
|
||||
},
|
||||
'&::after': {
|
||||
content: '""',
|
||||
width: '140%',
|
||||
height: '140%',
|
||||
position: 'absolute',
|
||||
bottom: '-50%',
|
||||
left: '-30%',
|
||||
background:
|
||||
'radial-gradient(at center center, rgb(247, 237, 225) 0%, rgba(247, 237, 225, 0) 70%)',
|
||||
transform: 'rotate(30deg)',
|
||||
},
|
||||
});
|
||||
|
||||
function jsonToPath(json: string) {
|
||||
try {
|
||||
let o = JSON.parse(json);
|
||||
return o.value;
|
||||
}
|
||||
catch (_) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
interface WidgetProps {
|
||||
noBorders: boolean;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const WidgetBorders = styled('div')(({ theme }) => ({
|
||||
|
||||
padding: 16,
|
||||
borderRadius: 16,
|
||||
minWidth: 300,
|
||||
maxWidth: 800,
|
||||
width: "70%",
|
||||
margin: 'auto',
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
backgroundColor: 'rgba(255,255,255,0.4)',
|
||||
boxShadow: "1px 3px 20px #0008",
|
||||
...theme.applyStyles('dark', {
|
||||
backgroundColor: 'rgba(0,0,0,0.6)',
|
||||
}),
|
||||
}));
|
||||
|
||||
const WidgetNoBorders = styled('div')(({ theme }) => ({
|
||||
|
||||
padding: 16,
|
||||
borderRadius: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
margin: 0,
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
backgroundColor: 'rgba(255,255,255,0.4)',
|
||||
...theme.applyStyles('dark', {
|
||||
backgroundColor: 'rgba(0,0,0,0.6)',
|
||||
}),
|
||||
}));
|
||||
|
||||
|
||||
function Widget(props: WidgetProps) {
|
||||
if (props.noBorders) {
|
||||
return (<WidgetNoBorders>{props.children} </WidgetNoBorders>);
|
||||
} else {
|
||||
return (<WidgetBorders>{props.children} </WidgetBorders>);
|
||||
}
|
||||
}
|
||||
|
||||
const CoverImage = styled('div')({
|
||||
width: 60,
|
||||
height: 60,
|
||||
objectFit: 'cover',
|
||||
overflow: 'hidden',
|
||||
flexShrink: 0,
|
||||
borderRadius: 8,
|
||||
backgroundColor: 'rgba(0,0,0,0.08)',
|
||||
'& > img': {
|
||||
width: '100%',
|
||||
},
|
||||
});
|
||||
|
||||
const TinyText = styled(Typography)({
|
||||
fontSize: '0.85rem',
|
||||
opacity: 0.9,
|
||||
fontWeight: 400,
|
||||
letterSpacing: 0.2,
|
||||
});
|
||||
|
||||
|
||||
export interface ToobPlayerControlProps {
|
||||
instanceId: number;
|
||||
extraControls: React.ReactElement[];
|
||||
}
|
||||
|
||||
export default function ToobPlayerControl(
|
||||
props: ToobPlayerControlProps
|
||||
) {
|
||||
const defaultCoverArt = "/img/default_album.jpg";
|
||||
const [duration, setDuration] = React.useState(0.0);
|
||||
const [position, setPosition] = React.useState(0.0);
|
||||
const [dragging, setDragging] = React.useState(false);
|
||||
const [pluginState, setPluginState] = React.useState(0.0);
|
||||
const [sliderValue, setSliderValue] = React.useState(0.0);
|
||||
const [coverArt, setCoverArt] = React.useState(defaultCoverArt);
|
||||
const [audioFile, setAudioFile] = React.useState("");
|
||||
const [title, setTitle] = React.useState("");
|
||||
const [album, setAlbum] = React.useState("");
|
||||
const [showFileDialog, setShowFileDialog] = React.useState(false);
|
||||
|
||||
const model = PiPedalModelFactory.getInstance();
|
||||
|
||||
const [size] = useWindowSize();
|
||||
const width = size.width;
|
||||
const height = size.height;
|
||||
|
||||
const HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK = 500;
|
||||
|
||||
const useHorizontalLayout = height < HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK && width > 573 && width > height;
|
||||
const useVerticalScroll = width < 573;
|
||||
const useQuadMixPanel = width < 720;
|
||||
const noBorders = width < 420 || height < 720;
|
||||
|
||||
function SelectFile() {
|
||||
setShowFileDialog(true);
|
||||
}
|
||||
function formatDuration(value_: number) {
|
||||
let value = Math.ceil(value_);
|
||||
const minute = Math.floor(value / 60);
|
||||
const secondLeft = value - minute * 60;
|
||||
return `${minute}:${secondLeft < 10 ? `0${secondLeft}` : secondLeft}`;
|
||||
}
|
||||
function onAudioFileChanged(path: string) {
|
||||
setAudioFile(path);
|
||||
model.getAudioFileMetadata(path)
|
||||
.then((metadata) => {
|
||||
if (metadata.track !== "") {
|
||||
let track = metadata.track;
|
||||
if (!track.endsWith('.')) {
|
||||
track += '.';
|
||||
}
|
||||
setTitle(track + " " + metadata.title);
|
||||
}
|
||||
else {
|
||||
setTitle(metadata.title);
|
||||
}
|
||||
setAlbum(metadata.album);
|
||||
})
|
||||
.catch(()=>{
|
||||
setTitle("#error");
|
||||
setAlbum("");
|
||||
});
|
||||
|
||||
}
|
||||
function ControlCluster() {
|
||||
return (
|
||||
<Box
|
||||
sx={(theme) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
mt: -1,
|
||||
'& svg': {
|
||||
color: '#000',
|
||||
...theme.applyStyles('dark', {
|
||||
color: '#fff',
|
||||
}),
|
||||
},
|
||||
})}
|
||||
>
|
||||
<ButtonTooltip title="Previous track">
|
||||
<IconButton
|
||||
style={{ opacity: (pluginState === PluginState.Idle ? 0.2 : 0.7) }}
|
||||
onClick={() => {
|
||||
if (position > 3) {
|
||||
model.sendPedalboardControlTrigger(
|
||||
props.instanceId,
|
||||
"stop",
|
||||
1
|
||||
);
|
||||
} else {
|
||||
onPreviousTrack();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FastRewind fontSize="large" />
|
||||
</IconButton>
|
||||
</ButtonTooltip>
|
||||
<ButtonTooltip title="Play/Pause">
|
||||
<IconButton
|
||||
|
||||
style={{ opacity: (pluginState === PluginState.Idle ? 0.2 : 0.7) }}
|
||||
onClick={() => {
|
||||
if (pluginState != PluginState.Idle) {
|
||||
if (paused) {
|
||||
model.sendPedalboardControlTrigger(
|
||||
props.instanceId,
|
||||
"play",
|
||||
1
|
||||
);
|
||||
} else {
|
||||
model.sendPedalboardControlTrigger(
|
||||
props.instanceId,
|
||||
"pause",
|
||||
1
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{paused ? (
|
||||
<PlayArrow sx={{ fontSize: '3rem' }} />
|
||||
) : (
|
||||
<Pause sx={{ fontSize: '3rem' }} />
|
||||
)}
|
||||
</IconButton>
|
||||
</ButtonTooltip>
|
||||
<ButtonTooltip title="Next track">
|
||||
<IconButton
|
||||
style={{ opacity: (pluginState == PluginState.Idle ? 0.2 : 0.7) }}
|
||||
onClick={() => { onNextTrack(); }}
|
||||
>
|
||||
<FastForward fontSize="large" />
|
||||
</IconButton>
|
||||
</ButtonTooltip>
|
||||
</Box>
|
||||
|
||||
);
|
||||
}
|
||||
function FilePanel() {
|
||||
return (
|
||||
<ButtonBase style={{ display: "block", width: "100%", borderRadius: 10, textAlign: "left" }}
|
||||
onClick={() => { SelectFile() }}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', padding: "2px" }}>
|
||||
<CoverImage>
|
||||
<img style={{ opacity: pluginState === PluginState.Idle ? 0.3 : 1.0 }}
|
||||
src={
|
||||
coverArt
|
||||
}
|
||||
/>
|
||||
</CoverImage>
|
||||
<Box sx={{ ml: 1.5, minWidth: 0 }}>
|
||||
{pluginState === PluginState.Idle ? (
|
||||
<Typography variant="caption" noWrap>
|
||||
Tap to select
|
||||
</Typography>
|
||||
) : (
|
||||
<div>
|
||||
<Typography variant="body1" noWrap>
|
||||
{effectiveTitle}
|
||||
</Typography>
|
||||
<Typography variant="body2" noWrap sx={{}}>
|
||||
{album}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</ButtonBase>
|
||||
);
|
||||
|
||||
}
|
||||
function getUiFileProperty(uri: string): UiFileProperty {
|
||||
let pedalboardItem = model.pedalboard.get().getItem(props.instanceId);
|
||||
let uiPlugin = model.getUiPlugin(pedalboardItem.uri);
|
||||
if (!uiPlugin) {
|
||||
throw "uiPlugin not found.";
|
||||
}
|
||||
for (let property of uiPlugin.fileProperties) {
|
||||
if (property.patchProperty === uri) {
|
||||
return property;
|
||||
}
|
||||
}
|
||||
throw "FileProperty not found.";
|
||||
}
|
||||
function onNextTrack() {
|
||||
model.getNextAudioFile(audioFile)
|
||||
.then((file) => {
|
||||
let json = JsonAtom.Path(file);
|
||||
model.setPatchProperty(
|
||||
props.instanceId,
|
||||
AUDIO_FILE_PROPERTY_URI,
|
||||
json);
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
|
||||
}
|
||||
function onPreviousTrack() {
|
||||
model.getPreviousAudioFile(audioFile)
|
||||
.then((file) => {
|
||||
let json = JsonAtom.Path(file);
|
||||
model.setPatchProperty(
|
||||
props.instanceId,
|
||||
AUDIO_FILE_PROPERTY_URI,
|
||||
json);
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
}
|
||||
function OnSeek(value: number) {
|
||||
setDragging(true);
|
||||
model.setPatchProperty(props.instanceId, Player__seek, value)
|
||||
.then(() => {
|
||||
setPosition(value);
|
||||
setDragging(false);
|
||||
}).catch((e) => {
|
||||
console.warn("Seek error. " + e.toString());
|
||||
setPosition(value);
|
||||
setDragging(false);
|
||||
});
|
||||
setSliderValue(value);
|
||||
}
|
||||
useEffect(() => {
|
||||
let durationHandle = model.monitorPort(props.instanceId, "duration", 1.0 / 15,
|
||||
(value) => {
|
||||
setDuration(value);
|
||||
}
|
||||
);
|
||||
let positionHandle = model.monitorPort(props.instanceId, "position", 1.0,
|
||||
(value) => {
|
||||
setPosition(value);
|
||||
}
|
||||
);
|
||||
let pluginStateHandle = model.monitorPort(props.instanceId, "state", 1.0 / 1000.0,
|
||||
(value) => {
|
||||
setPluginState(value);
|
||||
}
|
||||
);
|
||||
let filePropertyHandle = model.monitorPatchProperty(
|
||||
props.instanceId,
|
||||
AUDIO_FILE_PROPERTY_URI,
|
||||
(instanceId: number, propertyUri: string, atomObject: any) => {
|
||||
if (typeof (atomObject) === "object") {
|
||||
let path = atomObject.value;
|
||||
onAudioFileChanged(path);
|
||||
} else if (typeof(atomObject) === "string")
|
||||
{
|
||||
onAudioFileChanged(atomObject as string);
|
||||
}
|
||||
}
|
||||
|
||||
);
|
||||
|
||||
model.getPatchProperty(props.instanceId, AUDIO_FILE_PROPERTY_URI)
|
||||
.then((o) => {
|
||||
let path = o.value;
|
||||
onAudioFileChanged(path);
|
||||
})
|
||||
|
||||
|
||||
return () => {
|
||||
model.unmonitorPort(durationHandle);
|
||||
model.unmonitorPort(positionHandle);
|
||||
model.unmonitorPort(pluginStateHandle);
|
||||
model.cancelMonitorPatchProperty(filePropertyHandle);
|
||||
};
|
||||
},
|
||||
[]
|
||||
);
|
||||
const effectivePosition =
|
||||
(pluginState !== PluginState.Idle) ? Math.min(position, duration) : 0.0;
|
||||
const effectiveTitle = title !== "" ? title : pathFileNameOnly(audioFile);
|
||||
|
||||
const paused = (pluginState === PluginState.Idle
|
||||
|| pluginState === PluginState.CuePlayPaused
|
||||
|| pluginState === PluginState.Paused);
|
||||
|
||||
function LinearMixPanel() {
|
||||
return (
|
||||
<Box style={{ width: "100%" }} >
|
||||
<div style={{ display: "flex", flex: "0 0 auto", justifyContent: "center", alignItems: 'center', flexFlow: "row nowrap" }}>
|
||||
{props.extraControls}
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
|
||||
}
|
||||
function QuadMixPanel() {
|
||||
return (
|
||||
<div style={{ display: "flex", width: "100%", flex: "0 0 auto", justifyContent: "center", alignItems: "center", flexFlow: "row nowrap" }}>
|
||||
<div style={{ display: "flex", flex: "0 0 auto", gap: 8, alignItems: "center", flexFlow: "column nowrap" }}>
|
||||
{props.extraControls[0]}
|
||||
{props.extraControls[1]}
|
||||
</div>
|
||||
<div style={{ display: "flex", flex: "0 0 auto", gap: 8, alignItems: "center", flexFlow: "column nowrap" }}>
|
||||
{props.extraControls[2]}
|
||||
{props.extraControls[3]}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
function SliderCluster() {
|
||||
return (
|
||||
|
||||
<div style={{ display: "flex", flexFlow: "column nowrap" }}>
|
||||
<Slider
|
||||
value={dragging ? sliderValue : effectivePosition}
|
||||
min={0}
|
||||
step={1}
|
||||
max={duration}
|
||||
onChange={(_, val) => {
|
||||
let v = val as number;
|
||||
setPosition(v);
|
||||
setSliderValue(v);
|
||||
}}
|
||||
onChangeCommitted={(e, value) => {
|
||||
let v = value as number;
|
||||
OnSeek(v);
|
||||
}}
|
||||
onPointerDown={(e) => {
|
||||
setDragging(true);
|
||||
}}
|
||||
onPointerUp={(e) => {
|
||||
// setDragging(false);
|
||||
}}
|
||||
disabled={duration == 0}
|
||||
sx={(t) => ({
|
||||
width: "100%",
|
||||
color: 'rgba(0,0,0,0.87)',
|
||||
height: 4,
|
||||
'& .MuiSlider-thumb': {
|
||||
width: 8,
|
||||
height: 8,
|
||||
transition: '0.3s cubic-bezier(.47,1.64,.41,.8)',
|
||||
'&::before': {
|
||||
boxShadow: '0 2px 12px 0 rgba(0,0,0,0.4)',
|
||||
},
|
||||
'&:hover, &.Mui-focusVisible': {
|
||||
boxShadow: `0px 0px 0px 8px ${'rgb(0 0 0 / 16%)'}`,
|
||||
...t.applyStyles('dark', {
|
||||
boxShadow: `0px 0px 0px 8px ${'rgb(255 255 255 / 16%)'}`,
|
||||
}),
|
||||
},
|
||||
'&.Mui-active': {
|
||||
width: 20,
|
||||
height: 20,
|
||||
},
|
||||
},
|
||||
'& .MuiSlider-rail': {
|
||||
opacity: 0.28,
|
||||
},
|
||||
...t.applyStyles('dark', {
|
||||
color: '#fff',
|
||||
}),
|
||||
})}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
flex: "0 0 auto",
|
||||
width: "100%",
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
marginTop: -4,
|
||||
}}
|
||||
>
|
||||
<TinyText>{formatDuration(dragging ? sliderValue : effectivePosition)}</TinyText>
|
||||
<TinyText>{formatDuration(duration)}</TinyText>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function VerticalWidget() {
|
||||
return (
|
||||
<div style={{
|
||||
position: "relative", width: "100%", height: "100%",
|
||||
display: "flex", flexFlow: "column nowrap"
|
||||
}} >
|
||||
<div style={{ flex: "1 1 1px" }} />
|
||||
|
||||
<Widget noBorders={noBorders}>
|
||||
{
|
||||
FilePanel()
|
||||
}
|
||||
<Slider
|
||||
value={dragging ? sliderValue : effectivePosition}
|
||||
min={0}
|
||||
step={1}
|
||||
max={duration}
|
||||
onChange={(_, val) => {
|
||||
let v = val as number;
|
||||
setPosition(v);
|
||||
setSliderValue(v);
|
||||
}}
|
||||
onChangeCommitted={(e, value) => {
|
||||
let v = value as number;
|
||||
OnSeek(v);
|
||||
}}
|
||||
onPointerDown={(e) => {
|
||||
setDragging(true);
|
||||
}}
|
||||
onPointerUp={(e) => {
|
||||
// setDragging(false);
|
||||
}}
|
||||
disabled={duration == 0}
|
||||
sx={(t) => ({
|
||||
width: "100%",
|
||||
color: 'rgba(0,0,0,0.87)',
|
||||
height: 4,
|
||||
'& .MuiSlider-thumb': {
|
||||
width: 8,
|
||||
height: 8,
|
||||
transition: '0.3s cubic-bezier(.47,1.64,.41,.8)',
|
||||
'&::before': {
|
||||
boxShadow: '0 2px 12px 0 rgba(0,0,0,0.4)',
|
||||
},
|
||||
'&:hover, &.Mui-focusVisible': {
|
||||
boxShadow: `0px 0px 0px 8px ${'rgb(0 0 0 / 16%)'}`,
|
||||
...t.applyStyles('dark', {
|
||||
boxShadow: `0px 0px 0px 8px ${'rgb(255 255 255 / 16%)'}`,
|
||||
}),
|
||||
},
|
||||
'&.Mui-active': {
|
||||
width: 20,
|
||||
height: 20,
|
||||
},
|
||||
},
|
||||
'& .MuiSlider-rail': {
|
||||
opacity: 0.28,
|
||||
},
|
||||
...t.applyStyles('dark', {
|
||||
color: '#fff',
|
||||
}),
|
||||
})}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
mt: -2,
|
||||
}}
|
||||
>
|
||||
<TinyText>{formatDuration(dragging ? sliderValue : effectivePosition)}</TinyText>
|
||||
<TinyText>{formatDuration(duration)}</TinyText>
|
||||
</Box>
|
||||
{
|
||||
ControlCluster()
|
||||
}
|
||||
<Divider style={{ marginTop: 8, marginBottom: 8 }} />
|
||||
{useQuadMixPanel ? QuadMixPanel()
|
||||
:
|
||||
LinearMixPanel()
|
||||
}
|
||||
</Widget>
|
||||
<div style={{ flex: "2 2 1px" }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function HorizontalWidget() {
|
||||
return (
|
||||
<div style={{
|
||||
position: "relative", width: "100%", height: "100%",
|
||||
display: "flex", flexFlow: "column nowrap"
|
||||
}} >
|
||||
|
||||
<Widget noBorders={true}>
|
||||
<div style={{
|
||||
display: "flex", flexFlow: "row nowrap", alignItems: "center", justifyContent: "stretch",
|
||||
width: "100%"
|
||||
}}>
|
||||
<div style={{
|
||||
display: "flex", alignItems: "stretch", flexFlow: "column nowrap", flex: "1 1 100%"
|
||||
|
||||
}}>
|
||||
<div style={{ display: "flex", flexFlow: "row nowrap", flex: "1 1 auto" }}>
|
||||
<div style={{ position: "relative", display: "flex", flexFlow: "row nowrap", flex: "1 1 auto" }}>
|
||||
{
|
||||
FilePanel()
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ flex: "0 0 auto" }}>
|
||||
{
|
||||
SliderCluster()
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{
|
||||
position: "relative", display: "flex", flexFlow: "row nowrap", flex: "0 0 auto"
|
||||
|
||||
}}>
|
||||
{
|
||||
ControlCluster()
|
||||
}
|
||||
</div>
|
||||
<div style={{ flex: "0 0 auto" }}>
|
||||
{LinearMixPanel()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</Widget>
|
||||
<div style={{ flex: "2 2 1px" }} />
|
||||
</div >
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div id="toobPlayerViewFrame" style={{
|
||||
width: '100%', height: "100%", overflow: 'hidden', position: 'relative',
|
||||
}}>
|
||||
<WallPaper />
|
||||
{
|
||||
useHorizontalLayout ?
|
||||
HorizontalWidget() : VerticalWidget()
|
||||
}
|
||||
{showFileDialog && (
|
||||
<FilePropertyDialog open={showFileDialog}
|
||||
fileProperty={getUiFileProperty(AUDIO_FILE_PROPERTY_URI)}
|
||||
instanceId={props.instanceId}
|
||||
selectedFile={audioFile}
|
||||
onCancel={() => {
|
||||
setShowFileDialog(false);
|
||||
}}
|
||||
onApply={(fileProperty, selectedFile) => {
|
||||
model.setPatchProperty(
|
||||
props.instanceId,
|
||||
AUDIO_FILE_PROPERTY_URI,
|
||||
JsonAtom.Path(selectedFile)
|
||||
)
|
||||
.then(() => {
|
||||
|
||||
})
|
||||
.catch((error) => {
|
||||
model.showAlert("Unable to complete the operation. " + error);
|
||||
});
|
||||
|
||||
}}
|
||||
onOk={(fileProperty, selectedFile) => {
|
||||
|
||||
model.setPatchProperty(
|
||||
props.instanceId,
|
||||
fileProperty.patchProperty,
|
||||
JsonAtom.Path(selectedFile)
|
||||
)
|
||||
.then(() => {
|
||||
|
||||
})
|
||||
.catch((error) => {
|
||||
model.showAlert("Unable to complete the operation. " + error);
|
||||
});
|
||||
setShowFileDialog(false);
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user