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

View File

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

View File

@ -14,7 +14,7 @@ import React from "react";
import { StripBusOutput, StripBusOutputEvent } from "./StripBusOutput";
import { EventCounter } from "@/utils/EventCounter";
import { getComponentState } from "@/utils/GetComponentState";
import { GainSlider, GainSliderOnChangeEvent } from "./GainSlider";
import { stopPropagation } from "@/utils/StopPropagation";
export interface BusProps {
values?: Partial<Omit<BusState, StateDefaultValueException>>;
@ -28,16 +28,17 @@ type StateDefaultValueException = "";
export interface BusState {
muted: boolean;
selected: boolean;
gain: number;
}
export type BusEvent = BusMuted | BusGainChanged | BusSelectToggle;
export interface BusGainChanged {
type: "StripGainChanged";
type: "BusGainChanged";
gain: number;
}
export interface BusMuted {
type: "StripMuted";
type: "BusMuted";
muted: boolean;
}
export interface BusSelectToggle {
@ -49,11 +50,21 @@ export class Bus extends React.Component<BusProps, BusState> {
private defaultValues: Omit<BusState, StateDefaultValueException> = {
selected: false,
muted: false,
gain: 0,
};
private eventCounter = new EventCounter<"onSliderResetDefaults">();
constructor(props: BusProps) {
super(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) {
console.log(event);
@ -62,25 +73,43 @@ export class Bus extends React.Component<BusProps, BusState> {
event.preventDefault();
}
}
onGainSliderChange(event: GainSliderOnChangeEvent) {
const { gain, type } = event;
if (type === "GainChanged")
this.props.onChange({
type: "StripGainChanged",
gain,
});
_setGain(gain: number) {
this.setState({
gain,
});
this.props.onChange({
type: "BusGainChanged",
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) {
this.stopPropagation(event);
stopPropagation(event);
this.setState(
(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) {
this.stopPropagation(event);
stopPropagation(event);
this.setState(
(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() {
return (
<>
@ -147,9 +172,23 @@ export class Bus extends React.Component<BusProps, BusState> {
{this.state.gain}dB
</Typography>
)} */}
<GainSlider
sliderProps={{
valueLabelDisplay: this.props.width > 600 ? "off" : "on",
<Slider
valueLabelDisplay={this.props.width > 600 ? "off" : "auto"}
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)}
/>

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 { Bus } from "./Bus";
import { Bus, BusEvent } from "./Bus";
import { range } from "@/utils/Range";
export interface BusesListProps {
physical: number;
virtual: number;
width: number;
stackProps?: StackOwnProps
}
export interface BusesListState {
stackProps?: StackOwnProps;
onChange: (event: BusEvent, id: number) => any;
}
export interface BusesListState {}
export class BusesList extends React.Component<BusesListProps, BusesListState> {
constructor(props: BusesListProps) {
@ -24,21 +32,31 @@ export class BusesList extends React.Component<BusesListProps, BusesListState> {
const [busId] = args;
const isPhysical = busId < this.props.physical;
const name = `${isPhysical ? "A" : "B"}${
isPhysical ? busId + 1 : busId - this.props.physical + 1
}`;
isPhysical ? busId + 1 : busId - this.props.physical + 1
}`;
return (
<Stack direction={"row"} sx={{width:"100%"}} alignItems={"center"}
<Stack
direction={"row"}
sx={{ width: "100%" }}
alignItems={"center"}
key={name}
>
{/* <Chip variant="outlined" sx={{marginInlineEnd: "15px"}} label={name}/> */}
<Bus width={this.props.width} name={name} onChange={console.log} />
<Bus
width={this.props.width}
name={name}
onChange={(e) => this.props.onChange(e, busId)}
/>
</Stack>
);
}
render(): React.ReactNode {
if (this.props.width > 800) {
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%" }}>
{range(this.props.physical).map(this.mapCallback.bind(this))}
</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",
});
}
onResetDefaults() {
this.setState({ gain: this.defaultValues.gain });
}
onSliderClick(event: React.MouseEvent) {
stopPropagation(event);
this.eventCounter.emit("onSliderResetDefaults");
@ -95,20 +92,27 @@ export class Strip extends React.Component<StripProps, StripState> {
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);
const multiplier = (event as unknown as React.KeyboardEvent).shiftKey
? 2
: 1;
const changedGain =
parseFloat((event.target as HTMLInputElement).value) * multiplier;
this.setState({
gain: changedGain,
});
this.props.onChange({
type: "StripGainChanged",
gain: changedGain,
});
this._setGain(changedGain);
}
onBusChange(event: StripBusOutputEvent) {
const buses = this.state.outputBuses;
@ -174,12 +178,17 @@ export class Strip extends React.Component<StripProps, StripState> {
)}
<Slider
orientation="vertical"
valueLabelDisplay={this.props.width > 600 ? "off" : "auto"}
value={this.state.gain}
min={-60}
max={12}
step={0.1}
slotProps={{
root: {
onClick: (e) => this.onSliderClick(e),
},
}}
onChange={(e) => this.onGainChange(e)}
onChange={(e) => this.onGainSliderChange(e)}
/>
</Stack>
<Stack>

View File

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