import * as React from "react";
import {Translatable} from "tc-shared/ui/react-elements/i18n";
import {Button} from "tc-shared/ui/react-elements/Button";
import {modal, Registry} from "tc-shared/events";
import {MicrophoneDevice, MicrophoneSettingsEvents} from "tc-shared/ui/modal/settings/Microphone";
import {useEffect, useRef, useState} from "react";
import {ClientIconRenderer} from "tc-shared/ui/react-elements/Icons";
import {ClientIcon} from "svg-sprites/client-icons";
import {LoadingDots} from "tc-shared/ui/react-elements/LoadingDots";
import {createErrorModal} from "tc-shared/ui/elements/Modal";
import {Slider} from "tc-shared/ui/react-elements/Slider";
import {RadioButton} from "tc-shared/ui/react-elements/RadioButton";
import {VadType} from "tc-shared/voice/RecorderProfile";
import {key_description, KeyDescriptor} from "tc-shared/PPTListener";
import {spawnKeySelect} from "tc-shared/ui/modal/ModalKeySelect";
import {Checkbox} from "tc-shared/ui/react-elements/Checkbox";
import {BoxedInputField} from "tc-shared/ui/react-elements/InputField";
import {IDevice} from "tc-shared/audio/recorder";
const cssStyle = require("./Microphone.scss");
type MicrophoneSelectedState = "selected" | "applying" | "unselected";
const MicrophoneStatus = (props: { state: MicrophoneSelectedState }) => {
switch (props.state) {
case "applying":
return (
);
case "unselected":
return null;
case "selected":
return ;
}
}
type ActivityBarStatus = { mode: "success" } | { mode: "error", message: string } | { mode: "loading" } | { mode: "uninitialized" };
const ActivityBar = (props: { events: Registry, deviceId: string, disabled?: boolean }) => {
const refHider = useRef();
const [ status, setStatus ] = useState({ mode: "loading" });
props.events.reactUse("notify_device_level", event => {
if(event.status === "uninitialized") {
if(status.mode === "uninitialized")
return;
setStatus({ mode: "uninitialized" });
} else if(event.status === "no-permissions") {
const noPermissionsMessage = tr("no permissions");
if(status.mode === "error" && status.message === noPermissionsMessage)
return;
setStatus({ mode: "error", message: noPermissionsMessage });
} else {
const device = event.level[props.deviceId];
if(!device) {
if(status.mode === "loading")
return;
setStatus({ mode: "loading" });
} else if(device.status === "success") {
if(status.mode !== "success") {
setStatus({ mode: "success" });
}
refHider.current.style.width = (100 - device.level) + "%";
} else {
if(status.mode === "error" && status.message === device.error)
return;
setStatus({ mode: "error", message: device.error + "" });
}
}
});
let error;
switch (status.mode) {
case "error":
error = {status.message}
;
break;
case "loading":
error = Loading
;
break;
case "success":
error = undefined;
break;
}
return (
)
};
const Microphone = (props: { events: Registry, device: MicrophoneDevice, state: MicrophoneSelectedState, onClick: () => void }) => {
return (
{props.device.driver}
{props.device.name}
{props.device.id === IDevice.NoDeviceId ? undefined :
}
);
};
type MicrophoneListState = {
type: "normal" | "loading" | "audio-not-initialized"
} | {
type: "error",
message: string
} | {
type: "no-permissions",
bySystem: boolean
}
const PermissionDeniedOverlay = (props: { bySystem: boolean, shown: boolean, onRequestPermission: () => void }) => {
if(props.bySystem) {
return (
);
} else {
return (
);
}
}
const MicrophoneList = (props: { events: Registry }) => {
const [ state, setState ] = useState(() => {
props.events.fire("query_devices");
return { type: "loading" };
});
const [ selectedDevice, setSelectedDevice ] = useState<{ deviceId: string, mode: "selected" | "selecting" }>();
const [ deviceList, setDeviceList ] = useState([]);
const [ error, setError ] = useState(undefined);
props.events.reactUse("notify_devices", event => {
setSelectedDevice(undefined);
switch (event.status) {
case "success":
setDeviceList(event.devices.slice(0));
setState({ type: "normal" });
setSelectedDevice({ mode: "selected", deviceId: event.selectedDevice });
break;
case "error":
setState({ type: "error", message: event.error || tr("Unknown error") });
break;
case "audio-not-initialized":
setState({ type: "audio-not-initialized" });
break;
case "no-permissions":
setState({ type: "no-permissions", bySystem: event.shouldAsk });
break;
}
});
props.events.reactUse("action_set_selected_device", event => {
setSelectedDevice({ mode: "selecting", deviceId: event.deviceId });
});
props.events.reactUse("action_set_selected_device_result", event => {
if(event.status === "error")
createErrorModal(tr("Failed to select microphone"), tra("Failed to select microphone:\n{}", event.error)).open();
setSelectedDevice({ mode: "selected", deviceId: event.deviceId });
});
return (
props.events.fire("action_request_permissions")}
/>
{
if(state.type !== "normal" || selectedDevice?.mode === "selecting")
return;
props.events.fire("action_set_selected_device", { deviceId: IDevice.NoDeviceId });
}}
/>
{deviceList.map(e => {
if(state.type !== "normal" || selectedDevice?.mode === "selecting")
return;
props.events.fire("action_set_selected_device", { deviceId: e.id });
}}
/>)}
)
}
const ListRefreshButton = (props: { events: Registry }) => {
const [ updateTimeout, setUpdateTimeout ] = useState(Date.now() + 2000);
useEffect(() => {
if(updateTimeout === 0)
return;
const id = setTimeout(() => {
setUpdateTimeout(0);
}, Math.max(updateTimeout - Date.now(), 0));
return () => clearTimeout(id);
});
props.events.reactUse("query_devices", () => setUpdateTimeout(Date.now() + 2000));
return ;
}
const VolumeSettings = (props: { events: Registry }) => {
const refSlider = useRef();
const [ value, setValue ] = useState<"loading" | number>(() => {
props.events.fire("query_setting", { setting: "volume" });
return "loading";
})
props.events.reactUse("notify_setting", event => {
if(event.setting !== "volume")
return;
refSlider.current?.setState({ value: event.value });
setValue(event.value);
});
return (
Volume
props.events.fire("action_set_setting", { setting: "volume", value: value })}
/>
)
};
const PPTKeyButton = React.memo((props: { events: Registry }) => {
const [ key, setKey ] = useState<"loading" | KeyDescriptor>(() => {
props.events.fire("query_setting", { setting: "ppt-key" });
return "loading";
});
const [ isActive, setActive ] = useState(false);
props.events.reactUse("notify_setting", event => {
if(event.setting === "vad-type")
setActive(event.value === "push_to_talk");
else if(event.setting === "ppt-key")
setKey(event.value);
});
if(key === "loading") {
return ;
} else {
return ;
}
});
const PPTDelaySettings = (props: { events: Registry }) => {
const [ delayActive, setDelayActive ] = useState<"loading" | boolean>(() => {
props.events.fire("query_setting", { setting: "ppt-release-delay" });
return "loading";
});
const [ delay, setDelay ] = useState<"loading" | number>(() => {
props.events.fire("query_setting", { setting: "ppt-release-delay-active" });
return "loading";
});
const [ isActive, setActive ] = useState(false);
props.events.reactUse("notify_setting", event => {
if(event.setting === "vad-type")
setActive(event.value === "push_to_talk");
else if(event.setting === "ppt-release-delay")
setDelay(event.value);
else if(event.setting === "ppt-release-delay-active")
setDelayActive(event.value);
});
return (
{ props.events.fire("action_set_setting", { setting: "ppt-release-delay-active", value: value })}}
disabled={!isActive}
value={delayActive === true}
label={Delay on Push to Talk}
/>
{
if(event.target.value === "") {
const target = event.target;
setImmediate(() => target.value = "");
return;
}
const newValue = event.target.valueAsNumber;
if(isNaN(newValue))
return;
if(newValue < 0 || newValue > 4000)
return;
props.events.fire("action_set_setting", { setting: "ppt-release-delay", value: newValue });
}}
/>}
/>
);
}
const VadSelector = (props: { events: Registry }) => {
const [ selectedType, setSelectedType ] = useState(() => {
props.events.fire("query_setting", { setting: "vad-type" });
return "loading";
});
props.events.reactUse("notify_setting", event => {
if(event.setting !== "vad-type")
return;
setSelectedType(event.value);
});
return (
{ props.events.fire("action_set_setting", { setting: "vad-type", value: "push_to_talk" }) }}
selected={selectedType === "push_to_talk"}
disabled={selectedType === "loading"}
>
Push to Talk
{ props.events.fire("action_set_setting", { setting: "vad-type", value: "threshold" }) }}
selected={selectedType === "threshold"}
disabled={selectedType === "loading"}
>
Voice activity detection
{ props.events.fire("action_set_setting", { setting: "vad-type", value: "active" }) }}
selected={selectedType === "active"}
disabled={selectedType === "loading"}
>
Always active
);
}
const ThresholdSelector = (props: { events: Registry }) => {
const refSlider = useRef();
const [ value, setValue ] = useState<"loading" | number>(() => {
props.events.fire("query_setting", { setting: "threshold-threshold" });
return "loading";
});
const [ currentDevice, setCurrentDevice ] = useState(undefined);
const [ isActive, setActive ] = useState(false);
props.events.reactUse("notify_setting", event => {
if(event.setting === "threshold-threshold") {
refSlider.current?.setState({ value: event.value });
setValue(event.value);
} else if(event.setting === "vad-type") {
setActive(event.value === "threshold");
}
});
props.events.reactUse("notify_devices", event => {
setCurrentDevice(event.selectedDevice);
});
props.events.reactUse("action_set_selected_device_result", event => {
setCurrentDevice(event.deviceId);
});
return (
{ props.events.fire("action_set_setting", { setting: "threshold-threshold", value: value })}}
/>
)
};
export const MicrophoneSettings = (props: { events: Registry }) => (
)