TeaWeb/shared/js/ui/modal/ModalChangeVolumeNew.tsx

289 lines
No EOL
9.4 KiB
TypeScript

import {Modal, spawnReactModal} from "tc-shared/ui/react-elements/Modal";
import * as React from "react";
import {Slider} from "tc-shared/ui/react-elements/Slider";
import {Button} from "tc-shared/ui/react-elements/Button";
import {Translatable} from "tc-shared/ui/react-elements/i18n";
import {EventHandler, ReactEventHandler, Registry} from "tc-shared/events";
import {ClientEntry, MusicClientEntry} from "tc-shared/ui/client";
const cssStyle = require("./ModalChangeVolume.scss");
export interface VolumeChangeEvents {
"change-volume": {
newValue: number,
origin: "user-input" | "reset" | "unknown"
}
"query-volume": {},
"query-volume-response": {
volume: number
}
"apply-volume": {
newValue: number,
origin: "user-input" | "reset" | "unknown"
},
"apply-volume-result": {
newValue: number,
success: boolean
},
"close-modal": {}
}
interface VolumeChangeModalState {
state: "querying" | "applying" | "user-input";
volumeModifier: number;
sliderValue: number;
}
@ReactEventHandler(e => e.props.events)
class VolumeChangeModal extends React.Component<{ clientName: string, maxVolume?: number, remote: boolean, events: Registry<VolumeChangeEvents> }, VolumeChangeModalState> {
private readonly refSlider = React.createRef<Slider>();
private originalValue: number;
constructor(props) {
super(props);
this.state = {
volumeModifier: 1,
sliderValue: 100,
state: "querying"
};
}
componentDidMount(): void {
this.props.events.fire("query-volume");
}
render() {
const db = Math.log2(this.state.volumeModifier) * 10;
let valueString = db.toFixed(1) + "db";
if(!valueString.startsWith("-") && valueString !== "0") valueString = "+" + valueString;
return (
<div className={cssStyle.container}>
<div className={cssStyle.info}>
<a>Change value for client {this.props.clientName}</a>
</div>
<div className={cssStyle.sliderContainer}>
<Slider
minValue={0}
maxValue={200}
stepSize={1}
className={cssStyle.slider}
tooltip={() => valueString}
onInput={value => this.onValueChanged(value)}
value={this.state.sliderValue}
disabled={this.state.state !== "user-input"}
ref={this.refSlider}
/>
<a className={cssStyle.value}>{valueString}</a>
</div>
<div className={cssStyle.buttons}>
<Button type={"small"} color={"blue"} className={cssStyle.reset} onClick={() => this.onResetClicked()} disabled={this.state.state !== "user-input" || this.state.sliderValue === 100}>
<Translatable>Reset</Translatable>
</Button>
<Button type={"small"} color={"green"} className={cssStyle.apply} onClick={() => this.onApplyClick()} hidden={!this.props.remote} disabled={this.state.state !== "user-input" || this.originalValue === this.state.volumeModifier}>
<Translatable>Apply</Translatable>
</Button>
<Button type={"small"} color={"red"} className={cssStyle.cancel} onClick={() => this.onCancelClick()}>
<Translatable>Cancel</Translatable>
</Button>
<Button type={"small"} color={"green"} className={cssStyle.ok} onClick={() => this.onOkClick()}>
<Translatable>Ok</Translatable>
</Button>
</div>
</div>
);
}
private static slider2value(target: number) {
if(target > 100) {
/* between +0db and +20db */
const value = (target - 100) * 20 / 100;
return Math.pow(2, value / 10);
} else if(target < 100) {
/* between -30db and +0db */
const value = 30 - target * 30 / 100;
return Math.pow(2, -value / 10);
} else {
return 1;
}
}
private static value2slider(value: number) {
const db = Math.log2(value) * 10;
if(db > 0) {
return 100 + db * 100 / 20;
} else if(db < 0) {
return 100 + db * 100 / 30; /* db is negative */
} else {
return 100;
}
}
private onValueChanged(target: number) {
this.props.events.fire("change-volume", {
newValue: VolumeChangeModal.slider2value(target),
origin: "user-input"
});
}
private onResetClicked() {
this.props.events.fire("change-volume", { newValue: 1, origin: "reset" });
}
private onApplyClick() {
this.props.events.fire("apply-volume", {
newValue: this.state.volumeModifier,
origin: "user-input"
});
}
private onCancelClick() {
this.props.events.fire("change-volume", { origin: "user-input", newValue: this.originalValue });
this.props.events.fire("close-modal");
}
private onOkClick() {
if(this.props.remote && this.state.volumeModifier !== this.originalValue)
this.props.events.fire("apply-volume", { origin: "user-input", newValue: this.originalValue });
this.props.events.fire("close-modal");
}
@EventHandler<VolumeChangeEvents>("change-volume")
private handleVolumeChanged(event: VolumeChangeEvents["change-volume"]) {
const sliderValue = VolumeChangeModal.value2slider(event.newValue);
this.setState({
volumeModifier: event.newValue,
sliderValue: sliderValue
});
if(event.origin !== "user-input")
this.refSlider.current?.setState({ value: sliderValue });
}
@EventHandler<VolumeChangeEvents>("query-volume")
private handleVolumeQuery() {
this.setState({
state: "querying"
});
this.refSlider.current?.setState({
disabled: true
});
}
@EventHandler<VolumeChangeEvents>("apply-volume")
private handleApplyVolume() {
this.setState({
state: "applying"
});
this.refSlider.current?.setState({
disabled: true
});
}
@EventHandler<VolumeChangeEvents>("query-volume-response")
private handleVolumeQueryResponse(event: VolumeChangeEvents["query-volume-response"]) {
const sliderValue = VolumeChangeModal.value2slider(event.volume);
this.setState({
volumeModifier: event.volume,
sliderValue: sliderValue,
state: "user-input"
});
this.refSlider.current?.setState({
value: sliderValue,
disabled: false
});
this.originalValue = event.volume;
}
@EventHandler<VolumeChangeEvents>("apply-volume-result")
private handleApplyVolumeResult(event: VolumeChangeEvents["apply-volume-result"]) {
const sliderValue = VolumeChangeModal.value2slider(event.newValue);
this.setState({
volumeModifier: event.newValue,
sliderValue: sliderValue,
state: "user-input"
});
this.refSlider.current?.setState({
value: sliderValue,
disabled: false
});
this.originalValue = event.newValue;
}
}
export function spawnClientVolumeChange(client: ClientEntry) {
const events = new Registry<VolumeChangeEvents>();
events.on("query-volume", () => {
events.fire_async("query-volume-response", {
volume: client.getAudioVolume()
});
});
events.on("change-volume", event => {
client.setAudioVolume(event.newValue);
});
const modal = spawnReactModal(class extends Modal {
renderBody() {
return <VolumeChangeModal remote={false} clientName={client.clientNickName()} events={events} />;
}
title(): string {
return tr("Change local volume");
}
});
events.on("close-modal", event => modal.destroy());
modal.show();
return modal;
}
export function spawnMusicBotVolumeChange(client: MusicClientEntry, maxValue: number) {
//FIXME: Max value!
const events = new Registry<VolumeChangeEvents>();
events.on("query-volume", () => {
events.fire_async("query-volume-response", {
volume: client.properties.player_volume
});
});
events.on("apply-volume", event => {
client.channelTree.client.serverConnection.send_command("clientedit", {
clid: client.clientId(),
player_volume: event.newValue,
}).then(() => {
events.fire("apply-volume-result", { newValue: client.properties.player_volume, success: true });
}).catch(() => {
events.fire("apply-volume-result", { newValue: client.properties.player_volume, success: false });
});
});
const modal = spawnReactModal(class extends Modal {
renderBody() {
return <VolumeChangeModal remote={true} clientName={client.clientNickName()} maxVolume={maxValue} events={events} />;
}
title(): string {
return tr("Change remote volume");
}
});
events.on("close-modal", event => modal.destroy());
modal.show();
return modal;
}