Fixed the microphone selection for the newcomer modal

canary
WolverinDEV 2020-08-19 22:40:40 +02:00 committed by WolverinDEV
parent b6db01e7d2
commit b8fbd58276
9 changed files with 316 additions and 189 deletions

View File

@ -14,7 +14,7 @@ html:root {
padding: 0!important;
min-width: 20em;
width: 50em;
width: 60em;
@include user-select(none);
@ -70,6 +70,8 @@ html:root {
@include chat-scrollbar-horizontal();
@include chat-scrollbar-vertical();
background-color: #19191b;
.body {
display: flex;
flex-direction: column;
@ -86,7 +88,7 @@ html:root {
&.step-welcome, &.step-finish {
display: flex;
flex-direction: row;
justify-content: stretch;
justify-content: center;
.text {
align-self: center;
@ -119,7 +121,7 @@ html:root {
}
/* for step-identity or step-microphone */
.container-settings-identity-profile, .container-settings-audio-microphone {
.container-settings-identity-profile {
padding: .5em;
.left .body {
@ -136,8 +138,6 @@ html:root {
&.step-identity { }
&.step-microphone { }
&.hidden {
display: none;
}

View File

@ -37,9 +37,7 @@
{{tr "It is save to exit this guide at any point and directly jump ahead using the client." /}}
</div>
</div>
<div class="step step-microphone">
{{include tmpl="tmpl_settings-microphone" /}}
</div>
<div class="step step-microphone"></div>
<div class="step step-identity">
{{include tmpl="tmpl_settings-profiles" /}}
</div>

View File

@ -5,6 +5,10 @@ import { modal as emodal } from "tc-shared/events";
import {modal_settings} from "tc-shared/ui/modal/ModalSettings";
import {profiles} from "tc-shared/profiles/ConnectionProfile";
import {spawnYesNo} from "tc-shared/ui/modal/ModalYesNo";
import {initialize_audio_microphone_controller, MicrophoneSettingsEvents} from "tc-shared/ui/modal/settings/Microphone";
import {MicrophoneSettings} from "tc-shared/ui/modal/settings/MicrophoneRenderer";
import * as React from "react";
import * as ReactDOM from "react-dom";
const next_step: {[key: string]:string} = {
"welcome": "microphone",
@ -79,7 +83,7 @@ function initializeBasicFunctionality(tag: JQuery, event_registry: Registry<emod
const button_last_step = buttons.find(".button-last-step");
const button_next_step = buttons.find(".button-next-step");
button_last_step.on('click', event => {
button_last_step.on('click', () => {
if(last_step[current_step])
event_registry.fire("show_step", { step: last_step[current_step] as any });
else
@ -100,8 +104,8 @@ function initializeBasicFunctionality(tag: JQuery, event_registry: Registry<emod
button_last_step.text(last_step[current_step] ? tr("Last step") : tr("Skip guide"));
});
event_registry.on("show_step", event => button_next_step.prop("disabled", true));
event_registry.on("show_step", event => button_last_step.prop("disabled", true));
event_registry.on("show_step", () => button_next_step.prop("disabled", true));
event_registry.on("show_step", () => button_last_step.prop("disabled", true));
event_registry.on("step-status", event => button_next_step.prop("disabled", !event.next_button));
event_registry.on("step-status", event => button_last_step.prop("disabled", !event.previous_button));
@ -292,145 +296,28 @@ function initializeStepIdentity(tag: JQuery, event_registry: Registry<emodal.new
}
function initializeStepMicrophone(tag: JQuery, event_registry: Registry<emodal.newcomer>, modal: Modal) {
const microphone_events = new Registry<emodal.settings.microphone>();
//microphone_events.enable_debug("settings-microphone");
//modal_settings.initialize_audio_microphone_controller(microphone_events);
//modal_settings.initialize_audio_microphone_view(tag, microphone_events);
modal.close_listener.push(() => microphone_events.fire_async("deinitialize"));
let helpStep = 0;
let help_animation_done = false;
const update_step_status = () => event_registry.fire_async("step-status", { next_button: help_animation_done, previous_button: help_animation_done });
event_registry.on("show_step", e => {
if(e.step !== "microphone") return;
const settingEvents = new Registry<MicrophoneSettingsEvents>();
settingEvents.on("query_help", () => settingEvents.fire_async("notify_highlight", { field: helpStep <= 2 ? ("hs-" + helpStep) as any : undefined }));
settingEvents.on("action_help_click", () => {
helpStep++;
settingEvents.fire("query_help");
update_step_status();
event_registry.fire_async("step-status", { next_button: helpStep > 2, previous_button: helpStep > 2 })
});
/* the help sequence */
{
const container = tag.find(".container-settings-audio-microphone");
const container_help_text = tag.find(".container-help-text");
initialize_audio_microphone_controller(settingEvents);
ReactDOM.render(<MicrophoneSettings events={settingEvents} />, tag[0]);
modal.close_listener.push(() => {
settingEvents.fire("notify_destroy");
ReactDOM.unmountComponentAtNode(tag[0]);
});
const container_profile_list = tag.find(".highlight-microphone-list");
const container_profile_settings = tag.find(".highlight-microphone-settings");
let is_first_show = true;
event_registry.on("show_step", event => {
if(!is_first_show || event.step !== "microphone") return;
is_first_show = false;
container.addClass("help-shown");
const text = tr( /* @tr-ignore */
"Firstly we need to setup a microphone.\n" +
"Let me guide you thru the basic UI elements.\n" +
"\n" +
"To continue click anywhere on the screen."
);
set_help_text(text);
$("body").one('mousedown', event => show_microphone_list_help());
if(event.step !== "microphone") return;
event_registry.fire_async("step-status", { next_button: helpStep > 2, previous_button: helpStep > 2 });
});
const set_help_text = text => {
container_help_text.empty();
text.split("\n").forEach(e => container_help_text.append(e == "" ? $.spawn("br") : $.spawn("a").text(e)));
};
const show_microphone_list_help = () => {
container.find(".highlighted").removeClass("highlighted");
container_profile_list.addClass("highlighted");
const update_position = () => {
const font_size = parseFloat(getComputedStyle(container_help_text[0]).fontSize);
const offset = container_profile_list.offset();
const abs = container.offset();
container_help_text.css({
top: offset.top - abs.top,
left: ((offset.left - abs.left) + container_profile_list.outerWidth() + font_size) + "px",
right: "1em",
bottom: "1em"
});
};
update_position();
container_help_text.off('resize').on('resize', update_position);
const text = tr( /* @tr-ignore */
"All your available microphones are listed within this box.\n" +
"\n" +
"The currently selected microphone\n" +
"is marked with a green checkmark. To change the selected microphone\n" +
"just click on the new one.\n" +
"\n" +
"To continue click anywhere on the screen."
);
set_help_text(text);
$("body").one('mousedown', event => show_microphone_settings_help());
};
const show_microphone_settings_help = () => {
container.find(".highlighted").removeClass("highlighted");
container_profile_settings.addClass("highlighted");
const update_position = () => {
const font_size = parseFloat(getComputedStyle(container_help_text[0]).fontSize);
const container_settings_offset = container_profile_settings.offset();
const right = container_profile_settings.outerWidth() + font_size * 2;
container_help_text.css({
top: container_settings_offset.top - container.offset().top,
left: "1em",
right: right + "px",
bottom: "1em"
});
};
container_help_text.empty();
container_help_text.append($.spawn("div").addClass("help-microphone-settings").append(
$.spawn("a").text(tr("On the right side you'll find all microphone settings.")),
$.spawn("br"),
$.spawn("a").text("TeaSpeak has three voice activity detection types:"),
$.spawn("ol").append(
$.spawn("li").addClass("vad-type").append(
$.spawn("a").addClass("title").text(tr("Push to Talk")),
$.spawn("a").addClass("description").html(tr( /* @tr-ignore */
"To transmit audio data you'll have to<br>" +
"press a key. The key could be selected " +
"via the button right to the radio button."
))
),
$.spawn("li").addClass("vad-type").append(
$.spawn("a").addClass("title").text(tr("Voice activity detection")),
$.spawn("a").addClass("description").html(tr( /* @tr-ignore */
"In this mode, TeaSpeak will continuously analyze your microphone input. " +
"If the audio level is grater than a certain threshold, " +
"the audio will be transmitted. " +
"The threshold is changeable via the \"Sensitivity Settings\" slider."
))
),
$.spawn("li").addClass("vad-type").append(
$.spawn("a").addClass("title").html(tr("Always active")),
$.spawn("a").addClass("description").text(tr( /* @tr-ignore */
"Continuously transmit any audio data.\n"
))
)
),
$.spawn("br"),
$.spawn("a").text(tr("Now you're ready to configure your microphone. Just click anywhere on the screen."))
));
update_position();
container_help_text.off('resize').on('resize', update_position);
$("body").one('mousedown', event => hide_help());
};
const hide_help = () => {
container.find(".highlighted").removeClass("highlighted");
container.addClass("hide-help");
setTimeout(() => container.removeClass("help-shown"), 1000);
container_help_text.off('resize');
help_animation_done = true;
update_step_status();
};
}
}

View File

@ -0,0 +1,115 @@
@import "../../../../css/static/mixin.scss";
@import "../../../../css/static/properties.scss";
.container {
$highlight-time: .5s;
$backdrop-color: rgba(0, 0, 0, .9);
display: flex;
position: relative;
padding: .5em;
background-color: inherit;
.background {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: none;
background-color: $backdrop-color;
border-radius: .15em;
padding: .5em;
}
/*
.highlightable {
display: flex;
}
*/
.helpText {
opacity: 0;
z-index: 20;
pointer-events: none;
display: block;
overflow: auto;
@include chat-scrollbar();
@include transition($highlight-time ease-in-out);
a {
display: block;
}
ol {
margin-top: .5em;
margin-bottom: 0;
}
li {
margin-bottom: .5em;
.title {
font-weight: bold;
}
}
&.shown {
opacity: 1;
pointer-events: initial;
@include transition($highlight-time ease-in-out);
}
}
&.shown {
.background {
display: flex;
z-index: 1;
opacity: 1;
}
.highlightable {
border-radius: .1em;
position: relative;
z-index: 10;
background-color: inherit;
@include transition($highlight-time ease-in-out);
&::after {
content: ' ';
z-index: 5;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: $backdrop-color;
@include transition($highlight-time ease-in-out);
}
&.highlighted {
padding: .5em;
&::after {
background-color: #00000000;
}
}
}
}
}

View File

@ -0,0 +1,38 @@
import * as React from "react";
import {useContext} from "react";
const cssStyle = require("./Heighlight.scss");
const HighlightContext = React.createContext<string>(undefined);
export const HighlightContainer = (props: { children: React.ReactNode | React.ReactNode[], highlightedId?: string, onClick?: () => void }) => {
return (
<HighlightContext.Provider value={props.highlightedId}>
<div className={cssStyle.container + " " + (props.highlightedId ? cssStyle.shown : "")} onClick={props.highlightedId ? props.onClick : undefined}>
{props.children}
<div className={cssStyle.background} />
</div>
</HighlightContext.Provider>
);
};
export const HighlightRegion = (props: React.HTMLProps<HTMLDivElement> & { highlightId: string } ) => {
const wProps = Object.assign({}, props);
delete wProps["highlightId"];
const highlightedId = useContext(HighlightContext);
const highlighted = highlightedId === props.highlightId;
wProps.className = (props.className || "") + " " + cssStyle.highlightable + " " + (highlighted ? cssStyle.highlighted : "");
return React.createElement("div", wProps);
};
export const HighlightText = (props: { highlightId: string, className?: string, children?: React.ReactNode | React.ReactNode[] } ) => {
const highlightedId = useContext(HighlightContext);
const highlighted = highlightedId === props.highlightId;
return (
<div className={cssStyle.helpText + " " + (highlighted ? cssStyle.shown : "") + " " + props.className}>
{props.children}
</div>
)
};

View File

@ -11,6 +11,7 @@
min-width: 43em;
min-height: 41em;
background-color: inherit;
position: relative;
.left, .right {
@ -588,6 +589,20 @@
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#70407e', endColorstr='#45407e',GradientType=1 ); /* IE6-9 */
}
/* The help overlays */
.help {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
&.paddingTop {
padding-top: 3.6em;
}
}
@-moz-keyframes spin { 100% { -moz-transform: rotate(360deg); } }
@-webkit-keyframes spin { 100% { -webkit-transform: rotate(360deg); } }
@keyframes spin { 100% { -webkit-transform: rotate(360deg); transform:rotate(360deg); } }

View File

@ -24,11 +24,12 @@ export type MicrophoneDevice = {
export interface MicrophoneSettingsEvents {
"query_devices": { refresh_list: boolean },
"query_help": {},
"query_setting": {
setting: MicrophoneSetting
},
"action_help_click": {},
"action_request_permissions": {},
"action_set_selected_device": { deviceId: string },
"action_set_selected_device_result": {
@ -70,6 +71,10 @@ export interface MicrophoneSettingsEvents {
status: Exclude<DeviceListState, "error">
},
notify_highlight: {
field: "hs-0" | "hs-1" | "hs-2" | undefined
}
notify_destroy: {}
}
@ -310,7 +315,6 @@ export function initialize_audio_microphone_controller(events: Registry<Micropho
}
}
/*
loader.register_task(Stage.LOADED, {
name: "test",
function: async () => {
@ -342,4 +346,3 @@ loader.register_task(Stage.LOADED, {
},
priority: -2
})
*/

View File

@ -1,7 +1,7 @@
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 {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";
@ -16,6 +16,7 @@ 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";
import {HighlightContainer, HighlightRegion, HighlightText} from "./Heighlight";
const cssStyle = require("./Microphone.scss");
@ -157,7 +158,6 @@ const MicrophoneList = (props: { events: Registry<MicrophoneSettingsEvents> }) =
});
const [ selectedDevice, setSelectedDevice ] = useState<{ deviceId: string, mode: "selected" | "selecting" }>();
const [ deviceList, setDeviceList ] = useState<MicrophoneDevice[]>([]);
const [ error, setError ] = useState(undefined);
props.events.reactUse("notify_devices", event => {
setSelectedDevice(undefined);
@ -202,7 +202,7 @@ const MicrophoneList = (props: { events: Registry<MicrophoneSettingsEvents> }) =
</a>
</div>
<div className={cssStyle.overlay + " " + (state.type !== "error" ? cssStyle.hidden : undefined)}>
<a>{error}</a>
<a>{state.type === "error" ? state.message : undefined}</a>
</div>
<div className={cssStyle.overlay + " " + (state.type !== "no-permissions" ? cssStyle.hidden : undefined)}>
<a><Translatable>Please grant access to your microphone.</Translatable></a>
@ -506,16 +506,86 @@ const ThresholdSelector = (props: { events: Registry<MicrophoneSettingsEvents> }
)
};
export const MicrophoneSettings = (props: { events: Registry<MicrophoneSettingsEvents> }) => (
const HelpText0 = () => (
<HighlightText highlightId={"hs-0"} className={cssStyle.help}>
<Translatable>
Firstly we need to setup a microphone.<br />
Let me guide you thru the basic UI elements.<br />
<br />
To continue click anywhere on the screen.
</Translatable>
</HighlightText>
);
const HelpText1 = () => (
<HighlightText highlightId={"hs-1"} className={cssStyle.help + " " + cssStyle.paddingTop}>
<Translatable>
All your available microphones are listed within this box.<br />
<br />
The currently selected microphone<br />
is marked with a green checkmark. To change the selected microphone<br />
just click on the new one.<br />
<br />
To continue click anywhere on the screen.
</Translatable>
</HighlightText>
);
const HelpText2 = () => (
<HighlightText highlightId={"hs-2"} className={cssStyle.help + " " + cssStyle.paddingTop}>
<a>
<Translatable>TeaSpeak has three voice activity detection types:</Translatable>
</a>
<ol>
<li>
<Translatable>
To transmit audio data you'll have to<br />
press a key. The key could be selected via the button right to the radio button
</Translatable>
</li>
<li>
<Translatable>
In this mode, TeaSpeak will continuously analyze your microphone input.
If the audio level is grater than a certain threshold,
the audio will be transmitted.
The threshold is changeable via the "Sensitivity Settings" slider
</Translatable>
</li>
<li>
<Translatable>Continuously transmit any audio data.</Translatable>
</li>
</ol>
<a>
<Translatable>
Now you're ready to configure your microphone.<br />
Just click anywhere on the screen.
</Translatable>
</a>
</HighlightText>
);
export const MicrophoneSettings = (props: { events: Registry<MicrophoneSettingsEvents> }) => {
const [ highlighted, setHighlighted ] = useState(() => {
props.events.fire("query_help");
return undefined;
});
props.events.reactUse("notify_highlight", event => setHighlighted(event.field));
return (
<HighlightContainer highlightedId={highlighted} onClick={() => props.events.fire("action_help_click")}>
<div className={cssStyle.container}>
<div className={cssStyle.left}>
<HelpText0/>
<HighlightRegion className={cssStyle.left} highlightId={"hs-1"}>
<div className={cssStyle.header}>
<a><Translatable>Select your Microphone Device</Translatable></a>
<ListRefreshButton events={props.events}/>
</div>
<MicrophoneList events={props.events}/>
</div>
<div className={cssStyle.right}>
<HelpText2/>
</HighlightRegion>
<HighlightRegion className={cssStyle.right} highlightId={"hs-2"}>
<HelpText1/>
<div className={cssStyle.header}>
<a><Translatable>Microphone Settings</Translatable></a>
</div>
@ -537,6 +607,8 @@ export const MicrophoneSettings = (props: { events: Registry<MicrophoneSettingsE
<PPTDelaySettings events={props.events}/>
</div>
</div>
</HighlightRegion>
</div>
</div>
)
</HighlightContainer>
);
}

File diff suppressed because one or more lines are too long