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 { StripBusOutput, StripBusOutputEvent } from "./StripBusOutput";
export interface StripProps {
physicalBuses: number;
virtualBuses: number;
default?: Partial<StripState>;
export interface BusProps {
values?: Partial<BusState>;
onChange: (event: BusEvent) => any;
}
export interface StripState {
export interface BusState {
gain: number;
buses: boolean[];
muted: boolean;
selected: boolean;
}
export type BusEvent = StripBusOutputChanged | StripMuted | StripGainChanged;
export type BusEvent = BusMuted | BusGainChanged | BusSelectToggle;
export interface StripBusOutputChanged extends StripBusOutputEvent {
type: "StripBusOutputChanged";
}
export interface StripGainChanged {
export interface BusGainChanged {
type: "StripGainChanged";
gain: number;
}
export interface StripMuted {
export interface BusMuted {
type: "StripMuted";
muted: boolean;
}
export interface BusSelectToggle {
type: "BusSelectToggle";
selected: boolean;
}
export class Bus extends React.Component<StripProps, StripState> {
constructor(props: StripProps) {
export class Bus extends React.Component<BusProps, BusState> {
constructor(props: BusProps) {
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,
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) {
console.log(event);
@ -70,21 +73,6 @@ export class Bus extends React.Component<StripProps, StripState> {
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) {
console.log(event);
}
@ -96,58 +84,69 @@ 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() {
return (
<>
<Grid container sx={{ width: "100%"}}>
<Stack alignItems={"center"} direction={"row"}>
<Input
inputProps={{
"aria-labelledby": "input-slider",
itemType: "number",
}}
value={this.state.gain}
onWheel={(ev) => this.onStripWheel(ev)}
size="small"
sx={{ width: "50px" }}
/>
<Slider
sx={{
margin: "5px",
}}
// orientation="vertical"
defaultValue={0.0}
step={0.1}
min={-60}
max={12}
value={this.state.gain}
onChange={(ev) =>
this.onChange(
ev as unknown as React.ChangeEvent<HTMLInputElement>
)
}
onKeyDown={(ev) => this.onKeyDown(ev)}
/>
<Stack alignItems={"center"} direction={"row"} spacing={0}>
<Input
inputProps={{
"aria-labelledby": "input-slider",
itemType: "number",
}}
value={this.state.gain}
onWheel={(ev) => this.onStripWheel(ev)}
size="small"
sx={{ width: "50px" }}
/>
<Slider
sx={{
margin: "5px",
marginInline: "15px",
}}
// orientation="vertical"
defaultValue={0.0}
step={0.1}
min={-60}
max={12}
value={this.state.gain}
onChange={(ev) =>
this.onChange(
ev as unknown as React.ChangeEvent<HTMLInputElement>
)
}
onKeyDown={(ev) => this.onKeyDown(ev)}
/>
<ButtonGroup>
<Button
onClick={() => this.onMuteToggle()}
variant={this.state.muted ? "contained" : "outlined"}
color={"error"}
>
Mute
</Button>
<Button
onClick={() => this.onMuteToggle()}
sx={{ }}
variant={this.state.muted ? "contained" : "outlined"}
color={"warning"}
>
S
</Button>
</ButtonGroup>
</Stack>
{/* <Stack>
<ButtonGroup>
<Button
onClick={() => this.onMuteToggle()}
variant={this.state.muted ? "contained" : "outlined"}
color={"error"}
>
Mute
</Button>
<Button
onClick={() => this.onSelectToggle()}
sx={{}}
variant={this.state.selected ? "contained" : "outlined"}
color={"warning"}
>
S
</Button>
</ButtonGroup>
</Stack>
{/* <Stack>
<ButtonGroup orientation="vertical">
{[
...Array(this.props.physicalBuses + this.props.virtualBuses),
@ -170,7 +169,6 @@ export class Bus extends React.Component<StripProps, StripState> {
))}
</ButtonGroup>
</Stack> */}
</Grid>
</>
);
}

View File

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

View File

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

View File

@ -1,5 +1,5 @@
"use client";
import React from "react";
import React, { useEffect, useMemo, useState } from "react";
import { Strip, StripEvent } from "./component/Strip";
import {
Button,
@ -7,11 +7,15 @@ import {
createTheme,
CssBaseline,
Divider,
Slide,
Snackbar,
Stack,
ThemeProvider,
Typography,
useMediaQuery,
} from "@mui/material";
import { Bus } from "./component/Bus";
import Grid from "@mui/material/Unstable_Grid2";
function random(min: number, max: number, floor: boolean = true) {
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() {
const theme = useMemo(() => createTheme({ palette: { mode: "dark" } }), []);
function onStripEvent(event: StripEvent) {
console.log(event);
}
const physicalBuses = 5;
const virtualBuses = 3;
const strips = physicalBuses + virtualBuses;
const isSnackBarHidden = useMemo(() => {
if (typeof localStorage === "undefined") return false;
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 timeout: NodeJS.Timeout | undefined;
document.body.onclick = (e) => {
clicks++;
if (timeout !== undefined) {
if (typeof document !== "undefined") {
document.onclick = (e) => {
clicks++;
if (timeout !== undefined) {
clearTimeout(timeout);
timeout = undefined;
}
timeout = setTimeout(() => (clicks = 0), 250);
console.log(e);
if (clicks < 2) return;
clearTimeout(timeout);
timeout = undefined;
}
timeout = setTimeout(() => (clicks = 0), 500);
if (clicks < 2) return;
clearTimeout(timeout);
clicks = 0;
console.log(e.target);
clicks = 0;
e.stopPropagation();
e.preventDefault();
if (document.fullscreenElement !== null) document.exitFullscreen();
else document.body.requestFullscreen({ navigationUI: "hide" });
// e.stopPropagation();
};
// 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 (
<ThemeProvider theme={createTheme({ palette: { mode: "dark" } })}>
<Container maxWidth="xl" sx={{ height: "100%" }}>
<ThemeProvider theme={theme}>
<Container maxWidth={"xl"} sx={{ height: "100%", paddingInline: "5px" }}>
<div>
<CssBaseline />
<Typography variant="h5">Inputs</Typography>
<Stack direction={"row"}>
{[...Array(strips)].map((_v, i) => (
<Grid
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
key={`${i < physicalBuses ? "a" : "b"}${
i < physicalBuses ? i : i - physicalBuses
key={`${stripId < amountOfStrips ? "a" : "b"}${
stripId < amountOfStrips
? stripId + 1
: stripId + 1 - amountOfStrips
}`}
>
{i === physicalBuses ? (
<Divider
orientation="vertical"
variant="middle"
sx={{ marginInlineEnd: "15px" }}
/>
) : (
""
{stripId === amountOfStrips && width > breakPoints.lg && (
<Grid>
<Divider
sx={{ marginBlock: "15px", borderWidth: "1px" }}
variant="fullWidth"
orientation="vertical"
>
Virtual Inputs
</Divider>
</Grid>
)}
<Strip
onChange={(ev) => onStripEvent(ev)}
physicalBuses={physicalBuses}
virtualBuses={virtualBuses}
/>
<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
onChange={(ev) => onStripEvent(ev)}
physicalBuses={buses.physical}
virtualBuses={buses.virtual}
/>
</Grid>
</React.Fragment>
))}
</Stack>
<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>
</Grid>
<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>
</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>
);
}

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 });
}
}