modified: src/app/component/Bus.tsx

modified:   src/app/component/Strip.tsx
	modified:   src/app/layout.tsx
	modified:   src/app/page.tsx
	new file:   src/utils/EventCounter.ts
This commit is contained in:
Maksym 2024-08-06 02:51:59 +02:00
parent df1bb8c35a
commit 16b2de18a9
5 changed files with 318 additions and 183 deletions

View File

@ -3,52 +3,55 @@ import { Button, ButtonGroup, Grid, Input, Slider, Stack } from "@mui/material";
import React from "react"; import React from "react";
import { StripBusOutput, StripBusOutputEvent } from "./StripBusOutput"; import { StripBusOutput, StripBusOutputEvent } from "./StripBusOutput";
export interface StripProps { export interface BusProps {
physicalBuses: number; values?: Partial<BusState>;
virtualBuses: number;
default?: Partial<StripState>;
onChange: (event: BusEvent) => any; onChange: (event: BusEvent) => any;
} }
export interface StripState { export interface BusState {
gain: number; gain: number;
buses: boolean[];
muted: boolean; muted: boolean;
selected: boolean;
} }
export type BusEvent = StripBusOutputChanged | StripMuted | StripGainChanged; export type BusEvent = BusMuted | BusGainChanged | BusSelectToggle;
export interface StripBusOutputChanged extends StripBusOutputEvent { export interface BusGainChanged {
type: "StripBusOutputChanged";
}
export interface StripGainChanged {
type: "StripGainChanged"; type: "StripGainChanged";
gain: number; gain: number;
} }
export interface StripMuted { export interface BusMuted {
type: "StripMuted"; type: "StripMuted";
muted: boolean; muted: boolean;
} }
export interface BusSelectToggle {
export class Bus extends React.Component<StripProps, StripState> { type: "BusSelectToggle";
constructor(props: StripProps) { selected: boolean;
super(props);
const buses = (props.default && props.default.buses) || [];
this.state = {
gain:
props.default && props.default.gain !== undefined
? props.default.gain
: 0,
buses: [...Array(props.physicalBuses + props.virtualBuses)].map((_v, k) =>
buses[k] === undefined ? false : buses[k]
),
muted:
props.default && props.default.muted !== undefined
? props.default.muted
: false,
};
} }
export class Bus extends React.Component<BusProps, BusState> {
constructor(props: BusProps) {
super(props);
const { values: initialValues } = props;
const defaultValues: BusState = {
gain: 0,
selected: false,
muted: false,
};
const getValue = (name: keyof typeof defaultValues) => {
if (initialValues === undefined) return defaultValues[name];
return initialValues[name] !== undefined
? defaultValues[name]
: initialValues[name];
};
this.state = Object.fromEntries(
Object.entries(defaultValues).map(([name]) => [
name,
getValue(name as keyof typeof defaultValues) as any,
])
) as BusState;
}
onResetDefaults(e: React.MouseEvent) {}
onKeyDown(event: React.KeyboardEvent) { onKeyDown(event: React.KeyboardEvent) {
console.log(event); console.log(event);
@ -70,21 +73,6 @@ export class Bus extends React.Component<StripProps, StripState> {
gain: changedGain, gain: changedGain,
}); });
} }
onBusChange(event: StripBusOutputEvent) {
const buses = this.state.buses;
buses[
event.isVirtual ? this.props.physicalBuses + event.busId : event.busId
] = event.enabled;
this.setState({
buses,
});
this.props.onChange({
type: "StripBusOutputChanged",
busId: event.busId,
isVirtual: event.isVirtual,
enabled: event.enabled,
});
}
onStripWheel(event: React.WheelEvent) { onStripWheel(event: React.WheelEvent) {
console.log(event); console.log(event);
} }
@ -96,11 +84,21 @@ export class Bus extends React.Component<StripProps, StripState> {
} }
); );
} }
onSelectToggle() {
this.setState(
(state) => ({ selected: !state.selected }),
() => {
this.props.onChange({
type: "BusSelectToggle",
selected: this.state.selected,
});
}
);
}
render() { render() {
return ( return (
<> <>
<Grid container sx={{ width: "100%"}}> <Stack alignItems={"center"} direction={"row"} spacing={0}>
<Stack alignItems={"center"} direction={"row"}>
<Input <Input
inputProps={{ inputProps={{
"aria-labelledby": "input-slider", "aria-labelledby": "input-slider",
@ -114,6 +112,7 @@ export class Bus extends React.Component<StripProps, StripState> {
<Slider <Slider
sx={{ sx={{
margin: "5px", margin: "5px",
marginInline: "15px",
}} }}
// orientation="vertical" // orientation="vertical"
defaultValue={0.0} defaultValue={0.0}
@ -138,9 +137,9 @@ export class Bus extends React.Component<StripProps, StripState> {
Mute Mute
</Button> </Button>
<Button <Button
onClick={() => this.onMuteToggle()} onClick={() => this.onSelectToggle()}
sx={{}} sx={{}}
variant={this.state.muted ? "contained" : "outlined"} variant={this.state.selected ? "contained" : "outlined"}
color={"warning"} color={"warning"}
> >
S S
@ -170,7 +169,6 @@ export class Bus extends React.Component<StripProps, StripState> {
))} ))}
</ButtonGroup> </ButtonGroup>
</Stack> */} </Stack> */}
</Grid>
</> </>
); );
} }

View File

@ -1,18 +1,26 @@
"use client"; "use client";
import { Button, ButtonGroup, Grid, Input, Slider, Stack } from "@mui/material"; import {
Box,
Button,
ButtonGroup,
Grid,
Input,
Slider,
Stack,
} from "@mui/material";
import React from "react"; import React from "react";
import { StripBusOutput, StripBusOutputEvent } from "./StripBusOutput"; import { StripBusOutput, StripBusOutputEvent } from "./StripBusOutput";
export interface StripProps { export interface StripProps {
physicalBuses: number; physicalBuses: number;
virtualBuses: number; virtualBuses: number;
default?: Partial<StripState>; values?: Partial<StripState>;
onChange: (event: StripEvent) => any; onChange: (event: StripEvent) => any;
} }
export interface StripState { export interface StripState {
gain: number; gain: number;
buses: boolean[]; outputBuses: boolean[];
muted: boolean; muted: boolean;
} }
@ -34,20 +42,30 @@ export interface StripMuted {
export class Strip extends React.Component<StripProps, StripState> { export class Strip extends React.Component<StripProps, StripState> {
constructor(props: StripProps) { constructor(props: StripProps) {
super(props); super(props);
const buses = (props.default && props.default.buses) || []; const { values: initialValues } = props;
this.state = { const defaultValues: StripState = {
gain: gain: 0,
props.default && props.default.gain !== undefined outputBuses: [...Array(props.physicalBuses + props.virtualBuses)].map(
? props.default.gain (_v, k) => false
: 0,
buses: [...Array(props.physicalBuses + props.virtualBuses)].map((_v, k) =>
buses[k] === undefined ? false : buses[k]
), ),
muted: muted: false,
props.default && props.default.muted !== undefined
? props.default.muted
: false,
}; };
const getValue = (name: keyof typeof defaultValues) => {
if (initialValues === undefined) return defaultValues[name];
return initialValues[name] !== undefined
? defaultValues[name]
: initialValues[name];
};
this.state = Object.fromEntries(
Object.entries(defaultValues).map(([name]) => [
name,
getValue(name as keyof typeof defaultValues) as any,
])
) as StripState;
}
onResetDefaults(e: React.MouseEvent) {
e.stopPropagation();
this.setState({ gain: 0 });
} }
onKeyDown(event: React.KeyboardEvent) { onKeyDown(event: React.KeyboardEvent) {
console.log(event); console.log(event);
@ -71,12 +89,12 @@ export class Strip extends React.Component<StripProps, StripState> {
}); });
} }
onBusChange(event: StripBusOutputEvent) { onBusChange(event: StripBusOutputEvent) {
const buses = this.state.buses; const buses = this.state.outputBuses;
buses[ buses[
event.isVirtual ? this.props.physicalBuses + event.busId : event.busId event.isVirtual ? this.props.physicalBuses + event.busId : event.busId
] = event.enabled; ] = event.enabled;
this.setState({ this.setState({
buses, outputBuses: buses,
}); });
this.props.onChange({ this.props.onChange({
type: "StripBusOutputChanged", type: "StripBusOutputChanged",
@ -87,7 +105,6 @@ export class Strip extends React.Component<StripProps, StripState> {
} }
onStripWheel(event: React.WheelEvent) { onStripWheel(event: React.WheelEvent) {
console.log(event); console.log(event);
} }
onMuteToggle() { onMuteToggle() {
this.setState( this.setState(
@ -100,8 +117,8 @@ export class Strip extends React.Component<StripProps, StripState> {
render() { render() {
return ( return (
<> <>
<Grid container sx={{ minHeight: "263px", minWidth: "112px" }}> <Stack direction={"row"}>
<Stack alignItems={"center"}> <Stack alignItems={"center"} style={{ maxWidth: "min-content" }}>
<Input <Input
inputProps={{ inputProps={{
"aria-labelledby": "input-slider", "aria-labelledby": "input-slider",
@ -110,11 +127,12 @@ export class Strip extends React.Component<StripProps, StripState> {
value={this.state.gain} value={this.state.gain}
onWheel={(ev) => this.onStripWheel(ev)} onWheel={(ev) => this.onStripWheel(ev)}
size="small" size="small"
sx={{ width: "50px" }} sx={{ marginInline: "5px" }}
/> />
<Slider <Slider
sx={{ sx={{
margin: "5px", margin: "5px",
marginBlock: "15px",
}} }}
orientation="vertical" orientation="vertical"
defaultValue={0.0} defaultValue={0.0}
@ -135,6 +153,8 @@ export class Strip extends React.Component<StripProps, StripState> {
onClick={() => this.onMuteToggle()} onClick={() => this.onMuteToggle()}
variant={this.state.muted ? "contained" : "outlined"} variant={this.state.muted ? "contained" : "outlined"}
color={"error"} color={"error"}
style={{ paddingInline: "0px", maxWidth: "5px" }}
size="large"
> >
Mute Mute
</Button> </Button>
@ -157,12 +177,12 @@ export class Strip extends React.Component<StripProps, StripState> {
} }
isVirtual={i < this.props.physicalBuses ? false : true} isVirtual={i < this.props.physicalBuses ? false : true}
onChange={(ev) => this.onBusChange(ev)} onChange={(ev) => this.onBusChange(ev)}
default={{ enabled: this.state.buses[i] }} default={{ enabled: this.state.outputBuses[i] }}
/> />
))} ))}
</ButtonGroup> </ButtonGroup>
</Stack> </Stack>
</Grid> </Stack>
</> </>
); );
} }

View File

@ -16,6 +16,7 @@ export default function RootLayout({
<Head> <Head>
<link rel="manifest" href="/manifest.json" /> <link rel="manifest" href="/manifest.json" />
</Head> </Head>
<body>{children}</body> <body>{children}</body>
</html> </html>
); );

View File

@ -1,5 +1,5 @@
"use client"; "use client";
import React from "react"; import React, { useEffect, useMemo, useState } from "react";
import { Strip, StripEvent } from "./component/Strip"; import { Strip, StripEvent } from "./component/Strip";
import { import {
Button, Button,
@ -7,11 +7,15 @@ import {
createTheme, createTheme,
CssBaseline, CssBaseline,
Divider, Divider,
Slide,
Snackbar,
Stack, Stack,
ThemeProvider, ThemeProvider,
Typography, Typography,
useMediaQuery,
} from "@mui/material"; } from "@mui/material";
import { Bus } from "./component/Bus"; import { Bus } from "./component/Bus";
import Grid from "@mui/material/Unstable_Grid2";
function random(min: number, max: number, floor: boolean = true) { function random(min: number, max: number, floor: boolean = true) {
const value = Math.random() * (max - min + 1) + min; const value = Math.random() * (max - min + 1) + min;
@ -19,92 +23,175 @@ function random(min: number, max: number, floor: boolean = true) {
} }
export default function Home() { export default function Home() {
const theme = useMemo(() => createTheme({ palette: { mode: "dark" } }), []);
function onStripEvent(event: StripEvent) { function onStripEvent(event: StripEvent) {
console.log(event); console.log(event);
} }
const physicalBuses = 5; const isSnackBarHidden = useMemo(() => {
const virtualBuses = 3; if (typeof localStorage === "undefined") return false;
const strips = physicalBuses + virtualBuses; return localStorage.getItem("snackBarHidden") === "true";
}, []);
const [snackBarVisible, setSnackBarVisibility] = useState(false);
const breakPoints = useMemo(() => theme.breakpoints.values, []);
const [width, setWidth] = useState(
typeof document !== "undefined" ? window.innerWidth : 0
);
useEffect(() => {
setSnackBarVisibility(!isSnackBarHidden);
}, []);
useEffect(() => {
const values = {} as Record<string, [number, "bigger" | "smaller" | "equal"]>;
type valuesKey = keyof typeof breakPoints;
for (const key in breakPoints) {
if (Object.prototype.hasOwnProperty.call(breakPoints, key)) {
const breakPointWidth = breakPoints[key as keyof typeof breakPoints];
values[key as valuesKey] =
[breakPointWidth, breakPointWidth === width
? "equal"
: breakPointWidth > width
? "bigger"
: "smaller"];
}
}
console.clear()
console.log(
width,
`Closest: ${
Object.entries(breakPoints).reduce((acc, i) => {
const equation = Math.abs(i[1] - width) <= Math.abs(acc[1] - width) ? i : acc;
return equation;
})[0]
}`,
);
console.table(values);
}, [width]);
const strips = { virtual: 3, physical: 5 };
const buses = { virtual: 3, physical: 5 };
const amountOfStrips = strips.physical + strips.virtual;
const amountOfBuses = buses.physical + buses.virtual;
let clicks = 0; let clicks = 0;
let timeout: NodeJS.Timeout | undefined; let timeout: NodeJS.Timeout | undefined;
document.body.onclick = (e) => { if (typeof document !== "undefined") {
document.onclick = (e) => {
clicks++; clicks++;
if (timeout !== undefined) { if (timeout !== undefined) {
clearTimeout(timeout); clearTimeout(timeout);
timeout = undefined; timeout = undefined;
} }
timeout = setTimeout(() => (clicks = 0), 500); timeout = setTimeout(() => (clicks = 0), 250);
console.log(e);
if (clicks < 2) return; if (clicks < 2) return;
clearTimeout(timeout); clearTimeout(timeout);
clicks = 0; clicks = 0;
console.log(e.target);
e.stopPropagation(); // e.stopPropagation();
e.preventDefault();
if (document.fullscreenElement !== null) document.exitFullscreen();
else document.body.requestFullscreen({ navigationUI: "hide" });
// if (document.fullscreenElement !== null) document.exitFullscreen();
// else document.body.requestFullscreen({ navigationUI: "hide" });
}; };
window.onresize = () => {
setWidth(window.innerWidth);
};
}
const snackBarActionButton = (
<>
<Button
onClick={() => {
localStorage.setItem("snackBarHidden", "true");
setSnackBarVisibility(false);
}}
color="secondary"
>
Gotcha
</Button>
</>
);
return ( return (
<ThemeProvider theme={createTheme({ palette: { mode: "dark" } })}> <ThemeProvider theme={theme}>
<Container maxWidth="xl" sx={{ height: "100%" }}> <Container maxWidth={"xl"} sx={{ height: "100%", paddingInline: "5px" }}>
<div> <div>
<CssBaseline /> <CssBaseline />
<Typography variant="h5">Inputs</Typography> <Typography variant="h5">Inputs</Typography>
<Stack direction={"row"}> <Grid
{[...Array(strips)].map((_v, i) => ( container
spacing={width ? 0 : 1}
display={"flex"}
alignItems={"center"}
justifyContent={"space-evenly"}
columns={{ xs: 8, sm: 12, md: 5, lg: 12 }}
>
{[...Array(amountOfStrips)].map((_v, stripId) => (
<React.Fragment <React.Fragment
key={`${i < physicalBuses ? "a" : "b"}${ key={`${stripId < amountOfStrips ? "a" : "b"}${
i < physicalBuses ? i : i - physicalBuses stripId < amountOfStrips
? stripId + 1
: stripId + 1 - amountOfStrips
}`} }`}
> >
{i === physicalBuses ? ( {stripId === amountOfStrips && width > breakPoints.lg && (
<Grid>
<Divider <Divider
sx={{ marginBlock: "15px", borderWidth: "1px" }}
variant="fullWidth"
orientation="vertical" orientation="vertical"
variant="middle" >
sx={{ marginInlineEnd: "15px" }} Virtual Inputs
/> </Divider>
) : ( </Grid>
""
)} )}
<Grid
lg={stripId + 1 === strips.physical ? 2 : 0}
xs={1}
sm={1}
md={0}
sx={{ minWidth: "fit-content" }}
>
<Typography variant="overline">{`Strip #${
stripId + 1
}`}</Typography>
<Strip <Strip
onChange={(ev) => onStripEvent(ev)} onChange={(ev) => onStripEvent(ev)}
physicalBuses={physicalBuses} physicalBuses={buses.physical}
virtualBuses={virtualBuses} virtualBuses={buses.virtual}
/> />
</Grid>
</React.Fragment> </React.Fragment>
))} ))}
</Stack> </Grid>
<Typography variant="h5">Outputs</Typography>
<Stack direction={"column"}>
{[...Array(strips)].map((_v, i) => (
<React.Fragment
key={`${i < physicalBuses ? "a" : "b"}${
i < physicalBuses ? i : i - physicalBuses
}`}
>
{i === physicalBuses ? (
<Divider
orientation="vertical"
variant="middle"
sx={{ margin: "5px" }}
/>
) : (
""
)}
<Bus
onChange={(ev) => onStripEvent(ev)}
physicalBuses={physicalBuses}
virtualBuses={virtualBuses}
/>
</React.Fragment>
))}
</Stack>
<Button></Button> <Typography variant="h5">Outputs</Typography>
<Grid container spacing={1} direction={"column"}>
{[...Array(amountOfBuses)].map((_v, busId) => (
<Grid
key={`${busId < buses.physical ? "a" : "b"}${
busId < buses.physical
? busId + 1
: busId + 1 - buses.physical
}`}
xs={12}
>
<Typography variant="overline">{`Bus ${
busId < buses.physical ? "a" : "b"
}${
busId < buses.physical
? busId + 1
: busId + 1 - buses.physical
}`}</Typography>
<Bus onChange={console.log} />
</Grid>
))}
</Grid>
</div> </div>
</Container> </Container>
<Snackbar
anchorOrigin={{ horizontal: "center", vertical: "top" }}
TransitionComponent={Slide}
transitionDuration={250}
open={snackBarVisible}
message="Triple tap on empty space for toggle fullscreen"
action={snackBarActionButton}
/>
</ThemeProvider> </ThemeProvider>
); );
} }

29
src/utils/EventCounter.ts Normal file
View File

@ -0,0 +1,29 @@
interface EventListener {
name: keyof EventCounter["events"];
callback: Function | Promise<any>;
count: number;
}
type EventName = string;
type EventInfo = number[];
export class EventCounter {
events: Record<EventName, EventInfo> = {};
listeners: Record<EventName, EventListener[]> = {};
private checkListeners(eventName: EventListener["name"]) {
this.events[eventName];
}
private countEmits(eventName: EventListener["name"], periodOfTime: number) {}
emit(eventName: EventListener["name"]) {
this.events[eventName].length >= 100 && this.events[eventName].shift();
}
on(args: EventListener) {
const { name: eventName, callback, count } = args;
if (eventName in this.events === undefined) this.events[eventName] = [];
if (!Object.prototype.hasOwnProperty.call(this.listeners, eventName))
this.listeners[eventName] = [];
this.listeners[eventName].push({ callback, count, name: eventName });
}
}