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 }, VolumeChangeModalState> { private readonly refSlider = React.createRef(); 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 (
Change value for client {this.props.clientName}
valueString} onInput={value => this.onValueChanged(value)} value={this.state.sliderValue} disabled={this.state.state !== "user-input"} ref={this.refSlider} /> {valueString}
); } 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("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("query-volume") private handleVolumeQuery() { this.setState({ state: "querying" }); this.refSlider.current?.setState({ disabled: true }); } @EventHandler("apply-volume") private handleApplyVolume() { this.setState({ state: "applying" }); this.refSlider.current?.setState({ disabled: true }); } @EventHandler("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("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(); 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 ; } 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(); 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 ; } title(): string { return tr("Change remote volume"); } }); events.on("close-modal", event => modal.destroy()); modal.show(); return modal; }