modified: package-lock.json

modified:   package.json
	modified:   src/app/component/Bus.tsx
	modified:   src/app/component/BusesList.tsx
	deleted:    src/app/component/GainSlider.tsx
	modified:   src/app/component/Strip.tsx
	modified:   src/app/page.tsx
This commit is contained in:
Maksym 2024-10-16 19:15:11 +02:00
parent 3b2fb0264e
commit 4763e22691
7 changed files with 148 additions and 212 deletions

8
package-lock.json generated
View File

@ -10,7 +10,7 @@
"dependencies": { "dependencies": {
"@emotion/react": "^11.11.4", "@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5", "@emotion/styled": "^11.11.5",
"@fontsource/roboto": "^5.0.14", "@fontsource/roboto": "^5.1.0",
"@mui/material": "^5.15.20", "@mui/material": "^5.15.20",
"next": "14.2.4", "next": "14.2.4",
"next-pwa": "^5.6.0", "next-pwa": "^5.6.0",
@ -1968,9 +1968,9 @@
"integrity": "sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw==" "integrity": "sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw=="
}, },
"node_modules/@fontsource/roboto": { "node_modules/@fontsource/roboto": {
"version": "5.0.14", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.0.14.tgz", "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.1.0.tgz",
"integrity": "sha512-zHAxlTTm9RuRn9/StwclFJChf3z9+fBrOxC3fw71htjHP1BgXNISwRjdJtAKAmMe5S2BzgpnjkQR93P9EZYI/Q==", "integrity": "sha512-cFRRC1s6RqPygeZ8Uw/acwVHqih8Czjt6Q0MwoUoDe9U3m4dH1HmNDRBZyqlMSFwgNAUKgFImncKdmDHyKpwdg==",
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/@humanwhocodes/config-array": { "node_modules/@humanwhocodes/config-array": {

View File

@ -11,7 +11,7 @@
"dependencies": { "dependencies": {
"@emotion/react": "^11.11.4", "@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5", "@emotion/styled": "^11.11.5",
"@fontsource/roboto": "^5.0.14", "@fontsource/roboto": "^5.1.0",
"@mui/material": "^5.15.20", "@mui/material": "^5.15.20",
"next": "14.2.4", "next": "14.2.4",
"next-pwa": "^5.6.0", "next-pwa": "^5.6.0",

View File

@ -14,7 +14,7 @@ import React from "react";
import { StripBusOutput, StripBusOutputEvent } from "./StripBusOutput"; import { StripBusOutput, StripBusOutputEvent } from "./StripBusOutput";
import { EventCounter } from "@/utils/EventCounter"; import { EventCounter } from "@/utils/EventCounter";
import { getComponentState } from "@/utils/GetComponentState"; import { getComponentState } from "@/utils/GetComponentState";
import { GainSlider, GainSliderOnChangeEvent } from "./GainSlider"; import { stopPropagation } from "@/utils/StopPropagation";
export interface BusProps { export interface BusProps {
values?: Partial<Omit<BusState, StateDefaultValueException>>; values?: Partial<Omit<BusState, StateDefaultValueException>>;
@ -28,16 +28,17 @@ type StateDefaultValueException = "";
export interface BusState { export interface BusState {
muted: boolean; muted: boolean;
selected: boolean; selected: boolean;
gain: number;
} }
export type BusEvent = BusMuted | BusGainChanged | BusSelectToggle; export type BusEvent = BusMuted | BusGainChanged | BusSelectToggle;
export interface BusGainChanged { export interface BusGainChanged {
type: "StripGainChanged"; type: "BusGainChanged";
gain: number; gain: number;
} }
export interface BusMuted { export interface BusMuted {
type: "StripMuted"; type: "BusMuted";
muted: boolean; muted: boolean;
} }
export interface BusSelectToggle { export interface BusSelectToggle {
@ -49,11 +50,21 @@ export class Bus extends React.Component<BusProps, BusState> {
private defaultValues: Omit<BusState, StateDefaultValueException> = { private defaultValues: Omit<BusState, StateDefaultValueException> = {
selected: false, selected: false,
muted: false, muted: false,
gain: 0,
}; };
private eventCounter = new EventCounter<"onSliderResetDefaults">();
constructor(props: BusProps) { constructor(props: BusProps) {
super(props); super(props);
let { values: initialValues } = props; let { values: initialValues } = props;
this.state = getComponentState<BusState, "">(props.values, this.defaultValues); this.state = getComponentState<BusState, "">(
props.values,
this.defaultValues
);
this.eventCounter.on({
callback: this.onResetDefaults.bind(this),
count: 2,
name: "onSliderResetDefaults",
});
} }
onKeyDown(event: React.KeyboardEvent) { onKeyDown(event: React.KeyboardEvent) {
console.log(event); console.log(event);
@ -62,25 +73,43 @@ export class Bus extends React.Component<BusProps, BusState> {
event.preventDefault(); event.preventDefault();
} }
} }
onGainSliderChange(event: GainSliderOnChangeEvent) { _setGain(gain: number) {
const { gain, type } = event; this.setState({
if (type === "GainChanged") gain,
});
this.props.onChange({ this.props.onChange({
type: "StripGainChanged", type: "BusGainChanged",
gain, gain,
}); });
} }
onResetDefaults() {
this._setGain(this.defaultValues.gain);
}
onGainSliderChange(event: Event) {
stopPropagation(event);
const multiplier = (event as unknown as React.KeyboardEvent).shiftKey
? 2
: 1;
const changedGain =
parseFloat((event.target as HTMLInputElement).value) * multiplier;
this._setGain(changedGain);
}
onMuteToggle(event: React.MouseEvent) { onMuteToggle(event: React.MouseEvent) {
this.stopPropagation(event); stopPropagation(event);
this.setState( this.setState(
(state) => ({ muted: !state.muted }), (state) => ({ muted: !state.muted }),
() => { () => {
this.props.onChange({ type: "StripMuted", muted: this.state.muted }); this.props.onChange({ type: "BusMuted", muted: this.state.muted });
} }
); );
} }
onSliderClick(event: React.MouseEvent) {
stopPropagation(event);
this.eventCounter.emit("onSliderResetDefaults");
}
onSelectToggle(event: React.MouseEvent) { onSelectToggle(event: React.MouseEvent) {
this.stopPropagation(event); stopPropagation(event);
this.setState( this.setState(
(state) => ({ selected: !state.selected }), (state) => ({ selected: !state.selected }),
() => { () => {
@ -91,10 +120,6 @@ export class Bus extends React.Component<BusProps, BusState> {
} }
); );
} }
stopPropagation(event: Event | React.MouseEvent) {
if (event instanceof Event) event.stopImmediatePropagation();
else event.nativeEvent.stopImmediatePropagation();
}
render() { render() {
return ( return (
<> <>
@ -147,9 +172,23 @@ export class Bus extends React.Component<BusProps, BusState> {
{this.state.gain}dB {this.state.gain}dB
</Typography> </Typography>
)} */} )} */}
<GainSlider <Slider
sliderProps={{ valueLabelDisplay={this.props.width > 600 ? "off" : "auto"}
valueLabelDisplay: this.props.width > 600 ? "off" : "on", value={this.state.gain}
color={
this.state.gain > 0 && this.state.gain < 12
? "warning"
: this.state.gain <= 0
? "primary"
: "error"
}
min={-60}
max={12}
step={0.1}
slotProps={{
root: {
onClick: (e) => this.onSliderClick(e),
},
}} }}
onChange={(e) => this.onGainSliderChange(e)} onChange={(e) => this.onGainSliderChange(e)}
/> />

View File

@ -1,16 +1,24 @@
import { Chip, Stack, StackOwnProps, Theme, Typography, useTheme, withTheme } from "@mui/material"; import {
Chip,
Stack,
StackOwnProps,
Theme,
Typography,
useTheme,
withTheme,
} from "@mui/material";
import React from "react"; import React from "react";
import { Bus } from "./Bus"; import { Bus, BusEvent } from "./Bus";
import { range } from "@/utils/Range"; import { range } from "@/utils/Range";
export interface BusesListProps { export interface BusesListProps {
physical: number; physical: number;
virtual: number; virtual: number;
width: number; width: number;
stackProps?: StackOwnProps stackProps?: StackOwnProps;
} onChange: (event: BusEvent, id: number) => any;
export interface BusesListState {
} }
export interface BusesListState {}
export class BusesList extends React.Component<BusesListProps, BusesListState> { export class BusesList extends React.Component<BusesListProps, BusesListState> {
constructor(props: BusesListProps) { constructor(props: BusesListProps) {
@ -27,18 +35,28 @@ export class BusesList extends React.Component<BusesListProps, BusesListState> {
isPhysical ? busId + 1 : busId - this.props.physical + 1 isPhysical ? busId + 1 : busId - this.props.physical + 1
}`; }`;
return ( return (
<Stack direction={"row"} sx={{width:"100%"}} alignItems={"center"} <Stack
direction={"row"}
sx={{ width: "100%" }}
alignItems={"center"}
key={name} key={name}
> >
{/* <Chip variant="outlined" sx={{marginInlineEnd: "15px"}} label={name}/> */} <Bus
<Bus width={this.props.width} name={name} onChange={console.log} /> width={this.props.width}
name={name}
onChange={(e) => this.props.onChange(e, busId)}
/>
</Stack> </Stack>
); );
} }
render(): React.ReactNode { render(): React.ReactNode {
if (this.props.width > 800) { if (this.props.width > 800) {
return ( return (
<Stack {...this.props.stackProps} direction={"row"} spacing={this.props.width > 1200 ? 4 : 2}> <Stack
{...this.props.stackProps}
direction={"row"}
spacing={this.props.width > 1200 ? 4 : 2}
>
<Stack sx={{ width: "50%" }}> <Stack sx={{ width: "50%" }}>
{range(this.props.physical).map(this.mapCallback.bind(this))} {range(this.props.physical).map(this.mapCallback.bind(this))}
</Stack> </Stack>

View File

@ -1,153 +0,0 @@
import { EventCounter } from "@/utils/EventCounter";
import { getComponentState } from "@/utils/GetComponentState";
import { stopPropagation } from "@/utils/StopPropagation";
import { Slider, SliderOwnProps, styled } from "@mui/material";
import React from "react";
export type GainSliderOnChangeEventType = "GainChanged";
export interface GainSliderOnChangeEvent {
type: GainSliderOnChangeEventType;
gain: number;
}
export interface GainSliderOwnProps {
gain?: number;
onChange: (event: GainSliderOnChangeEvent) => any;
}
export interface GainSliderOwnState {
gainPercent: number;
}
export type GainSliderProps = GainSliderOwnProps& {sliderProps?: SliderOwnProps}
export const SliderStyled = styled(Slider)(({ theme }) => ({
"& .MuiSlider-thumb": {
width: "25px",
height: "25px",
},
"& .MuiSlider-valueLabel": {
top: 10,
left: -10,
fontFamily: '"Roboto","Helvetica","Arial",sans-serif',
fontWeight: 400,
fontSize: "0.75rem",
width: "4em",
letterSpacing: "0.03333em",
backgroundColor: "unset",
color: theme.palette.text.primary,
"&::before": {
display: "none",
},
"& *": {
background: "transparent",
color: theme.palette.mode === "dark" ? "#000" : "#fff",
},
},
}));
export class GainSlider extends React.Component<
GainSliderProps,
GainSliderOwnState
> {
/*[fromRangeMin, fromRangeMax],
[toRangeMin, toRangeMax]*/
private static o = {
//originally gain to percent
lessThanHalf: [
[-60, 0],
[0, 50],
],
halfOrMore: [
[0, 12],
[50, 100],
],
};
static gainToPercent(gain: number) {
const key: keyof typeof GainSlider.o =
gain >= 0 ? "halfOrMore" : "lessThanHalf";
return this.convertRange(gain, ...GainSlider.o[key]);
}
static percentToGain(percent: number) {
const key: keyof typeof GainSlider.o =
percent >= 50 ? "halfOrMore" : "lessThanHalf";
return this.convertRange(
percent,
...GainSlider.o[key].map((i) => i.reverse()).reverse()
);
}
static convertRange(value: number, ...ranges: number[][]) {
const [output, input] = ranges;
return (
((value - output[0]) * (input[1] - input[0])) / (output[1] - output[0]) +
input[0]
);
}
static stopPropagation(event: Event | React.MouseEvent) {
if (event instanceof Event) event.stopImmediatePropagation();
else event.nativeEvent.stopImmediatePropagation();
}
private eventCounter = new EventCounter<"SliderClick">();
defaultValues: GainSliderOwnState = { gainPercent: 50 };
constructor(props: GainSliderProps) {
super(props);
this.state = getComponentState<GainSliderOwnState, "gainPercent">(
props,
this.defaultValues
);
this.eventCounter.on({
name: "SliderClick",
count: 2,
callback: this.resetGain.bind(this),
});
}
resetGain() {
this.setState({ gainPercent: this.defaultValues.gainPercent });
}
onGainChange(event: Event): void {
let changedGain = parseFloat((event.target as HTMLInputElement).value);
if (changedGain > 100) changedGain = 100;
else if (changedGain < 0) changedGain = 0;
this.setState({
gainPercent: changedGain,
});
this.props.onChange({
type: "GainChanged",
gain: Math.round(GainSlider.percentToGain(changedGain) * 10) / 10,
});
}
onSliderClick(e: React.MouseEvent<HTMLSpanElement, MouseEvent>): void {
stopPropagation(e);
this.eventCounter.emit("SliderClick");
}
render(): React.ReactNode {
return (
<SliderStyled
sx={{
// margin: "5px",
marginInline: "10px",
}}
valueLabelFormat={(e) => `${e.toFixed(1)} dB`}
// orientation="vertical"
color={
(this.state.gainPercent < GainSlider.gainToPercent(0) && "info") ||
(this.state.gainPercent > GainSlider.gainToPercent(0) &&
this.state.gainPercent < 100 &&
"warning") ||
(this.state.gainPercent === 100 && "error") ||
"primary"
}
step={1}
min={0}
max={100}
value={this.state.gainPercent}
onChange={(e) => this.onGainChange(e)}
onClick={(e) => this.onSliderClick(e)}
scale={(v) => GainSlider.percentToGain(v)}
{...this.props.sliderProps}
/>
);
}
}

View File

@ -81,9 +81,6 @@ export class Strip extends React.Component<StripProps, StripState> {
name: "onSliderResetDefaults", name: "onSliderResetDefaults",
}); });
} }
onResetDefaults() {
this.setState({ gain: this.defaultValues.gain });
}
onSliderClick(event: React.MouseEvent) { onSliderClick(event: React.MouseEvent) {
stopPropagation(event); stopPropagation(event);
this.eventCounter.emit("onSliderResetDefaults"); this.eventCounter.emit("onSliderResetDefaults");
@ -95,20 +92,27 @@ export class Strip extends React.Component<StripProps, StripState> {
event.preventDefault(); event.preventDefault();
} }
} }
onGainChange(event: Event) { _setGain(gain: number) {
this.setState({
gain,
});
this.props.onChange({
type: "StripGainChanged",
gain,
});
}
onResetDefaults() {
this._setGain(this.defaultValues.gain);
}
onGainSliderChange(event: Event) {
stopPropagation(event); stopPropagation(event);
const multiplier = (event as unknown as React.KeyboardEvent).shiftKey const multiplier = (event as unknown as React.KeyboardEvent).shiftKey
? 2 ? 2
: 1; : 1;
const changedGain = const changedGain =
parseFloat((event.target as HTMLInputElement).value) * multiplier; parseFloat((event.target as HTMLInputElement).value) * multiplier;
this.setState({ this._setGain(changedGain);
gain: changedGain,
});
this.props.onChange({
type: "StripGainChanged",
gain: changedGain,
});
} }
onBusChange(event: StripBusOutputEvent) { onBusChange(event: StripBusOutputEvent) {
const buses = this.state.outputBuses; const buses = this.state.outputBuses;
@ -174,12 +178,17 @@ export class Strip extends React.Component<StripProps, StripState> {
)} )}
<Slider <Slider
orientation="vertical" orientation="vertical"
valueLabelDisplay={this.props.width > 600 ? "off" : "auto"}
value={this.state.gain}
min={-60}
max={12}
step={0.1}
slotProps={{ slotProps={{
root: { root: {
onClick: (e) => this.onSliderClick(e), onClick: (e) => this.onSliderClick(e),
}, },
}} }}
onChange={(e) => this.onGainChange(e)} onChange={(e) => this.onGainSliderChange(e)}
/> />
</Stack> </Stack>
<Stack> <Stack>

View File

@ -14,7 +14,7 @@ import {
Typography, Typography,
useMediaQuery, useMediaQuery,
} from "@mui/material"; } from "@mui/material";
import { Bus } from "./component/Bus"; import { Bus, BusEvent } from "./component/Bus";
import Grid from "@mui/material/Unstable_Grid2"; import Grid from "@mui/material/Unstable_Grid2";
import { EventCounter } from "@/utils/EventCounter"; import { EventCounter } from "@/utils/EventCounter";
import { BusesList } from "./component/BusesList"; import { BusesList } from "./component/BusesList";
@ -33,18 +33,40 @@ export default function Home() {
}), }),
[] []
); );
function sendApi(path: string, fetchOptions: RequestInit) { function sendApi(
const url = new URL(path, "http://127.0.0.1:3000/"); path: string,
return fetch(url.toString(), fetchOptions).then((r) => r.json()); method: RequestInit["method"],
body: any,
fetchOptions?: RequestInit
) {
const url = new URL(path, "http://127.0.0.1:3001/");
return fetch(url.toString(), {
method,
body: body !== undefined ? JSON.stringify(body) : undefined,
headers: (() =>
body !== undefined && body !== null
? { "Content-Type": "application/json" }
: ({} as RequestInit["headers"]))(),
...fetchOptions,
}).then((r) => r.json());
} }
function onStripEvent(event: StripEvent, stripId: number) { function onEvent(event: StripEvent | BusEvent, id: number) {
console.log(event); console.log(event);
switch (event.type) { switch (event.type) {
case "StripMuted": { case "StripMuted": {
sendApi(`/strip/${stripId}/mute`, { sendApi(`/strip/${id}/mute`, "PATCH", { muteState: event.muted });
method: "PATCH", break;
body: JSON.stringify({ muteState: event.muted }), }
}); case "StripGainChanged": {
sendApi(`/strip/${id}/gain`, "PATCH", { gain: event.gain });
break;
}
case "BusMuted": {
sendApi(`/bus/${id}/mute`, "PATCH", { muteState: event.muted });
break;
}
case "BusGainChanged": {
sendApi(`/bus/${id}/gain`, "PATCH", { gain: event.gain });
break; break;
} }
} }
@ -168,7 +190,7 @@ export default function Home() {
stripId + 1 stripId + 1
}`}</Typography> */} }`}</Typography> */}
<Strip <Strip
onChange={(ev) => onStripEvent(ev, stripId)} onChange={(ev) => onEvent(ev, stripId)}
physicalBuses={buses.physical} physicalBuses={buses.physical}
virtualBuses={buses.virtual} virtualBuses={buses.virtual}
width={width} width={width}
@ -185,6 +207,7 @@ export default function Home() {
</Grid> </Grid>
<Typography variant="h5">Outputs</Typography> <Typography variant="h5">Outputs</Typography>
<BusesList <BusesList
onChange={onEvent}
stackProps={{ sx: { paddingBlockStart: "10px" } }} stackProps={{ sx: { paddingBlockStart: "10px" } }}
width={width} width={width}
physical={buses.physical} physical={buses.physical}