feat: fully responsive gain tab
This commit is contained in:
22
src/components/AutoScale/AutoScale.css
Normal file
22
src/components/AutoScale/AutoScale.css
Normal file
@ -0,0 +1,22 @@
|
||||
.autoscale-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.autoscale-box {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.autoscale-content {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
|
||||
transform-origin: top left;
|
||||
}
|
||||
78
src/components/AutoScale/AutoScale.tsx
Normal file
78
src/components/AutoScale/AutoScale.tsx
Normal file
@ -0,0 +1,78 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
type AutoScaleProps = {
|
||||
designWidth: number;
|
||||
designHeight: number;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
};
|
||||
|
||||
export default function AutoScale({
|
||||
designWidth,
|
||||
designHeight,
|
||||
children,
|
||||
className,
|
||||
style,
|
||||
}: AutoScaleProps) {
|
||||
const outerRef = useRef<HTMLDivElement>(null);
|
||||
const [scale, setScale] = useState(1);
|
||||
|
||||
useEffect(() => {
|
||||
const el = outerRef.current;
|
||||
if (!el) return;
|
||||
|
||||
const ro = new ResizeObserver(([entry]) => {
|
||||
const { width, height } = entry.contentRect;
|
||||
|
||||
if (!width || !height) return;
|
||||
|
||||
const nextScale = Math.min(width / designWidth, height / designHeight);
|
||||
|
||||
setScale(nextScale > 0 ? nextScale : 1);
|
||||
});
|
||||
|
||||
ro.observe(el);
|
||||
|
||||
return () => ro.disconnect();
|
||||
}, [designWidth, designHeight]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={outerRef}
|
||||
className={className}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
minWidth: 0,
|
||||
minHeight: 0,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
width: designWidth * scale,
|
||||
height: designHeight * scale,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: designWidth,
|
||||
height: designHeight,
|
||||
transform: `scale(${scale})`,
|
||||
transformOrigin: 'top left',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,12 +1,42 @@
|
||||
// Button.tsx
|
||||
import './Button.css';
|
||||
import type { ButtonHTMLAttributes, ReactNode } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import AutoScale from '../AutoScale/AutoScale';
|
||||
|
||||
type ButtonVariant = 'primary' | 'ghost' | 'toggle' | 'outline';
|
||||
|
||||
// Design-space size per variant (same idea as DESIGN_WIDTH/HEIGHT in the slider)
|
||||
const DEFAULT_DESIGN_SIZE: Record<ButtonVariant, { width: number; height: number }> = {
|
||||
primary: { width: 90, height: 36 },
|
||||
ghost: { width: 90, height: 36 },
|
||||
toggle: { width: 90, height: 40 },
|
||||
outline: { width: 90, height: 36 },
|
||||
};
|
||||
|
||||
const MIN_FONT_SIZE = 8;
|
||||
const MAX_FONT_SIZE = 16;
|
||||
const AVG_CHAR_WIDTH_RATIO = 0.6;
|
||||
const HORIZONTAL_PADDING = 12; // reserved px inside the box, keeps text off the edges
|
||||
|
||||
function getTextLength(children: ReactNode): number {
|
||||
if (typeof children === 'string' || typeof children === 'number') {
|
||||
return String(children).length;
|
||||
}
|
||||
if (Array.isArray(children)) {
|
||||
return children.reduce((acc: number, c) => acc + getTextLength(c), 0);
|
||||
}
|
||||
return 6; // icons / non-text children: reasonable fallback estimate
|
||||
}
|
||||
|
||||
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: ButtonVariant;
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
fontSize?: number; // explicit override, in design px — skips auto-calc
|
||||
designWidth?: number; // override default design size if needed
|
||||
designHeight?: number;
|
||||
scale?: boolean; // wrap in AutoScale (default true)
|
||||
}
|
||||
|
||||
export default function Button({
|
||||
@ -16,17 +46,47 @@ export default function Button({
|
||||
disabled,
|
||||
onClick,
|
||||
type = 'button',
|
||||
fontSize,
|
||||
designWidth,
|
||||
designHeight,
|
||||
scale = true,
|
||||
style,
|
||||
...props
|
||||
}: ButtonProps) {
|
||||
return (
|
||||
const { width: defaultW, height: defaultH } = DEFAULT_DESIGN_SIZE[variant];
|
||||
const W = designWidth ?? defaultW;
|
||||
const H = designHeight ?? defaultH;
|
||||
|
||||
const computedFontSize = useMemo(() => {
|
||||
if (fontSize !== undefined) return fontSize; // user overload wins, no calc
|
||||
|
||||
const textLength = getTextLength(children);
|
||||
if (textLength === 0) return MAX_FONT_SIZE;
|
||||
|
||||
const availableWidth = W - HORIZONTAL_PADDING;
|
||||
const sizeByWidth = availableWidth / (textLength * AVG_CHAR_WIDTH_RATIO);
|
||||
|
||||
return Math.min(MAX_FONT_SIZE, Math.max(MIN_FONT_SIZE, sizeByWidth));
|
||||
}, [fontSize, children, W]);
|
||||
|
||||
const button = (
|
||||
<button
|
||||
type={type}
|
||||
className={`btn btn-${variant} ${className}`}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
style={{ width: '100%', height: '100%', fontSize: `${computedFontSize}px`, ...style }}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
|
||||
if (!scale) return button;
|
||||
|
||||
return (
|
||||
<AutoScale designWidth={W} designHeight={H}>
|
||||
{button}
|
||||
</AutoScale>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
import './Sidebar.css';
|
||||
import { ChevronLeftIcon } from '../../assets/icons/ChevronLeftIcon';
|
||||
|
||||
|
||||
@ -1,15 +1,3 @@
|
||||
.gauge-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.gauge-container {
|
||||
width: 70px;
|
||||
height: 260px;
|
||||
|
||||
@ -2,6 +2,10 @@ import { useMemo, useRef, useState, useEffect } from 'react';
|
||||
|
||||
import './VerticalSlider.css';
|
||||
|
||||
// Design size 260*70
|
||||
|
||||
const DESIGN_WIDTH = 70;
|
||||
|
||||
type VerticalSliderProps = {
|
||||
min: number;
|
||||
max: number;
|
||||
@ -19,9 +23,6 @@ type VerticalSliderProps = {
|
||||
style?: React.CSSProperties;
|
||||
};
|
||||
|
||||
const DESIGN_WIDTH = 70;
|
||||
const DESIGN_HEIGHT = 260;
|
||||
|
||||
function getDecimals(n: number) {
|
||||
const s = n.toString();
|
||||
const i = s.indexOf('.');
|
||||
@ -53,29 +54,11 @@ export default function VerticalSlider({
|
||||
const rafRef = useRef<number | null>(null);
|
||||
const dragging = useRef(false);
|
||||
const thumbRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// --- NEW: auto-fit scale ---
|
||||
const outerRef = useRef<HTMLDivElement>(null);
|
||||
const [scale, setScale] = useState(1);
|
||||
const keyboardEditing = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
const el = outerRef.current;
|
||||
if (!el) return;
|
||||
if (keyboardEditing.current) return;
|
||||
|
||||
const ro = new ResizeObserver((entries) => {
|
||||
const { width, height } = entries[0].contentRect;
|
||||
if (width === 0 || height === 0) return;
|
||||
|
||||
const s = Math.min(width / DESIGN_WIDTH, height / DESIGN_HEIGHT);
|
||||
setScale(s > 0 ? s : 1);
|
||||
});
|
||||
|
||||
ro.observe(el);
|
||||
return () => ro.disconnect();
|
||||
}, []);
|
||||
// ---------------------------
|
||||
|
||||
useEffect(() => {
|
||||
setDisplayValue(value);
|
||||
dragValue.current = value;
|
||||
}, [value]);
|
||||
@ -129,21 +112,40 @@ export default function VerticalSlider({
|
||||
|
||||
function handleKeyDown(e: React.KeyboardEvent<HTMLDivElement>) {
|
||||
if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) return;
|
||||
|
||||
keyboardEditing.current = true;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
let next = value;
|
||||
const base = keyboardValue.current ?? value;
|
||||
|
||||
let next = base;
|
||||
|
||||
if (e.key === 'ArrowUp' || e.key === 'ArrowRight') next += currentStep;
|
||||
|
||||
if (e.key === 'ArrowDown' || e.key === 'ArrowLeft') next -= currentStep;
|
||||
|
||||
updateValue(next);
|
||||
keyboardValue.current = normalizeValue(next);
|
||||
next = normalizeValue(next);
|
||||
|
||||
keyboardValue.current = next;
|
||||
|
||||
updateValue(next); // updates displayValue only
|
||||
}
|
||||
|
||||
function handleKeyUp() {
|
||||
if (keyboardValue.current !== null) {
|
||||
onChange?.(keyboardValue.current);
|
||||
keyboardValue.current = null;
|
||||
if (keyboardValue.current == null) return;
|
||||
|
||||
keyboardEditing.current = false;
|
||||
|
||||
const next = keyboardValue.current;
|
||||
|
||||
if (controlledValue === undefined) {
|
||||
setInternalValue(next);
|
||||
}
|
||||
|
||||
onChange?.(next);
|
||||
|
||||
keyboardValue.current = null;
|
||||
}
|
||||
|
||||
function valueFromPointer(clientY: number) {
|
||||
@ -190,78 +192,56 @@ export default function VerticalSlider({
|
||||
const percent = (value - min) / (max - min);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={outerRef}
|
||||
className="gauge-wrapper"
|
||||
style={{ width: '100%', height: '100%', ...style }}
|
||||
>
|
||||
<div
|
||||
className="gauge-scale-box"
|
||||
style={{
|
||||
position: 'relative',
|
||||
width: DESIGN_WIDTH * scale,
|
||||
height: DESIGN_HEIGHT * scale,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={['gauge-container', className].filter(Boolean).join(' ')}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: DESIGN_WIDTH,
|
||||
height: DESIGN_HEIGHT,
|
||||
transform: `scale(${scale})`,
|
||||
transformOrigin: 'top left',
|
||||
}}
|
||||
>
|
||||
<div className="gauge-track">
|
||||
<div className="ticks left">
|
||||
{Array.from({ length: 15 }).map((_, i) => <span key={i} />)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="slider"
|
||||
ref={sliderRef}
|
||||
tabIndex={0}
|
||||
onKeyDown={handleKeyDown}
|
||||
onKeyUp={handleKeyUp}
|
||||
onPointerDown={startDrag}
|
||||
onPointerMove={handlePointerMove}
|
||||
onPointerUp={endDrag}
|
||||
onPointerCancel={endDrag}
|
||||
>
|
||||
<div className="track" />
|
||||
<div ref={thumbRef} className="thumb" style={{ bottom: `${percent * 100}%` }} />
|
||||
</div>
|
||||
|
||||
<div className="ticks right">
|
||||
{Array.from({ length: 15 }).map((_, i) => <span key={i} />)}
|
||||
</div>
|
||||
<div className={['gauge-container', className].filter(Boolean).join(' ')} style={style}>
|
||||
<div className="gauge-track">
|
||||
<div className="ticks left">
|
||||
{Array.from({ length: 15 }).map((_, i) => (
|
||||
<span key={i} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="value" style={{ fontSize: `${valueFontSize}px` }}>
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
inputMode="decimal"
|
||||
value={editingText ?? displayValue.toFixed(decimals)}
|
||||
style={{ width: '100%' }}
|
||||
onFocus={() => setEditingText(value.toFixed(decimals))}
|
||||
onChange={(e) => setEditingText(e.target.value)}
|
||||
onBlur={commitEditing}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') inputRef.current?.blur();
|
||||
if (e.key === 'Escape') {
|
||||
setEditingText(null);
|
||||
inputRef.current?.blur();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<span className="value-unit">{unit}</span>
|
||||
<div
|
||||
className="slider"
|
||||
ref={sliderRef}
|
||||
tabIndex={0}
|
||||
onKeyDown={handleKeyDown}
|
||||
onKeyUp={handleKeyUp}
|
||||
onPointerDown={startDrag}
|
||||
onPointerMove={handlePointerMove}
|
||||
onPointerUp={endDrag}
|
||||
onPointerCancel={endDrag}
|
||||
>
|
||||
<div className="track" />
|
||||
<div ref={thumbRef} className="thumb" style={{ bottom: `${percent * 100}%` }} />
|
||||
</div>
|
||||
|
||||
<div className="ticks right">
|
||||
{Array.from({ length: 15 }).map((_, i) => (
|
||||
<span key={i} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="value" style={{ fontSize: `${valueFontSize}px` }}>
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
inputMode="decimal"
|
||||
value={editingText ?? displayValue.toFixed(decimals)}
|
||||
style={{ width: '100%' }}
|
||||
onFocus={() => setEditingText(value.toFixed(decimals))}
|
||||
onChange={(e) => setEditingText(e.target.value)}
|
||||
onBlur={commitEditing}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') inputRef.current?.blur();
|
||||
if (e.key === 'Escape') {
|
||||
setEditingText(null);
|
||||
inputRef.current?.blur();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<span className="value-unit">{unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
@ -1,82 +0,0 @@
|
||||
.vertical-stack {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
border: 1px solid var(--border-hairline);
|
||||
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
/* header */
|
||||
|
||||
.vertical-stack__title {
|
||||
flex: 0 0 auto;
|
||||
|
||||
padding: var(--space-1);
|
||||
|
||||
text-align: center;
|
||||
|
||||
font-family: var(--font-ui);
|
||||
|
||||
font-weight: 600;
|
||||
|
||||
color: var(--text-primary);
|
||||
|
||||
border-bottom: 1px solid var(--border-hairline);
|
||||
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* area containing components */
|
||||
|
||||
.vertical-stack__content {
|
||||
flex: 1;
|
||||
|
||||
min-height: 0;
|
||||
|
||||
display: flex;
|
||||
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* each component slot */
|
||||
|
||||
.vertical-stack__item {
|
||||
flex: 1;
|
||||
|
||||
min-height: 0;
|
||||
|
||||
display: flex;
|
||||
|
||||
justify-content: center;
|
||||
|
||||
align-items: center;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/*
|
||||
The child keeps its natural dimensions.
|
||||
Only horizontal scaling happens.
|
||||
*/
|
||||
|
||||
.vertical-stack__scale {
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
|
||||
justify-content: center;
|
||||
|
||||
align-items: center;
|
||||
|
||||
transform-origin: center;
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
// VerticalStack.tsx
|
||||
import React from 'react';
|
||||
import './VerticalStack.css';
|
||||
|
||||
type VerticalStackProps = {
|
||||
title: string;
|
||||
titleFontSize?: number;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export default function VerticalStack({ title, children }: VerticalStackProps) {
|
||||
return (
|
||||
<div className="vertical-stack">
|
||||
<div className="vertical-stack__title">
|
||||
{title}
|
||||
</div>
|
||||
|
||||
<div className="vertical-stack__content">
|
||||
{React.Children.map(children, (child, index) => (
|
||||
<div className="vertical-stack__item" key={index}>
|
||||
{child}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
:root {
|
||||
--gain-section-height: 40vh; /* your fixed % of screen height */
|
||||
--gain-section-height: 50vh; /* your fixed % of screen height */
|
||||
}
|
||||
|
||||
.gain-section {
|
||||
@ -33,26 +33,22 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
justify-content: safe center;
|
||||
justify-content: safe center; /* centers when it fits, falls back to start-aligned scroll when it overflows */
|
||||
gap: var(--space-4);
|
||||
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--border-hairline) transparent;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
.gain-panels > * {
|
||||
height: 100%;
|
||||
aspect-ratio: 90 / 340; /* your panel's design ratio (title+slider+buttons) — tune */
|
||||
|
||||
.gain-panel-frame {
|
||||
flex: 0 0 auto;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
height: calc(100% - 8px);
|
||||
aspect-ratio: var(--panel-w) / var(--panel-h); /* now driven by JS, not duplicated */
|
||||
margin: 4px 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.gain-panels::-webkit-scrollbar {
|
||||
@ -65,3 +61,29 @@
|
||||
.gain-panels::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.gain-panel {
|
||||
/* width/height now set inline from DESIGN_WIDTH/HEIGHT, drop the fixed px here */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
border: 1px solid var(--border-hairline);
|
||||
border-radius: var(--radius-md);
|
||||
box-sizing: border-box;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.gain-panel__title {
|
||||
width: 100%;
|
||||
|
||||
text-align: center;
|
||||
|
||||
font-family: var(--font-ui);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
|
||||
padding-bottom: 6px;
|
||||
|
||||
border-bottom: 1px solid var(--border-hairline);
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import VerticalStack from '../../VerticalStack/VerticalStack';
|
||||
import VerticalSlider from '../../Slider/VerticalSlider';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { InputChannel, Channel, OutputChannel } from '../../../types/dsp408State';
|
||||
@ -6,8 +5,12 @@ import { DSP } from '../../../types/types';
|
||||
import { NotificationType } from '../../../types/types';
|
||||
import { useState } from 'react';
|
||||
import Button from '../../Button/Button';
|
||||
import AutoScale from '../../AutoScale/AutoScale';
|
||||
import './GainSection.css';
|
||||
|
||||
const DESIGN_WIDTH = 90;
|
||||
const DESIGN_HEIGHT = 380;
|
||||
|
||||
function GainPanel({
|
||||
dsp,
|
||||
notify,
|
||||
@ -89,32 +92,43 @@ function GainPanel({
|
||||
};
|
||||
|
||||
return (
|
||||
<VerticalStack title={title}>
|
||||
<VerticalSlider
|
||||
min={-60}
|
||||
max={12}
|
||||
step={0.5}
|
||||
switchStepValue={-10}
|
||||
switchStep={0.1}
|
||||
unit=" dB"
|
||||
onChange={setGain}
|
||||
/>
|
||||
{/* <Button
|
||||
variant="toggle"
|
||||
className={muted ? "active-danger" : ""}
|
||||
onClick={toggleMute}
|
||||
>
|
||||
{muted ? "Muted" : "Mute"}
|
||||
</Button>
|
||||
<div
|
||||
className="gain-panel-frame"
|
||||
style={
|
||||
{
|
||||
'--panel-w': DESIGN_WIDTH,
|
||||
'--panel-h': DESIGN_HEIGHT,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<AutoScale designWidth={DESIGN_WIDTH} designHeight={DESIGN_HEIGHT}>
|
||||
<div className="gain-panel" style={{ width: DESIGN_WIDTH, height: DESIGN_HEIGHT }}>
|
||||
<div className="gain-panel__title">{title}</div>
|
||||
|
||||
<Button
|
||||
variant="toggle"
|
||||
className={inverted ? "active-warning" : ""}
|
||||
onClick={toggleInverse}
|
||||
>
|
||||
{inverted ? "Inverted" : "Invert"}
|
||||
</Button> */}
|
||||
</VerticalStack>
|
||||
<VerticalSlider
|
||||
min={-60}
|
||||
max={12}
|
||||
step={0.5}
|
||||
switchStepValue={-10}
|
||||
switchStep={0.1}
|
||||
unit=" dB"
|
||||
onChange={setGain}
|
||||
/>
|
||||
|
||||
<Button variant="toggle" className={muted ? 'active-danger' : ''} onClick={toggleMute}>
|
||||
{muted ? 'Muted' : 'Mute'}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="toggle"
|
||||
className={inverted ? 'active-warning' : ''}
|
||||
onClick={toggleInverse}
|
||||
>
|
||||
{inverted ? 'Inverted' : 'Invert'}
|
||||
</Button>
|
||||
</div>
|
||||
</AutoScale>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -131,14 +145,14 @@ function GainSection({
|
||||
{ channel: { Input: InputChannel.InC }, title: 'In C' },
|
||||
{ channel: { Input: InputChannel.InD }, title: 'In D' },
|
||||
|
||||
{ channel: { Output: OutputChannel.Out1 }, title: "Out 1" },
|
||||
{ channel: { Output: OutputChannel.Out2 }, title: "Out 2" },
|
||||
{ channel: { Output: OutputChannel.Out3 }, title: "Out 3" },
|
||||
{ channel: { Output: OutputChannel.Out4 }, title: "Out 4" },
|
||||
{ channel: { Output: OutputChannel.Out5 }, title: "Out 5" },
|
||||
{ channel: { Output: OutputChannel.Out6 }, title: "Out 6" },
|
||||
{ channel: { Output: OutputChannel.Out7 }, title: "Out 7" },
|
||||
{ channel: { Output: OutputChannel.Out8 }, title: "Out 8" },
|
||||
{ channel: { Output: OutputChannel.Out1 }, title: 'Out 1' },
|
||||
{ channel: { Output: OutputChannel.Out2 }, title: 'Out 2' },
|
||||
{ channel: { Output: OutputChannel.Out3 }, title: 'Out 3' },
|
||||
{ channel: { Output: OutputChannel.Out4 }, title: 'Out 4' },
|
||||
{ channel: { Output: OutputChannel.Out5 }, title: 'Out 5' },
|
||||
{ channel: { Output: OutputChannel.Out6 }, title: 'Out 6' },
|
||||
{ channel: { Output: OutputChannel.Out7 }, title: 'Out 7' },
|
||||
{ channel: { Output: OutputChannel.Out8 }, title: 'Out 8' },
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@ -47,11 +47,7 @@ function DSPPage({
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`dsp-layout app-root ${
|
||||
sidebarExpanded ? 'app-root--sidebar-expanded' : ''
|
||||
}`}
|
||||
>
|
||||
<div className={`dsp-layout app-root ${sidebarExpanded ? 'app-root--sidebar-expanded' : ''}`}>
|
||||
<Sidebar
|
||||
items={sidebarItems}
|
||||
activeId={activeSidebar}
|
||||
|
||||
Reference in New Issue
Block a user