diff --git a/src/app/component/Bus.tsx b/src/app/component/Bus.tsx index 822b2c7..f653f9b 100644 --- a/src/app/component/Bus.tsx +++ b/src/app/component/Bus.tsx @@ -13,6 +13,8 @@ import { import React from "react"; import { StripBusOutput, StripBusOutputEvent } from "./StripBusOutput"; import { EventCounter } from "@/utils/EventCounter"; +import { getComponentState } from "@/utils/GetComponentState"; +import { GainSlider, GainSliderOnChangeEvent } from "./GainSlider"; export interface BusProps { values?: Partial>; @@ -24,7 +26,6 @@ export interface BusProps { type StateDefaultValueException = ""; export interface BusState { - gain: number; muted: boolean; selected: boolean; } @@ -45,56 +46,14 @@ export interface BusSelectToggle { } export class Bus extends React.Component { - private eventCounter = new EventCounter<"onSliderResetDefaults">(); private defaultValues: Omit = { - gain: 0, selected: false, muted: false, }; - slider = styled(Slider)(({ theme }) => ({ - "& .MuiSlider-valueLabel": { - top: 0, - 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" ? "#fff" : "#000", - }, - }, - })); constructor(props: BusProps) { super(props); let { values: initialValues } = props; - const getValue = (name: keyof typeof this.defaultValues) => { - if (initialValues === undefined) return this.defaultValues[name]; - return initialValues[name] !== undefined - ? this.defaultValues[name] - : initialValues[name]; - }; - this.state = { - ...(Object.fromEntries( - Object.entries(this.defaultValues).map(([name]) => [ - name, - getValue(name as keyof typeof this.defaultValues) as any, - ]) - ) as Omit), - }; - this.eventCounter.on({ - callback: this.onResetDefaults.bind(this), - count: 2, - name: "onSliderResetDefaults", - }); - } - onResetDefaults() { - this.setState({ gain: this.defaultValues.gain }); + this.state = getComponentState(props.values, this.defaultValues); } onKeyDown(event: React.KeyboardEvent) { console.log(event); @@ -103,24 +62,13 @@ export class Bus extends React.Component { event.preventDefault(); } } - onGainChange(event: Event | React.ChangeEvent) { - let changedGain = parseFloat((event.target as HTMLInputElement).value); - if (changedGain > 12) changedGain = 12; - else if (changedGain < -60) changedGain = -60; - this.setState({ - gain: changedGain, - }); - this.props.onChange({ - type: "StripGainChanged", - gain: changedGain, - }); - } - onStripWheel(event: React.WheelEvent) { - console.log(event); - } - onSliderClick(event: React.MouseEvent) { - this.stopPropagation(event); - this.eventCounter.emit("onSliderResetDefaults"); + onGainSliderChange(event: GainSliderOnChangeEvent) { + const { gain, type } = event; + if (type === "GainChanged") + this.props.onChange({ + type: "StripGainChanged", + gain, + }); } onMuteToggle(event: React.MouseEvent) { this.stopPropagation(event); @@ -156,26 +104,36 @@ export class Bus extends React.Component { spacing={0} sx={{ width: "inherit" }} > - {this.props.width > 600 ? ( - 600 ? ( + // 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" }} - /> + // value={Bus.percentToGain(this.state.gainPercent).toFixed(1)} + // onWheel={(e) => this.onStripWheel(e)} + // onClick={(e) => this.stopPropagation(e)} + // onChange={(e) => { + // e.target.value = Bus.gainToPercent( + // parseInt(e.target.value) + // ).toFixed(0); + // this.onGainSliderChange(e as unknown as Event); + // }} + // size="small" + // label={this.props.name} + // type="number" + // variant="outlined" + // sx={{ + // minWidth: "100px", + // maxWidth: "100px", + // height: "", + // marginBlockEnd: "8px", + // }} + // /> ) : ( {this.props.name} - )} + )} */} {/* {this.props.width <= 600 && ( { {this.state.gain}dB )} */} - 600 ? "off" : "on", }} - valueLabelFormat={(e) => `${e} dB`} - // orientation="vertical" - color={ - (this.state.gain > 0 && this.state.gain < 12 && "warning") || - (this.state.gain >= 12 && "error") || - (this.state.gain < 0 && "info") || - "primary" - } - defaultValue={0.0} - step={0.1} - min={-60} - max={12} - value={this.state.gain} - valueLabelDisplay={this.props.width > 600 ? "off" : "on"} - onChange={(e) => this.onGainChange(e)} - onClick={(e) => this.onSliderClick(e)} + onChange={(e) => this.onGainSliderChange(e)} /> diff --git a/src/app/component/GainSlider.tsx b/src/app/component/GainSlider.tsx new file mode 100644 index 0000000..95e4b42 --- /dev/null +++ b/src/app/component/GainSlider.tsx @@ -0,0 +1,153 @@ +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 a8268e4..723179e 100644 --- a/src/app/component/Strip.tsx +++ b/src/app/component/Strip.tsx @@ -7,12 +7,14 @@ import { Input, Slider, Stack, + styled, TextField, Typography, } from "@mui/material"; import React from "react"; import { StripBusOutput, StripBusOutputEvent } from "./StripBusOutput"; import { EventCounter } from "@/utils/EventCounter"; +import { stopPropagation } from "@/utils/StopPropagation"; export interface StripProps { physicalBuses: number; @@ -83,7 +85,7 @@ export class Strip extends React.Component { this.setState({ gain: this.defaultValues.gain }); } onSliderClick(event: React.MouseEvent) { - this.stopPropagation(event); + stopPropagation(event); this.eventCounter.emit("onSliderResetDefaults"); } onKeyDown(event: React.KeyboardEvent) { @@ -94,7 +96,7 @@ export class Strip extends React.Component { } } onGainChange(event: Event) { - this.stopPropagation(event); + stopPropagation(event); const multiplier = (event as unknown as React.KeyboardEvent).shiftKey ? 2 : 1; @@ -127,7 +129,7 @@ export class Strip extends React.Component { console.log(event); } onMuteToggle(event: React.MouseEvent) { - this.stopPropagation(event); + stopPropagation(event); this.setState( (state) => ({ muted: !state.muted }), () => { @@ -135,10 +137,7 @@ export class Strip extends React.Component { } ); } - stopPropagation(event: Event | React.MouseEvent) { - if (event instanceof Event) event.stopImmediatePropagation(); - else event.nativeEvent.stopImmediatePropagation(); - } + render() { return ( <> @@ -158,7 +157,7 @@ export class Strip extends React.Component { // }} value={this.state.gain} onWheel={(e) => this.onStripWheel(e)} - onClick={(e) => this.stopPropagation(e)} + onClick={(e) => stopPropagation(e)} size="small" type="number" label={this.props.name} @@ -167,32 +166,20 @@ export class Strip extends React.Component { /> ) : ( <> - {this.props.name} + {/* {this.props.name} {this.state.gain} dB - + */} )} 0 && this.state.gain < 12 && "warning") || - (this.state.gain >= 12 && "error") || - (this.state.gain < 0 && "info") || - "primary" - } orientation="vertical" - defaultValue={0.0} - step={0.1} - min={-60} - max={12} - value={this.state.gain} - aria-label="Temperature" + slotProps={{ + root: { + onClick: (e) => this.onSliderClick(e), + }, + }} onChange={(e) => this.onGainChange(e)} - onClick={(e) => this.onSliderClick(e)} /> diff --git a/src/app/page.tsx b/src/app/page.tsx index d8263ee..73958ec 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,5 +1,5 @@ "use client"; -import React, { useEffect, useMemo, useState } from "react"; +import React, { createRef, useEffect, useMemo, useState } from "react"; import { Strip, StripEvent } from "./component/Strip"; import { Button, @@ -33,8 +33,21 @@ export default function Home() { }), [] ); - function onStripEvent(event: StripEvent) { + 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 onStripEvent(event: StripEvent, stripId: number) { console.log(event); + switch (event.type) { + case "StripMuted": { + sendApi(`/strip/${stripId}/mute`, { + method: "PATCH", + body: JSON.stringify({ muteState: event.muted }), + }); + break; + } + } } const isSnackBarHidden = useMemo(() => { if (typeof localStorage === "undefined") return false; @@ -114,7 +127,7 @@ export default function Home() { }} color="secondary" > - Gotcha + OK ); @@ -155,7 +168,7 @@ export default function Home() { stripId + 1 }`} */} onStripEvent(ev)} + onChange={(ev) => onStripEvent(ev, stripId)} physicalBuses={buses.physical} virtualBuses={buses.virtual} width={width} diff --git a/src/utils/GetComponentState.ts b/src/utils/GetComponentState.ts new file mode 100644 index 0000000..1a2baee --- /dev/null +++ b/src/utils/GetComponentState.ts @@ -0,0 +1,25 @@ +export function getComponentState< + State extends Object, + StateDefaultValueException extends keyof State | "" +>( + initialValues: Partial> | undefined, + defaultValues: State +) { + type InitialValuesType = Omit; + // const { initialValues } = props; + const getValue = (name: keyof InitialValuesType) => { + if (initialValues === undefined) return defaultValues[name]; + return initialValues[name] !== undefined + ? defaultValues[name] + : initialValues[name]; + }; + const state = { + ...(Object.fromEntries( + Object.entries(defaultValues).map(([name]) => [ + name, + getValue(name as keyof InitialValuesType) as any, + ]) + ) as InitialValuesType), + } as State; + return state; +} diff --git a/src/utils/StopPropagation.ts b/src/utils/StopPropagation.ts new file mode 100644 index 0000000..405bc16 --- /dev/null +++ b/src/utils/StopPropagation.ts @@ -0,0 +1,4 @@ +export function stopPropagation(event: Event | React.MouseEvent) { + if (event instanceof Event) event.stopImmediatePropagation(); + else event.nativeEvent.stopImmediatePropagation(); + } \ No newline at end of file