modified: src/app/component/Bus.tsx

modified:   src/app/component/BusesList.tsx
	modified:   src/app/component/Strip.tsx
	modified:   src/app/page.tsx
	modified:   src/utils/DetectMobile.ts
This commit is contained in:
Maksym 2024-08-07 23:34:13 +02:00
parent 7a65c76757
commit 42ccd675b8
5 changed files with 142 additions and 91 deletions

View File

@ -1,18 +1,31 @@
"use client"; "use client";
import { Button, ButtonGroup, Grid, Input, Slider, Stack } from "@mui/material"; import {
Button,
ButtonGroup,
Grid,
Input,
Slider,
Stack,
TextField,
} from "@mui/material";
import React from "react"; import React from "react";
import { StripBusOutput, StripBusOutputEvent } from "./StripBusOutput"; import { StripBusOutput, StripBusOutputEvent } from "./StripBusOutput";
import { EventCounter } from "@/utils/EventCounter"; import { EventCounter } from "@/utils/EventCounter";
export interface BusProps { export interface BusProps {
values?: Partial<BusState>; values?: Partial<Omit<BusState, StateDefaultValueException>>;
showInputdB?: boolean;
name: string;
onChange: (event: BusEvent) => any; onChange: (event: BusEvent) => any;
} }
type StateDefaultValueException = "showInputdB";
export interface BusState { export interface BusState {
gain: number; gain: number;
muted: boolean; muted: boolean;
selected: boolean; selected: boolean;
showInputdB: boolean;
} }
export type BusEvent = BusMuted | BusGainChanged | BusSelectToggle; export type BusEvent = BusMuted | BusGainChanged | BusSelectToggle;
@ -32,26 +45,29 @@ export interface BusSelectToggle {
export class Bus extends React.Component<BusProps, BusState> { export class Bus extends React.Component<BusProps, BusState> {
private eventCounter = new EventCounter<"onSliderResetDefaults">(); private eventCounter = new EventCounter<"onSliderResetDefaults">();
private defaultValues: BusState = { private defaultValues: Omit<BusState, StateDefaultValueException> = {
gain: 0, gain: 0,
selected: false, selected: false,
muted: false, muted: false,
}; };
constructor(props: BusProps) { constructor(props: BusProps) {
super(props); super(props);
const { values: initialValues } = props; let { values: initialValues } = props;
const getValue = (name: keyof typeof this.defaultValues) => { const getValue = (name: keyof typeof this.defaultValues) => {
if (initialValues === undefined) return this.defaultValues[name]; if (initialValues === undefined) return this.defaultValues[name];
return initialValues[name] !== undefined return initialValues[name] !== undefined
? this.defaultValues[name] ? this.defaultValues[name]
: initialValues[name]; : initialValues[name];
}; };
this.state = Object.fromEntries( this.state = {
Object.entries(this.defaultValues).map(([name]) => [ ...Object.fromEntries(
name, Object.entries(this.defaultValues).map(([name]) => [
getValue(name as keyof typeof this.defaultValues) as any, name,
]) getValue(name as keyof typeof this.defaultValues) as any,
) as BusState; ])
),
showInputdB: props.showInputdB !== undefined ? props.showInputdB : true,
} as BusState;
this.eventCounter.on({ this.eventCounter.on({
callback: this.onResetDefaults.bind(this), callback: this.onResetDefaults.bind(this),
count: 2, count: 2,
@ -69,11 +85,9 @@ export class Bus extends React.Component<BusProps, BusState> {
} }
} }
onGainChange(event: Event | React.ChangeEvent<HTMLInputElement>) { onGainChange(event: Event | React.ChangeEvent<HTMLInputElement>) {
let changedGain = parseFloat((event.target as HTMLInputElement).value);
let changedGain = if (changedGain > 12) changedGain = 12;
parseFloat((event.target as HTMLInputElement).value); else if (changedGain < -60) changedGain = -60;
if(changedGain > 12) changedGain = 12
else if(changedGain < -60) changedGain = -60;
this.setState({ this.setState({
gain: changedGain, gain: changedGain,
}); });
@ -117,20 +131,30 @@ export class Bus extends React.Component<BusProps, BusState> {
render() { render() {
return ( return (
<> <>
<Stack alignItems={"center"} direction={"row"} spacing={0}> <Stack
<Input alignItems={"center"}
inputProps={{ direction={"row"}
"aria-labelledby": "input-slider", spacing={0}
itemType: "number", sx={{ width: "inherit" }}
style:{padding: 0} >
}} {this.state.showInputdB && (
value={this.state.gain} <TextField
onWheel={(e) => this.onStripWheel(e)} // inputProps={{
onClick={(e) => this.stopPropagation(e)} // "aria-labelledby": "input-slider",
onChange={(e) => this.onGainChange(e as unknown as Event)} // itemType: "number",
size="small" // style:{padding: 0}
sx={{ width: "50px" }} // }}
/> value={this.state.gain}
onWheel={(e) => this.onStripWheel(e)}
onClick={(e) => this.stopPropagation(e)}
onChange={(e) => this.onGainChange(e as unknown as Event)}
size="small"
label={this.props.name}
type="number"
variant="outlined"
sx={{ minWidth: "100px", maxWidth: "100px" }}
/>
)}
<Slider <Slider
sx={{ sx={{
margin: "5px", margin: "5px",

View File

@ -1,4 +1,4 @@
import { Stack, Theme, Typography, useTheme, withTheme } from "@mui/material"; import { Chip, Stack, Theme, Typography, useTheme, withTheme } from "@mui/material";
import React from "react"; import React from "react";
import { Bus } from "./Bus"; import { Bus } from "./Bus";
import { range } from "@/utils/Range"; import { range } from "@/utils/Range";
@ -22,17 +22,16 @@ export class BusesList extends React.Component<BusesListProps, BusesListState> {
) { ) {
const [busId] = args; const [busId] = args;
const isPhysical = busId < this.props.physical; const isPhysical = busId < this.props.physical;
const name = `${isPhysical ? "A" : "B"}${
isPhysical ? busId + 1 : busId - this.props.physical + 1
}`;
return ( return (
<React.Fragment <Stack direction={"row"} sx={{width:"100%"}} alignItems={"center"}
key={`${isPhysical ? "A" : "B"}${ key={name}
isPhysical ? busId + 1 : busId - this.props.physical + 1
}`}
> >
<Typography variant="caption">{`${isPhysical ? "A" : "B"}${ {/* <Chip variant="outlined" sx={{marginInlineEnd: "15px"}} label={name}/> */}
isPhysical ? busId + 1 : busId - this.props.physical + 1 <Bus name={name} onChange={console.log} />
}`}</Typography> </Stack>
<Bus onChange={console.log} />
</React.Fragment>
); );
} }
render(): React.ReactNode { render(): React.ReactNode {

View File

@ -7,6 +7,7 @@ import {
Input, Input,
Slider, Slider,
Stack, Stack,
TextField,
} from "@mui/material"; } from "@mui/material";
import React from "react"; import React from "react";
import { StripBusOutput, StripBusOutputEvent } from "./StripBusOutput"; import { StripBusOutput, StripBusOutputEvent } from "./StripBusOutput";
@ -16,13 +17,17 @@ export interface StripProps {
physicalBuses: number; physicalBuses: number;
virtualBuses: number; virtualBuses: number;
values?: Partial<StripState>; values?: Partial<StripState>;
showOutputdB?: boolean;
name: string;
onChange: (event: StripEvent) => any; onChange: (event: StripEvent) => any;
} }
type StateDefaultValueException = "showOutputdB";
export interface StripState { export interface StripState {
gain: number; gain: number;
outputBuses: boolean[]; outputBuses: boolean[];
muted: boolean; muted: boolean;
showOutputdB: boolean;
} }
export type StripEvent = StripBusOutputChanged | StripMuted | StripGainChanged; export type StripEvent = StripBusOutputChanged | StripMuted | StripGainChanged;
@ -41,7 +46,7 @@ export interface StripMuted {
} }
export class Strip extends React.Component<StripProps, StripState> { export class Strip extends React.Component<StripProps, StripState> {
private defaultValues: StripState; private defaultValues: Omit<StripState, StateDefaultValueException>;
private eventCounter = new EventCounter<"onSliderResetDefaults">(); private eventCounter = new EventCounter<"onSliderResetDefaults">();
constructor(props: StripProps) { constructor(props: StripProps) {
super(props); super(props);
@ -59,12 +64,16 @@ export class Strip extends React.Component<StripProps, StripState> {
? this.defaultValues[name] ? this.defaultValues[name]
: initialValues[name]; : initialValues[name];
}; };
this.state = Object.fromEntries( this.state = {
Object.entries(this.defaultValues).map(([name]) => [ ...(Object.fromEntries(
name, Object.entries(this.defaultValues).map(([name]) => [
getValue(name as keyof typeof this.defaultValues) as any, name,
]) getValue(name as keyof typeof this.defaultValues) as any,
) as StripState; ])
) as Omit<StripState, StateDefaultValueException>),
showOutputdB:
props.showOutputdB !== undefined ? props.showOutputdB : true,
};
this.eventCounter.on({ this.eventCounter.on({
callback: this.onResetDefaults.bind(this), callback: this.onResetDefaults.bind(this),
count: 2, count: 2,
@ -135,18 +144,21 @@ export class Strip extends React.Component<StripProps, StripState> {
return ( return (
<> <>
<Stack direction={"row"}> <Stack direction={"row"}>
<Stack alignItems={"center"} style={{ maxWidth: "min-content" }}> <Stack alignItems={"center"} style={{ maxWidth: "70px" }}>
<Input <TextField
inputProps={{ // inputProps={{
"aria-labelledby": "input-slider", // "aria-labelledby": "input-slider",
itemType: "number", // itemType: "number",
style: { padding: 0 }, // style: { padding: 0 },
}} // }}
value={this.state.gain} value={this.state.gain}
onWheel={(e) => this.onStripWheel(e)} onWheel={(e) => this.onStripWheel(e)}
onClick={(e) => this.stopPropagation(e)} onClick={(e) => this.stopPropagation(e)}
size="small" size="small"
sx={{ marginInline: "5px" }} type="number"
label={this.props.name}
// sx={{minWidth: "4em", maxWidth: "5em"}}
// fullWidth
/> />
<Slider <Slider
sx={{ sx={{
@ -171,7 +183,7 @@ export class Strip extends React.Component<StripProps, StripState> {
/> />
</Stack> </Stack>
<Stack> <Stack>
<ButtonGroup orientation="vertical" sx={{paddingInlineStart:"5px"}}> <ButtonGroup orientation="vertical">
{[ {[
...Array(this.props.physicalBuses + this.props.virtualBuses), ...Array(this.props.physicalBuses + this.props.virtualBuses),
].map((_v, i) => ( ].map((_v, i) => (

View File

@ -26,7 +26,13 @@ function random(min: number, max: number, floor: boolean = true) {
} }
export default function Home() { export default function Home() {
const theme = useMemo(() => createTheme({ palette: { mode: "dark",background:{default: "black"} } }), []); const theme = useMemo(
() =>
createTheme({
palette: { mode: "dark", background: { default: "#000" } },
}),
[]
);
function onStripEvent(event: StripEvent) { function onStripEvent(event: StripEvent) {
console.log(event); console.log(event);
} }
@ -36,9 +42,7 @@ export default function Home() {
}, []); }, []);
const [snackBarVisible, setSnackBarVisibility] = useState(false); const [snackBarVisible, setSnackBarVisibility] = useState(false);
const breakPoints = useMemo(() => theme.breakpoints.values, []); const breakPoints = useMemo(() => theme.breakpoints.values, []);
const [width, setWidth] = useState( const [width, setWidth] = useState(1000);
1000
);
const eventCounter = useMemo(() => { const eventCounter = useMemo(() => {
const eventCounter = new EventCounter<"emptySpaceClick">(); const eventCounter = new EventCounter<"emptySpaceClick">();
eventCounter.on({ eventCounter.on({
@ -93,6 +97,7 @@ export default function Home() {
const buses = { virtual: 3, physical: 5 }; const buses = { virtual: 3, physical: 5 };
const amountOfStrips = strips.physical + strips.virtual; const amountOfStrips = strips.physical + strips.virtual;
const amountOfBuses = buses.physical + buses.virtual; const amountOfBuses = buses.physical + buses.virtual;
const virtualInputNames = useMemo(() => ["VAIO", "AUX", "VAIO3"], []);
if (typeof document !== "undefined") { if (typeof document !== "undefined") {
document.onclick = () => eventCounter.emit("emptySpaceClick"); document.onclick = () => eventCounter.emit("emptySpaceClick");
window.onresize = () => { window.onresize = () => {
@ -115,8 +120,8 @@ export default function Home() {
); );
return ( return (
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<Container <Container
id="container" id="container"
maxWidth={"xl"} maxWidth={"xl"}
style={{ height: "100%", paddingInline: "10px" }} style={{ height: "100%", paddingInline: "10px" }}
> >
@ -129,35 +134,44 @@ export default function Home() {
display={"flex"} display={"flex"}
alignItems={"center"} alignItems={"center"}
justifyContent={"space-evenly"} justifyContent={"space-evenly"}
columns={{ xs: 8, sm: 12, md: 16, lg: 12 }} columns={{ xs: 8, sm: 12, md: 16, lg: 14 }}
> >
{[...Array(amountOfStrips)].map((_v, stripId) => ( {[...Array(amountOfStrips)].map((_v, stripId) => {
<React.Fragment const isPhysical = stripId < strips.physical;
key={`${stripId < amountOfStrips ? "a" : "b"}${ const name = `${isPhysical ? "A" : "B"}${
stripId < amountOfStrips isPhysical ? stripId + 1 : stripId + 1 - strips.physical
? stripId + 1 }`;
: stripId + 1 - amountOfStrips return (
}`} <React.Fragment key={name}>
> <Grid
<Grid lgOffset={stripId === strips.physical ? 2 : 0}
lgOffset={stripId === strips.physical ? 2 : 0} mdOffset={stripId === strips.physical ? 0.8 : 0}
mdOffset={stripId === strips.physical ? 0.5 : 0} sx={{ minWidth: "fit-content" }}
sx={{ minWidth: "fit-content" }} >
> {/* <Typography variant="overline">{`Strip #${
<Typography variant="overline">{`Strip #${ stripId + 1
stripId + 1 }`}</Typography> */}
}`}</Typography> <Strip
<Strip onChange={(ev) => onStripEvent(ev)}
onChange={(ev) => onStripEvent(ev)} physicalBuses={buses.physical}
physicalBuses={buses.physical} virtualBuses={buses.virtual}
virtualBuses={buses.virtual} name={
/> isPhysical
</Grid> ? `#${stripId + 1}`
</React.Fragment> : virtualInputNames[stripId - strips.physical]
))} }
/>
</Grid>
</React.Fragment>
);
})}
</Grid> </Grid>
<Typography variant="h5">Outputs</Typography> <Typography variant="h5">Outputs</Typography>
<BusesList width={width} physical={buses.physical} virtual={buses.virtual} /> <BusesList
width={width}
physical={buses.physical}
virtual={buses.virtual}
/>
</div> </div>
</Container> </Container>
<Snackbar <Snackbar
@ -168,15 +182,16 @@ export default function Home() {
message={ message={
<> <>
<Typography fontWeight={600}>Tips:</Typography> <Typography fontWeight={600}>Tips:</Typography>
<Typography component={"span"}> <Typography component={"span"}>
<Typography variant={"button"} fontWeight={500}> <Typography variant={"button"} fontWeight={500}>
Double {isItMobileDevice? "tap": "click"} Double {isItMobileDevice ? "tap" : "click"}
</Typography>{" "} </Typography>{" "}
on any gain slider to reset it's value to 0 dB<br/> on any gain slider to reset it's value to 0 dB
<br />
</Typography> </Typography>
<Typography component={"span"}> <Typography component={"span"}>
<Typography variant={"button"} fontWeight={500}> <Typography variant={"button"} fontWeight={500}>
Triple {isItMobileDevice? "tap": "click"} Triple {isItMobileDevice ? "tap" : "click"}
</Typography>{" "} </Typography>{" "}
on empty space for toggle fullscreen on empty space for toggle fullscreen
</Typography> </Typography>

View File

@ -1,5 +1,6 @@
export function isItMobile(userAgent: string) { export function isItMobile(userAgent: string) {
return ( return (
userAgent.includes("Android") ||
/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test( /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(
userAgent userAgent
) || ) ||