feat: dont moove slider cursor if tcp command fails
This commit is contained in:
@@ -1,8 +1,6 @@
|
||||
import VerticalSlider from '../Slider/VerticalSlider';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { InputChannel, Channel, OutputChannel } from '../../../types/dsp408State';
|
||||
import { DSP } from '../../../types/types';
|
||||
import { NotificationType } from '../../../types/types';
|
||||
import Button from '../../Button/Button';
|
||||
import ChannelGroup from '../ChannelGroup/ChannelGroup';
|
||||
import { LinearGraph } from '../LinearGraph/LinearGraph';
|
||||
@@ -12,109 +10,62 @@ import './GainSection.css';
|
||||
const PANEL_DESIGN_WIDTH = 90;
|
||||
const PANEL_DESIGN_HEIGHT = 400;
|
||||
|
||||
function ChannelGroupScaled({ title, children }: { title: string; children: React.ReactNode }) {
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const [scale, setScale] = useState(1);
|
||||
// function ChannelGroupScaled({ title, children }: { title: string; children: React.ReactNode }) {
|
||||
// const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
// const [scale, setScale] = useState(1);
|
||||
|
||||
useEffect(() => {
|
||||
const el = wrapperRef.current;
|
||||
if (!el) return;
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
const { height } = entries[0].contentRect;
|
||||
if (height === 0) return;
|
||||
setScale(height / PANEL_DESIGN_HEIGHT); // height is the only free axis here
|
||||
});
|
||||
observer.observe(el);
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
// useEffect(() => {
|
||||
// const el = wrapperRef.current;
|
||||
// if (!el) return;
|
||||
// const observer = new ResizeObserver((entries) => {
|
||||
// const { height } = entries[0].contentRect;
|
||||
// if (height === 0) return;
|
||||
// setScale(height / PANEL_DESIGN_HEIGHT); // height is the only free axis here
|
||||
// });
|
||||
// observer.observe(el);
|
||||
// return () => observer.disconnect();
|
||||
// }, []);
|
||||
|
||||
return (
|
||||
<div ref={wrapperRef} style={{ height: '100%', '--panel-scale': scale } as React.CSSProperties}>
|
||||
<ChannelGroup title={title}>{children}</ChannelGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// return (
|
||||
// <div ref={wrapperRef} style={{ height: '100%', '--panel-scale': scale } as React.CSSProperties}>
|
||||
// <ChannelGroup title={title}>{children}</ChannelGroup>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
function GainPanel({
|
||||
dsp,
|
||||
notify,
|
||||
channel,
|
||||
title,
|
||||
setGain,
|
||||
setMute,
|
||||
setInverse,
|
||||
}: {
|
||||
dsp: DSP;
|
||||
notify: (message: string, type?: NotificationType, dspName?: string) => void;
|
||||
channel: Channel;
|
||||
title: string;
|
||||
setGain: (channel: Channel, gain: number) => Promise<boolean>;
|
||||
setMute: (channel: Channel, muted: boolean) => Promise<boolean>;
|
||||
setInverse: (channel: Channel, inverted: boolean) => Promise<boolean>;
|
||||
}) {
|
||||
const setGain = async (value: number) => {
|
||||
try {
|
||||
const success = await invoke<boolean>('set_channel_gain', {
|
||||
id: dsp.id,
|
||||
channel,
|
||||
db: value,
|
||||
});
|
||||
const channelState =
|
||||
'Input' in channel
|
||||
? dsp.state?.current_config.input_states[channel.Input]
|
||||
: dsp.state?.current_config.output_states[channel.Output];
|
||||
|
||||
if (success) {
|
||||
notify(`Input A gain set to ${value} dB`, 'success', dsp.name);
|
||||
} else {
|
||||
notify('Failed to set input gain', 'error', dsp.name);
|
||||
}
|
||||
} catch (err) {
|
||||
notify(`Gain update failed: ${String(err)}`, 'error', dsp.name);
|
||||
}
|
||||
};
|
||||
const muted = channelState?.mute ?? false;
|
||||
const inverted = channelState?.phase_inverted ?? false;
|
||||
const gain = channelState?.gain ?? 0;
|
||||
|
||||
const setMute = async (muted: boolean) => {
|
||||
try {
|
||||
const success = await invoke<boolean>('set_mute', {
|
||||
id: dsp.id,
|
||||
channel,
|
||||
muted,
|
||||
});
|
||||
|
||||
if (success) {
|
||||
notify(`Input A ${muted ? 'muted' : 'unmuted'}`, 'success', dsp.name);
|
||||
} else {
|
||||
notify('Failed to change mute state', 'error', dsp.name);
|
||||
}
|
||||
} catch (err) {
|
||||
notify(`Mute update failed: ${String(err)}`, 'error', dsp.name);
|
||||
}
|
||||
};
|
||||
|
||||
const setInverse = async (inverted: boolean) => {
|
||||
try {
|
||||
const success = await invoke<boolean>('set_channel_inverse_gain', {
|
||||
id: dsp.id,
|
||||
channel,
|
||||
inverted,
|
||||
});
|
||||
|
||||
if (success) {
|
||||
notify(`Input A phase ${inverted ? 'inverted' : 'normal'}`, 'success', dsp.name);
|
||||
} else {
|
||||
notify('Failed to change phase', 'error', dsp.name);
|
||||
}
|
||||
} catch (err) {
|
||||
notify(`Phase update failed: ${String(err)}`, 'error', dsp.name);
|
||||
}
|
||||
};
|
||||
|
||||
const [muted, setMuted] = useState(false);
|
||||
const [inverted, setInverted] = useState(false);
|
||||
const viewportRef = useRef<HTMLDivElement>(null);
|
||||
const [scale, setScale] = useState(1);
|
||||
|
||||
const toggleMute = async () => {
|
||||
const next = !muted;
|
||||
setMuted(next);
|
||||
await setMute(next);
|
||||
await setMute(channel, !muted);
|
||||
};
|
||||
|
||||
const toggleInverse = async () => {
|
||||
const next = !inverted;
|
||||
setInverted(next);
|
||||
await setInverse(next);
|
||||
await setInverse(channel, !inverted);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -148,7 +99,8 @@ function GainPanel({
|
||||
switchStepValue={-10}
|
||||
switchStep={0.1}
|
||||
unit=" dB"
|
||||
onChange={setGain}
|
||||
value={gain}
|
||||
onChange={(value) => setGain(channel, value)}
|
||||
style={{ width: 70, height: 260, flex: '0 0 auto' }} // pin to design size, disable its own auto-shrink
|
||||
/>
|
||||
|
||||
@@ -169,10 +121,14 @@ function GainPanel({
|
||||
|
||||
function GainSection({
|
||||
dsp,
|
||||
notify,
|
||||
setGain,
|
||||
setMute,
|
||||
setInverse,
|
||||
}: {
|
||||
dsp: DSP;
|
||||
notify: (message: string, type?: NotificationType, dspName?: string) => void;
|
||||
setGain: (channel: Channel, gain: number) => Promise<boolean>;
|
||||
setMute: (channel: Channel, muted: boolean) => Promise<boolean>;
|
||||
setInverse: (channel: Channel, inverted: boolean) => Promise<boolean>;
|
||||
}) {
|
||||
const inputs: { channel: Channel; title: string }[] = [
|
||||
{ channel: { Input: InputChannel.InA }, title: 'In A' },
|
||||
@@ -223,13 +179,29 @@ function GainSection({
|
||||
<div className="gain-panels">
|
||||
<ChannelGroup title="Input">
|
||||
{inputs.map(({ channel, title }) => (
|
||||
<GainPanel key={title} dsp={dsp} notify={notify} channel={channel} title={title} />
|
||||
<GainPanel
|
||||
key={title}
|
||||
dsp={dsp}
|
||||
channel={channel}
|
||||
title={title}
|
||||
setGain={setGain}
|
||||
setMute={setMute}
|
||||
setInverse={setInverse}
|
||||
/>
|
||||
))}
|
||||
</ChannelGroup>
|
||||
|
||||
<ChannelGroup title="Output">
|
||||
{outputs.map(({ channel, title }) => (
|
||||
<GainPanel key={title} dsp={dsp} notify={notify} channel={channel} title={title} />
|
||||
<GainPanel
|
||||
key={title}
|
||||
dsp={dsp}
|
||||
channel={channel}
|
||||
title={title}
|
||||
setGain={setGain}
|
||||
setMute={setMute}
|
||||
setInverse={setInverse}
|
||||
/>
|
||||
))}
|
||||
</ChannelGroup>
|
||||
</div>
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
import VerticalSlider from '../Slider/VerticalSlider';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { InputChannel, Channel, OutputChannel } from '../../../types/dsp408State';
|
||||
import { DSP } from '../../../types/types';
|
||||
import { NotificationType } from '../../../types/types';
|
||||
// import VerticalSlider from '../Slider/VerticalSlider';
|
||||
// import { invoke } from '@tauri-apps/api/core';
|
||||
// import { InputChannel, Channel, OutputChannel } from '../../../types/dsp408State';
|
||||
// import { DSP } from '../../../types/types';
|
||||
// import { NotificationType } from '../../../types/types';
|
||||
|
||||
import ChannelGroup from '../ChannelGroup/ChannelGroup';
|
||||
import './GateSection.css';
|
||||
// import ChannelGroup from '../ChannelGroup/ChannelGroup';
|
||||
// import './GateSection.css';
|
||||
|
||||
function GateSection({
|
||||
dsp,
|
||||
notify,
|
||||
}: {
|
||||
dsp: DSP;
|
||||
notify: (message: string, type?: NotificationType, dspName?: string) => void;
|
||||
}) {
|
||||
console.log(dsp, notify);
|
||||
return (
|
||||
<div className="rerere">
|
||||
<VerticalSlider
|
||||
min={-60}
|
||||
max={12}
|
||||
step={0.5}
|
||||
switchStepValue={-10}
|
||||
switchStep={0.1}
|
||||
unit=" dB"
|
||||
onChange={(_f: number) => {
|
||||
return;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// function GateSection({
|
||||
// dsp,
|
||||
// notify,
|
||||
// }: {
|
||||
// dsp: DSP;
|
||||
// notify: (message: string, type?: NotificationType, dspName?: string) => void;
|
||||
// }) {
|
||||
// console.log(dsp, notify);
|
||||
// return (
|
||||
// <div className="rerere">
|
||||
// <VerticalSlider
|
||||
// min={-60}
|
||||
// max={12}
|
||||
// step={0.5}
|
||||
// switchStepValue={-10}
|
||||
// switchStep={0.1}
|
||||
// unit=" dB"
|
||||
// onChange={(_f: number) => {
|
||||
// return;
|
||||
// }}
|
||||
// />
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
export default GateSection;
|
||||
// export default GateSection;
|
||||
|
||||
@@ -13,8 +13,8 @@ type VerticalSliderProps = {
|
||||
switchStepValue?: number;
|
||||
switchStep?: number;
|
||||
|
||||
value?: number;
|
||||
onChange?: (value: number) => void;
|
||||
value: number;
|
||||
onChange?: (value: number) => Promise<boolean>;
|
||||
|
||||
unit?: string;
|
||||
|
||||
@@ -34,23 +34,18 @@ export default function VerticalSlider({
|
||||
step,
|
||||
switchStepValue,
|
||||
switchStep,
|
||||
value: controlledValue,
|
||||
value,
|
||||
onChange,
|
||||
unit = '',
|
||||
className,
|
||||
style,
|
||||
}: VerticalSliderProps) {
|
||||
const [internalValue, setInternalValue] = useState(min);
|
||||
const value = controlledValue ?? internalValue;
|
||||
|
||||
const [editingText, setEditingText] = useState<string | null>(null);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const sliderRef = useRef<HTMLDivElement>(null);
|
||||
const lastValue = useRef(value);
|
||||
const keyboardValue = useRef<number | null>(null);
|
||||
const [displayValue, setDisplayValue] = useState(value);
|
||||
const dragValue = useRef(value);
|
||||
const rafRef = useRef<number | null>(null);
|
||||
const dragging = useRef(false);
|
||||
const thumbRef = useRef<HTMLDivElement>(null);
|
||||
const keyboardEditing = useRef(false);
|
||||
@@ -74,16 +69,20 @@ export default function VerticalSlider({
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (dragging.current) return;
|
||||
if (keyboardEditing.current) return;
|
||||
|
||||
setDisplayValue(value);
|
||||
dragValue.current = value;
|
||||
}, [value]);
|
||||
|
||||
const currentStep =
|
||||
switchStepValue !== undefined && switchStep !== undefined && value >= switchStepValue
|
||||
? switchStep
|
||||
: step;
|
||||
function getStep(value: number) {
|
||||
if (switchStepValue !== undefined && switchStep !== undefined && value >= switchStepValue) {
|
||||
return switchStep;
|
||||
}
|
||||
|
||||
return step;
|
||||
}
|
||||
|
||||
const decimals = useMemo(
|
||||
() => Math.max(getDecimals(step), switchStep !== undefined ? getDecimals(switchStep) : 0),
|
||||
@@ -92,30 +91,25 @@ export default function VerticalSlider({
|
||||
|
||||
function normalizeValue(input: number) {
|
||||
const clamped = Math.min(max, Math.max(min, input));
|
||||
const snapped = Math.round(clamped / currentStep) * currentStep;
|
||||
return Number(snapped.toFixed(6));
|
||||
|
||||
const currentStep = getStep(clamped);
|
||||
|
||||
const snapped = min + Math.round((clamped - min) / currentStep) * currentStep;
|
||||
|
||||
return Number(Math.min(max, Math.max(min, snapped)).toFixed(6));
|
||||
}
|
||||
|
||||
function updateValue(v: number) {
|
||||
const next = normalizeValue(v);
|
||||
function updateValue(input: number) {
|
||||
const next = normalizeValue(input);
|
||||
|
||||
dragValue.current = next;
|
||||
lastValue.current = next;
|
||||
|
||||
if (thumbRef.current) {
|
||||
const percent = (next - min) / (max - min);
|
||||
thumbRef.current.style.bottom = `${percent * 100}%`;
|
||||
}
|
||||
|
||||
if (rafRef.current === null) {
|
||||
rafRef.current = requestAnimationFrame(() => {
|
||||
setDisplayValue(dragValue.current);
|
||||
rafRef.current = null;
|
||||
});
|
||||
}
|
||||
setDisplayValue(next);
|
||||
}
|
||||
|
||||
function handleKeyDown(e: React.KeyboardEvent<HTMLDivElement>) {
|
||||
if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) return;
|
||||
if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
keyboardEditing.current = true;
|
||||
|
||||
@@ -123,33 +117,39 @@ export default function VerticalSlider({
|
||||
|
||||
const base = keyboardValue.current ?? value;
|
||||
|
||||
const currentStep = getStep(base);
|
||||
|
||||
let next = base;
|
||||
|
||||
if (e.key === 'ArrowUp' || e.key === 'ArrowRight') next += currentStep;
|
||||
if (e.key === 'ArrowUp' || e.key === 'ArrowRight') {
|
||||
next += currentStep;
|
||||
}
|
||||
|
||||
if (e.key === 'ArrowDown' || e.key === 'ArrowLeft') next -= currentStep;
|
||||
if (e.key === 'ArrowDown' || e.key === 'ArrowLeft') {
|
||||
next -= currentStep;
|
||||
}
|
||||
|
||||
next = normalizeValue(next);
|
||||
|
||||
keyboardValue.current = next;
|
||||
|
||||
updateValue(next); // updates displayValue only
|
||||
updateValue(next);
|
||||
}
|
||||
|
||||
function handleKeyUp() {
|
||||
async function handleKeyUp() {
|
||||
if (keyboardValue.current == null) return;
|
||||
|
||||
keyboardEditing.current = false;
|
||||
|
||||
const next = keyboardValue.current;
|
||||
|
||||
if (controlledValue === undefined) {
|
||||
setInternalValue(next);
|
||||
}
|
||||
|
||||
onChange?.(next);
|
||||
const success = await onChange?.(next);
|
||||
|
||||
keyboardEditing.current = false;
|
||||
keyboardValue.current = null;
|
||||
|
||||
if (success === false) {
|
||||
setDisplayValue(value);
|
||||
dragValue.current = value;
|
||||
}
|
||||
}
|
||||
|
||||
function valueFromPointer(clientY: number) {
|
||||
@@ -172,28 +172,47 @@ export default function VerticalSlider({
|
||||
valueFromPointer(e.clientY);
|
||||
}
|
||||
|
||||
function endDrag(e: React.PointerEvent<HTMLDivElement>) {
|
||||
dragging.current = false;
|
||||
async function endDrag(e: React.PointerEvent<HTMLDivElement>) {
|
||||
if (!dragging.current) return;
|
||||
|
||||
const finalValue = dragValue.current;
|
||||
|
||||
if (controlledValue === undefined) setInternalValue(finalValue);
|
||||
setDisplayValue(finalValue);
|
||||
onChange?.(finalValue);
|
||||
const success = await onChange?.(finalValue);
|
||||
|
||||
dragging.current = false;
|
||||
|
||||
if (success === false) {
|
||||
setDisplayValue(value);
|
||||
dragValue.current = value;
|
||||
}
|
||||
|
||||
if (e.currentTarget.hasPointerCapture(e.pointerId)) {
|
||||
e.currentTarget.releasePointerCapture(e.pointerId);
|
||||
}
|
||||
}
|
||||
|
||||
function commitEditing() {
|
||||
async function commitEditing() {
|
||||
if (editingText !== null && editingText !== '' && editingText !== '-') {
|
||||
const parsed = Number(editingText);
|
||||
if (!Number.isNaN(parsed)) updateValue(parsed);
|
||||
|
||||
if (!Number.isNaN(parsed)) {
|
||||
const next = normalizeValue(parsed);
|
||||
|
||||
setDisplayValue(next);
|
||||
|
||||
const success = await onChange?.(next);
|
||||
|
||||
if (success === false) {
|
||||
setDisplayValue(value);
|
||||
dragValue.current = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setEditingText(null);
|
||||
}
|
||||
|
||||
const percent = (value - min) / (max - min);
|
||||
const percent = (displayValue - min) / (max - min);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user