diff --git a/package-lock.json b/package-lock.json index 2bf405c..1252292 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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": { diff --git a/package.json b/package.json index ad64dcb..848d3d7 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/app/component/Bus.tsx b/src/app/component/Bus.tsx index f653f9b..27aeaa7 100644 --- a/src/app/component/Bus.tsx +++ b/src/app/component/Bus.tsx @@ -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>; @@ -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 { private defaultValues: Omit = { selected: false, muted: false, + gain: 0, }; + private eventCounter = new EventCounter<"onSliderResetDefaults">(); constructor(props: BusProps) { super(props); let { values: initialValues } = props; - this.state = getComponentState(props.values, this.defaultValues); + this.state = getComponentState( + 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 { 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 { } ); } - 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 { {this.state.gain}dB )} */} - 600 ? "off" : "on", + 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)} /> diff --git a/src/app/component/BusesList.tsx b/src/app/component/BusesList.tsx index 42aa200..6fdd967 100644 --- a/src/app/component/BusesList.tsx +++ b/src/app/component/BusesList.tsx @@ -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 { constructor(props: BusesListProps) { @@ -24,21 +32,31 @@ export class BusesList extends React.Component { 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 ( - - {/* */} - + this.props.onChange(e, busId)} + /> ); } render(): React.ReactNode { if (this.props.width > 800) { return ( - 1200 ? 4 : 2}> + 1200 ? 4 : 2} + > {range(this.props.physical).map(this.mapCallback.bind(this))} diff --git a/src/app/component/GainSlider.tsx b/src/app/component/GainSlider.tsx deleted file mode 100644 index 95e4b42..0000000 --- a/src/app/component/GainSlider.tsx +++ /dev/null @@ -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( - 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): void { - stopPropagation(e); - this.eventCounter.emit("SliderClick"); - } - - render(): React.ReactNode { - return ( - `${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} - /> - ); - } -} diff --git a/src/app/component/Strip.tsx b/src/app/component/Strip.tsx index 723179e..bf002a0 100644 --- a/src/app/component/Strip.tsx +++ b/src/app/component/Strip.tsx @@ -81,9 +81,6 @@ export class Strip extends React.Component { 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 { 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 { )} 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)} /> diff --git a/src/app/page.tsx b/src/app/page.tsx index 73958ec..6e03914 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -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 }`} */} onStripEvent(ev, stripId)} + onChange={(ev) => onEvent(ev, stripId)} physicalBuses={buses.physical} virtualBuses={buses.virtual} width={width} @@ -185,6 +207,7 @@ export default function Home() { Outputs