Added a welcomer guide

canary
WolverinDEV 2020-03-27 16:15:15 +01:00
parent dce44b6486
commit a7c8d764f4
21 changed files with 4063 additions and 1238 deletions

View File

@ -1,4 +1,7 @@
# Changelog:
* **21.03.20**
- Fixed identity import throwing an "btoa" error
* **19.03.20**
- Using proper icons for the client info
- Added an image preview overlay

View File

@ -35,6 +35,7 @@ files=(
"css/static/modal-group-assignment.css"
"css/static/modal-icons.css"
"css/static/modal-identity.css"
"css/static/modal-newcomer.css"
"css/static/modal-invite.css"
"css/static/modal-keyselect.css"
"css/static/modal-permissions.css"

View File

@ -70,6 +70,7 @@
& {
/* for moz */
scrollbar-color: #353535 #555;
scrollbarWidth: .5em;
}
&::-webkit-scrollbar-track {

View File

@ -0,0 +1,157 @@
@import "mixin";
@import "properties";
.modal-body.modal-newcomer {
display: flex!important;
flex-direction: column!important;
justify-content: stretch!important;
padding: 0!important;
min-width: 20em;
width: 50em;
@include user-select(none);
.container-header {
flex-shrink: 0;
flex-grow: 0;
color: #565656;
padding: .5em .5em .25em;
position: relative;
font-size: 1.5em;
text-transform: uppercase;
.step {
&.hidden {
display: none;
}
&::after {
content: ' ';
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 1.25px;
//background: linear-gradient(90deg, rgba(49,49,53,1) 80%, rgba(49,49,53,0) 100%);
background: rgba(49,49,53,1);
}
&.hidden {
&::after {
content: unset;
}
}
}
}
.container-body {
//flex-grow: 1;
//flex-shrink: 1;
flex-shrink: 1;
min-height: 18em;
display: flex;
flex-direction: column;
justify-content: center;
overflow: auto;
@include chat-scrollbar-horizontal();
@include chat-scrollbar-vertical();
.body {
display: flex;
flex-direction: column;
justify-content: stretch;
padding: .5em .5em .5em;
&.height-transition {
@include transition(max-height .25s ease-in-out, min-height .25s ease-in-out);
overflow: hidden;
}
.step {
&.step-welcome, &.step-finish {
display: flex;
flex-direction: row;
justify-content: stretch;
.text {
align-self: center;
h1 {
line-height: 1.1em;
margin-bottom: .8em;
margin-top: 0;
}
flex-shrink: 0;
}
&.step-welcome h1 {
margin-bottom: 0;
}
.logo {
max-height: 15em;
max-width: 15em;
align-self: center;
margin-right: 1em;
img {
max-height: 100%;
max-width: 100%;
}
}
}
/* for step-identity or step-microphone */
.container-settings-identity-profile, .container-settings-audio-microphone {
padding: .5em;
.left .body {
// background-color: #19191b;
background-color: hsla(220, 4%, 13%, 1);
.overlay {
background-color: hsla(220, 4%, 13%, 1);
}
.profile.selected {
background-color: hsla(240, 2%, 8%, 1);
}
}
}
&.step-identity {
}
&.step-microphone {
}
&.hidden {
display: none;
}
}
}
}
.buttons {
flex-shrink: 0;
flex-grow: 0;
display: flex;
flex-direction: row;
justify-content: space-between;
border-top: 1.25px solid rgba(49,49,53,1);
padding: .5em;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -409,7 +409,7 @@
background-image: linear-gradient(0deg, #008aff 2px, rgba(0, 150, 136, 0) 0), linear-gradient(0deg, #393939 1px, transparent 0);
&:focus {
height: calc(2.25em - 1px); /* Center the blue line */
height: 2.25em;
background-size: 100% 100%, 100% 100%;
transition-duration: .3s;

View File

@ -2237,102 +2237,7 @@
</div>
<div class="container audio-microphone">
<div class="left">
<div class="header">
<a>{{tr "Select your Microphone Device" /}}</a>
<button class="btn btn-info button-update">{{tr "Update" /}}</button>
</div>
<div class="body container-devices">
</div>
</div>
<div class="right">
<div class="header">
<a>{{tr "Microphone Settings" /}}</a>
</div>
<div class="body">
<div class="container-volume">
<a>{{tr "Volume" /}}</a>
<div class="container-slider">
<div class="filler" style="width: 30%"></div>
<div class="thumb container-tooltip" style="left: 30%">
<div class="tooltip">
<a>86%</a>
</div>
</div>
</div>
</div>
<div class="container-select-vad">
<div class="fieldset">
<div class="container container-ppt">
<label class="container-ppt">
<div class="ratio-button">
<input type="radio" name="vad-type" value="push_to_talk">
<div class="mark"></div>
</div>
<a>{{tr "Push to Talk" /}}</a>
</label>
<div class="container-button">
<button class="btn">{{tr "T" /}}</button>
</div>
</div>
<div class="container container-vad">
<label>
<div class="ratio-button">
<input type="radio" name="vad-type" value="threshold">
<div class="mark"></div>
</div>
<a>{{tr "Voice activity detection" /}}</a>
</label>
</div>
<div class="container container-always-active">
<label>
<div class="ratio-button">
<input type="radio" name="vad-type" value="active">
<div class="mark"></div>
</div>
<a>{{tr "Always active" /}}</a>
</label>
</div>
</div>
</div>
</div>
<div class="header">
<a>{{tr "Sensitivity Settings" /}}</a>
</div>
<div class="body">
<div class="container-sensitivity">
<div class="container-activity-bar">
<div class="bar-hider" style="width: 80%;"></div>
<div class="bar-error"></div>
<div class="thumb container-tooltip" style="left: 20%">
<div class="tooltip">
<a>20%</a>
</div>
</div>
</div>
</div>
</div>
<div class="header">
<a>{{tr "Advanced Settings" /}}</a>
</div>
<div class="body">
<div class="container-advanced">
<div class="container-ppt-delay">
<label>
<div class="checkbox">
<input type="checkbox" class="delay-enabled">
<div class="mark"></div>
</div>
<a>{{tr "Delay on Push to Talk" /}}</a>
</label>
<div class="container-input">
<input type="number" class="delay-time" min="0" max="4" step="0.1">
<label>{{tr "Sec" /}}</label>
</div>
</div>
</div>
</div>
</div>
{{include tmpl="#tmpl_settings-microphone" /}}
</div>
<div class="container audio-speaker">
<div class="left">
@ -2421,103 +2326,7 @@
</div>
<div class="container identity-profiles">
<div class="left">
<div class="header">
<a>{{tr "Your Profiles" /}}</a>
<button class="btn btn-info button-create">{{tr "Create new" /}}</button>
</div>
<div class="body">
<div class="container-profiles">
</div>
<div class="buttons">
<button class="btn btn-danger button-delete">{{tr "Delete selected" /}}</button>
<div class="spacer"></div>
<button class="btn btn-success button-set-default">{{tr "Select as Default" /}}
</button>
</div>
</div>
</div>
<div class="right">
<div class="header">
<a>{{tr "Profile Settings" /}}</a>
<div class="spacer"></div>
<div class="container-avatar"></div>
<button class="btn btn-info button-change-avatar">{{tr "Change Avatar" /}}</button>
<!-- AVATAR -->
</div>
<div class="body">
<div class="container-general">
<div class="form-group">
<label>{{tr "Profile Name" /}}</label>
<input class="form-control profile-name">
<div class="invalid-feedback">{{tr "Profile name is invalid" /}}</div>
</div>
<div class="form-group">
<label>{{tr "Default Nickname" /}}</label>
<input class="form-control profile-default-name"
placeholder="Another TeaSpeak user">
</div>
<div class="form-group">
<label>{{tr "Identity Type" /}}</label>
<select class="form-control profile-identity-type">
<option value="unset" style="display: none">{{tr "Unset" /}}</option>
<option value="teaforo">{{tr "Forum Account" /}}</option>
<option value="teamspeak">{{tr "TeamSpeak Identity" /}}</option>
<option value="nickname">{{tr "Nickname (Debug only!)" /}}</option>
</select>
<div class="invalid-feedback">{{tr "Invalid identity type" /}}</div>
</div>
</div>
<div class="container-teamspeak">
<div class="container-valid">
<div class="form-group">
<label>{{tr "Unique-ID" /}}</label>
<input class="form-control unique-id" readonly>
</div>
<div class="container-level">
<div class="form-group">
<label>{{tr "Level" /}}</label>
<input class="form-control current-level" readonly>
</div>
<button class="btn button-improve">{{tr "Improve" /}}</button>
</div>
</div>
<div class="container-invalid">
{{tr "You have'nt generated/imported an identity." /}}<br>
{{tr "Generate a new one or import one." /}}
</div>
<div class="buttons">
<button class="btn btn-danger button-new">{{tr "Generate new" /}}</button>
<div>
<button class="btn btn-danger button-import">{{tr "Import identity" /}}
</button>
<button class="btn btn-success button-export">{{tr "Export identity" /}}
</button>
</div>
</div>
</div>
<div class="container-teaforo">
<div class="container-valid">
{{tr "You're using your forum account as identification" /}}
</div>
<div class="container-invalid">
<a>{{tr "You cant use your TeaSpeak forum account. You're not connected with your forum Account!" /}}</a>
<button class="btn btn-success button-setup">{{tr "Setup your connection" /}}
</button>
</div>
</div>
<div class="container-nickname">
<div class="form-group">
<label>{{tr "Nickname" /}}</label>
<input class="form-control nickname">
<div class="invalid-feedback">{{tr "Invalid nickname. Name must be at least 5 characters" /}}
</div>
</div>
<div class="invalid-feedback"></div>
</div>
</div>
</div>
{{include tmpl="#tmpl_settings-profiles" /}}
</div>
<div class="container identity-forum">
<div class="fill">
@ -2558,6 +2367,238 @@
</div>
</script>
<script class="jsrender-template" id="tmpl_settings-microphone" type="text/html">
<div class="container-settings-audio-microphone">
<div class="left highlightable highlight-microphone-list">
<div class="header">
<a>{{tr "Select your Microphone Device" /}}</a>
<button class="btn btn-info button-update">{{tr "Update" /}}</button>
</div>
<div class="body container-devices">
<div class="overlay overlay-error">
<a class="error-text"></a>
</div>
<div class="overlay overlay-loading"></div>
</div>
</div>
<div class="right highlightable highlight-microphone-settings">
<div class="header">
<a>{{tr "Microphone Settings" /}}</a>
</div>
<div class="body">
<div class="container-volume">
<a>{{tr "Volume" /}}</a>
<div class="container-slider">
<div class="filler" style="width: 30%"></div>
<div class="thumb container-tooltip" style="left: 30%">
<div class="tooltip">
<a>86%</a>
</div>
</div>
</div>
</div>
<div class="container-select-vad">
<div class="fieldset">
<div class="container container-ppt">
<label class="container-ppt">
<div class="ratio-button">
<input type="radio" name="vad-type" value="push_to_talk">
<div class="mark"></div>
</div>
<a>{{tr "Push to Talk" /}}</a>
</label>
<div class="container-button">
<button class="btn">{{tr "T" /}}</button>
</div>
</div>
<div class="container container-vad">
<label>
<div class="ratio-button">
<input type="radio" name="vad-type" value="threshold">
<div class="mark"></div>
</div>
<a>{{tr "Voice activity detection" /}}</a>
</label>
</div>
<div class="container container-always-active">
<label>
<div class="ratio-button">
<input type="radio" name="vad-type" value="active">
<div class="mark"></div>
</div>
<a>{{tr "Always active" /}}</a>
</label>
</div>
</div>
</div>
</div>
<div class="header">
<a>{{tr "Sensitivity Settings" /}}</a>
</div>
<div class="body">
<div class="container-sensitivity">
<div class="container-activity-bar">
<div class="bar-hider" style="width: 80%;"></div>
<div class="bar-error"></div>
<div class="thumb container-tooltip" style="left: 20%">
<div class="tooltip">
<a>20%</a>
</div>
</div>
</div>
</div>
</div>
<div class="header">
<a>{{tr "Advanced Settings" /}}</a>
</div>
<div class="body">
<div class="container-advanced">
<div class="container-ppt-delay">
<label>
<div class="checkbox">
<input type="checkbox" class="delay-enabled">
<div class="mark"></div>
</div>
<a>{{tr "Delay on Push to Talk" /}}</a>
</label>
<div class="container-input">
<input type="number" class="delay-time" min="0" max="4" step="0.1">
<label>{{tr "Sec" /}}</label>
</div>
</div>
</div>
</div>
</div>
<div class="help-background"></div>
<div class="container-help-text window-resize-listener">
<a class="help-text">Hello nice to see you. This is even working with new lines etc....</a>
</div>
</div>
</script>
<script class="jsrender-template" id="tmpl_settings-profiles" type="text/html">
<div class="container-settings-identity-profile">
<div class="left highlight-profile-list highlightable">
<div class="header">
<a>{{tr "Your Profiles" /}}</a>
<button class="btn btn-info button-create">{{tr "Create new" /}}</button>
</div>
<div class="body">
<div class="container-profiles">
<div class="overlay overlay-error"><a class="error">error: error</a></div>
<div class="overlay overlay-timeout">
<a>{{tr "Timeout while loading" /}}</a><br>
<button class="btn btn-blue button-reload-list">{{tr "Reload" /}}</button>
</div>
<div class="overlay overlay-empty">
<a>{{tr "You've not profiles yet" /}}</a>
</div>
</div>
<div class="buttons">
<button class="btn btn-danger button-delete">{{tr "Delete selected" /}}</button>
<div class="spacer"></div>
<button class="btn btn-success button-set-default">{{tr "Select as Default" /}}
</button>
</div>
<div class="buttons-small">
<button class="btn btn-danger button-delete">{{tr "Delete" /}}</button>
<div class="spacer"></div>
<button class="btn btn-success button-set-default">{{tr "Default" /}}
</button>
</div>
</div>
</div>
<div class="right">
<div class="header">
<a>{{tr "Profile Settings" /}}</a>
<div class="spacer"></div>
<div class="container-avatar"></div>
<button class="btn btn-info button-change-avatar">{{tr "Change Avatar" /}}</button>
<!-- AVATAR -->
</div>
<div class="body">
<div class="container-general highlight-profile-settings highlightable">
<div class="form-group">
<label>{{tr "Profile Name" /}}</label>
<input class="form-control profile-name">
<div class="invalid-feedback">{{tr "Profile name is invalid" /}}</div>
</div>
<div class="form-group">
<label>{{tr "Default Nickname" /}}</label>
<input class="form-control profile-default-name"
placeholder="Another TeaSpeak user">
</div>
<div class="form-group">
<label>{{tr "Identity Type" /}}</label>
<select class="form-control profile-identity-type">
<option value="error" style="display: none">error: error</option>
<option value="unset" style="display: none">{{tr "Unset" /}}</option>
<option value="teaforo">{{tr "Forum Account" /}}</option>
<option value="teamspeak">{{tr "TeamSpeak Identity" /}}</option>
<option value="nickname">{{tr "Nickname (Debug only!)" /}}</option>
</select>
<div class="invalid-feedback">{{tr "Invalid identity type" /}}</div>
</div>
</div>
<div class="container-teamspeak">
<div class="container-valid">
<div class="form-group">
<label>{{tr "Unique-ID" /}}</label>
<input class="form-control unique-id" readonly>
</div>
<div class="container-level">
<div class="form-group">
<label>{{tr "Level" /}}</label>
<input class="form-control current-level" readonly>
</div>
<button class="btn button-improve">{{tr "Improve" /}}</button>
</div>
</div>
<div class="container-invalid">
{{tr "You have'nt generated/imported an identity." /}}<br>
{{tr "Generate a new one or import one." /}}
</div>
<div class="buttons">
<button class="btn btn-danger button-new">{{tr "Generate new" /}}</button>
<div>
<button class="btn btn-danger button-import">{{tr "Import identity" /}}
</button>
<button class="btn btn-success button-export">{{tr "Export identity" /}}
</button>
</div>
</div>
</div>
<div class="container-teaforo">
<div class="container-valid">
{{tr "You're using your forum account as identification" /}}
</div>
<div class="container-invalid">
<a>{{tr "You cant use your TeaSpeak forum account. You're not connected with your forum Account!" /}}</a>
<button class="btn btn-success button-setup">{{tr "Setup your connection" /}}
</button>
</div>
</div>
<div class="container-nickname">
<div class="form-group">
<label>{{tr "Nickname" /}}</label>
<input class="form-control nickname">
<div class="invalid-feedback">{{tr "Invalid nickname. Name must be at least 5 characters" /}}
</div>
</div>
<div class="invalid-feedback"></div>
</div>
<div class="container-highlight-dummy highlight-identity-settings highlightable"></div>
</div>
</div>
<div class="help-background"></div>
<div class="container-help-text window-resize-listener">
<a class="help-text">Hello nice to see you. This is even working with new lines etc....</a>
</div>
</div>
</script>
<script class="jsrender-template" id="tmpl_settings-sound_entry" type="text/html">
<div class="entry">
<div class="column sound-name">
@ -2860,12 +2901,6 @@
</div>
</script>
<script class="jsrender-template" id="tmpl_newcomer" type="text/html">
<div> <!-- required for the renderer -->
</div>
</script>
<!-- Permission overview -->
<script class="jsrender-template" id="tmpl_server_permissions" type="text/html">
<div class="container">

View File

@ -0,0 +1,70 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Template modal newcomer</title>
</head>
<body>
<script class="jsrender-template" id="tmpl_newcomer" type="text/html">
<div> <!-- required for the renderer -->
<div class="container-header">
<!-- We don't have a title here because we've put it into the body. -->
<!-- <div class="step step-welcome">{{tr "Welcome to the easy setup guide!" /}} </div> -->
<div class="step step-identity">{{tr "Identity setup" /}}</div>
<div class="step step-microphone">{{tr "Microphone setup" /}}</div>
<div class="step step-speaker">{{tr "Speaker setup" /}}</div>
<!-- <div class="step step-finish">{{tr "Setup finished" /}}</div> -->
</div>
<div class="container-body">
<div class="body">
<div class="step step-welcome">
<div class="logo">
<img src="img/teaspeak_cup_animated.png">
</div>
<div class="text">
<h1>{{tr "Welcome dear TeaSpeak user." /}}</h1>
{{tr "We would like to setup a few things before you're ready to go.<br>" /}}
{{tr "Dont worry! We'll guide you thru the basic setup process." /}}<br>
{{tr "Together we'll go thru these steps:" /}}
<ol>
<li>{{tr "Welcome Greeting" /}}</li>
<li>{{tr "Microphone configuration" /}}</li>
<li>{{tr "Identity setup" /}}</li>
{{if !is_web}}
<!-- <li>{{tr "Speaker configuration" /}}</li> -->
{{/if}}
</ol>
{{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-identity">
{{include tmpl="#tmpl_settings-profiles" /}}
</div>
<div class="step step-speaker">
<h1>TODO</h1>
</div>
<div class="step step-finish">
<div class="logo">
<img src="img/teaspeak_cup_animated.png">
</div>
<div class="text">
<h1>{{tr "Congratulations,<br>your done setting up TeaSpeak" /}}</h1>
{{tr "You're done setting up your client. But dont worry,<br>" /}}
{{tr "your could find all these settings within the settings menu<br>" /}}
{{tr 'To open the client settings click on "tools" and than "settings"<br>' /}}
</div>
</div>
</div>
</div>
<div class="buttons">
<button class="btn btn-red button-last-step">error: last step</button>
<button class="btn btn-green button-next-step">error: next step</button>
</div>
</div>
</script>
</body>
</html>

View File

@ -0,0 +1 @@
<svg id="Flat" height="512" viewBox="0 0 512 512" width="512" xmlns="http://www.w3.org/2000/svg"><g fill="#8690fa"><circle cx="48" cy="304" r="24"/><circle cx="192" cy="448" r="24"/><circle cx="101" cy="395" r="24"/><circle cx="48" cy="304" r="24"/><path d="m216 448a24 24 0 1 0 -24 24 24 24 0 0 0 24-24"/><circle cx="101" cy="395" r="24"/><circle cx="48" cy="192" r="24"/><circle cx="192" cy="48" r="24"/><circle cx="101" cy="101" r="24"/><circle cx="48" cy="192" r="24"/><path d="m216 48a24 24 0 1 1 -24-24 24 24 0 0 1 24 24"/><circle cx="101" cy="101" r="24"/><path d="m311.992 472a24 24 0 0 1 -6.433-47.123 185.506 185.506 0 0 0 96.328-64.917 181.561 181.561 0 0 0 38.113-111.96c0-81.5-55.349-154.248-134.6-176.922a24 24 0 0 1 13.2-46.147 236.543 236.543 0 0 1 121 82.086 230.506 230.506 0 0 1 .276 282.283 233.819 233.819 0 0 1 -121.421 81.812 24.04 24.04 0 0 1 -6.463.888z"/></g><path d="m456.029 488a24.512 24.512 0 0 1 -2.679-.148l-144-16a24 24 0 0 1 -20.474-30.278l40-144a24 24 0 1 1 46.248 12.848l-32.454 116.838 115.98 12.886a24 24 0 0 1 -2.621 47.854z" fill="#5153ff"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -15,10 +15,17 @@ namespace events {
}
export class Registry<Events> {
private handler: {[key: string]:((event) => void)[]} = {};
private readonly registry_uuid;
private handler: {[key: string]: ((event) => void)[]} = {};
private connections: {[key: string]:Registry<string>[]} = {};
private debug_prefix = undefined;
constructor() {
this.registry_uuid = "evreg_data_" + guid();
}
enable_debug(prefix: string) { this.debug_prefix = prefix || "---"; }
disable_debug() { this.debug_prefix = undefined; }
@ -28,12 +35,30 @@ namespace events {
if(!Array.isArray(events))
events = [events];
handler[this.registry_uuid] = {
singleshot: false
};
for(const event of events) {
const handlers = this.handler[event] || (this.handler[event] = []);
handlers.push(handler);
}
}
/* one */
one<T extends keyof Events>(event: T, handler: (event?: Events[T] & Event<T> & EventConvert<Events>) => void);
one(events: (keyof Events)[], handler: (event?: Event<keyof Events> & EventConvert<Events>) => void);
one(events, handler) {
if(!Array.isArray(events))
events = [events];
for(const event of events) {
const handlers = this.handler[event] || (this.handler[event] = []);
handler[this.registry_uuid] = { singleshot: true };
handlers.push(handler);
}
}
off<T extends keyof Events>(handler: (event?: Event<T>) => void);
off<T extends keyof Events>(event: T, handler: (event?: Event<T> & EventConvert<Events>) => void);
off(event: (keyof Events)[], handler: (event?: Event<keyof Events> & EventConvert<Events>) => void);
@ -73,12 +98,22 @@ namespace events {
as: function () { return this; }
});
for(const handler of (this.handler[event_type as string] || []))
for(const handler of (this.handler[event_type as string] || [])) {
handler(event);
const reg_data = handler[this.registry_uuid];
if(typeof reg_data === "object" && reg_data.singleshot)
this.handler[event_type as string].remove(handler);
}
for(const evhandler of (this.connections[event_type as string] || []))
evhandler.fire(event_type as any, event as any);
}
fire_async<T extends keyof Events>(event_type: T, data?: Events[T]) {
setTimeout(() => this.fire(event_type, data));
}
destory() {
this.handler = {};
}
@ -312,6 +347,252 @@ namespace events {
error_msg?: string
}
}
export interface newcomer {
"show_step": {
"step": "welcome" | "microphone" | "identity" | "finish"
},
"exit_guide": {
ask_yesno: boolean
},
"modal-shown": {},
"step-status": {
next_button: boolean,
previous_button: boolean
}
}
export namespace settings {
export type ProfileInfo = {
id: string,
name: string,
nickname: string,
identity_type: "teaforo" | "teamspeak" | "nickname",
identity_forum?: {
valid: boolean,
fallback_name: string
},
identity_nickname?: {
name: string,
fallback_name: string
},
identity_teamspeak?: {
unique_id: string,
fallback_name: string
}
}
export interface profiles {
"reload-profile": { profile_id?: string },
"select-profile": { profile_id: string },
"query-profile-list": { },
"query-profile-list-result": {
status: "error" | "success" | "timeout",
error?: string;
profiles?: ProfileInfo[]
}
"query-profile": { profile_id: string },
"query-profile-result": {
status: "error" | "success" | "timeout",
profile_id: string,
error?: string;
info?: ProfileInfo
},
"select-identity-type": {
profile_id: string,
identity_type: "teamspeak" | "teaforo" | "nickname" | "unset"
},
"query-profile-validity": { profile_id: string },
"query-profile-validity-result": {
profile_id: string,
status: "error" | "success" | "timeout",
error?: string,
valid?: boolean
}
"create-profile": { name: string },
"create-profile-result": {
status: "error" | "success" | "timeout",
name: string;
profile_id?: string;
error?: string;
},
"delete-profile": { profile_id: string },
"delete-profile-result": {
status: "error" | "success" | "timeout",
profile_id: string,
error?: string
}
"set-default-profile": { profile_id: string },
"set-default-profile-result": {
status: "error" | "success" | "timeout",
/* the profile which now has the id "default" */
old_profile_id: string,
/* the "default" profile which now has a new id */
new_profile_id?: string
error?: string;
}
/* profile name events */
"set-profile-name": {
profile_id: string,
name: string
},
"set-profile-name-result": {
status: "error" | "success" | "timeout",
profile_id: string,
name?: string
},
/* profile nickname events */
"set-default-name": {
profile_id: string,
name: string | null
},
"set-default-name-result": {
status: "error" | "success" | "timeout",
profile_id: string,
name?: string | null
},
"query-identity-teamspeak": { profile_id: string },
"query-identity-teamspeak-result": {
status: "error" | "success" | "timeout",
profile_id: string,
error?: string,
level?: number
}
"set-identity-name-name": { profile_id: string, name: string },
"set-identity-name-name-result": {
status: "error" | "success" | "timeout",
profile_id: string,
error?: string,
name?: string
},
"generate-identity-teamspeak": { profile_id: string },
"generate-identity-teamspeak-result": {
profile_id: string,
status: "error" | "success" | "timeout",
error?: string,
level?: number
unique_id?: string
},
"improve-identity-teamspeak-level": { profile_id: string },
"improve-identity-teamspeak-level-update": {
profile_id: string,
new_level: number
},
"import-identity-teamspeak": { profile_id: string },
"import-identity-teamspeak-result": {
profile_id: string,
level?: number
unique_id?: string
}
"export-identity-teamspeak": {
profile_id: string,
filename: string
},
"setup-forum-connection": {}
}
export type MicrophoneSettings = "volume" | "vad-type" | "ppt-key" | "ppt-release-delay" | "ppt-release-delay-active" | "threshold-threshold";
export interface microphone {
"query-devices": { refresh_list: boolean },
"query-device-result": {
status: "success" | "error" | "timeout",
error?: string,
devices?: {
id: string,
name: string,
driver: string
}[]
active_device?: string;
},
"query-settings": {},
"query-settings-result": {
status: "success" | "error" | "timeout",
error?: string,
info?: {
volume: number,
vad_type: string,
vad_ppt: {
key: ppt.KeyDescriptor,
release_delay: number,
release_delay_active: boolean
},
vad_threshold: {
threshold: number
}
}
},
"set-device": { device_id: string },
"set-device-result": {
device_id: string,
status: "success" | "error" | "timeout",
error?: string
},
"set-setting": {
setting: MicrophoneSettings;
value: any;
},
"set-setting-result": {
setting: MicrophoneSettings,
status: "success" | "error" | "timeout",
error?: string,
value?: any
},
"update-device-level": {
devices: {
device_id: string,
status: "success" | "error",
level?: number,
error?: string
}[]
},
"audio-initialized": {},
"deinitialize": {}
}
}
}
}

View File

@ -477,6 +477,13 @@ function main() {
modal.open();
}
*/
/* for testing */
if(settings.static_global(Settings.KEY_USER_IS_NEW)) {
const modal = Modals.openModalNewcomer();
modal.close_listener.push(() => settings.changeGlobal(Settings.KEY_USER_IS_NEW, false));
}
}
const task_teaweb_starter: loader.Task = {

View File

@ -1,5 +1,4 @@
namespace profiles {
export class ConnectionProfile {
id: string;
@ -8,14 +7,14 @@ namespace profiles {
default_password: string;
selected_identity_type: string = "unset";
identities: {[key:string]:identities.Identity} = {};
identities: { [key: string]: identities.Identity } = {};
constructor(id: string) {
this.id = id;
}
connect_username() : string {
if(this.default_username && this.default_username !== "Another TeaSpeak user")
connect_username(): string {
if (this.default_username && this.default_username !== "Another TeaSpeak user")
return this.default_username;
let selected = this.selected_identity();
@ -23,41 +22,41 @@ namespace profiles {
return name || "Another TeaSpeak user";
}
selected_identity(current_type?: identities.IdentitifyType) : identities.Identity {
if(!current_type)
selected_identity(current_type?: identities.IdentitifyType): identities.Identity {
if (!current_type)
current_type = this.selected_type();
if(current_type === undefined)
if (current_type === undefined)
return undefined;
if(current_type == identities.IdentitifyType.TEAFORO) {
if (current_type == identities.IdentitifyType.TEAFORO) {
return identities.static_forum_identity();
} else if(current_type == identities.IdentitifyType.TEAMSPEAK || current_type == identities.IdentitifyType.NICKNAME) {
return this.identities[this.selected_identity_type.toLowerCase()];
} else if (current_type == identities.IdentitifyType.TEAMSPEAK || current_type == identities.IdentitifyType.NICKNAME) {
return this.identities[identities.IdentitifyType[current_type].toLowerCase()];
}
return undefined;
}
selected_type?() : identities.IdentitifyType {
return identities.IdentitifyType[this.selected_identity_type.toUpperCase()];
selected_type?(): identities.IdentitifyType {
return this.selected_identity_type ? identities.IdentitifyType[this.selected_identity_type.toUpperCase()] : undefined;
}
set_identity(type: identities.IdentitifyType, identity: identities.Identity) {
this.identities[identities.IdentitifyType[type].toLowerCase()] = identity;
}
spawn_identity_handshake_handler?(connection: connection.AbstractServerConnection) : connection.HandshakeIdentityHandler {
spawn_identity_handshake_handler?(connection: connection.AbstractServerConnection): connection.HandshakeIdentityHandler {
const identity = this.selected_identity();
if(!identity)
if (!identity)
return undefined;
return identity.spawn_identity_handshake_handler(connection);
}
encode?() : string {
encode?(): string {
const identity_data = {};
for(const key in this.identities)
if(this.identities[key])
for (const key in this.identities)
if (this.identities[key])
identity_data[key] = this.identities[key].encode();
return JSON.stringify({
@ -71,17 +70,17 @@ namespace profiles {
});
}
valid() : boolean {
valid(): boolean {
const identity = this.selected_identity();
if(!identity || !identity.valid()) return false;
if (!identity || !identity.valid()) return false;
return true;
}
}
async function decode_profile(data) : Promise<ConnectionProfile | string> {
async function decode_profile(data): Promise<ConnectionProfile | string> {
data = JSON.parse(data);
if(data.version !== 1)
if (data.version !== 1)
return "invalid version";
const result: ConnectionProfile = new ConnectionProfile(data.id);
@ -90,14 +89,14 @@ namespace profiles {
result.profile_name = data.profile_name;
result.selected_identity_type = (data.identity_type || "").toLowerCase();
if(data.identity_data) {
for(const key in data.identity_data) {
if (data.identity_data) {
for (const key in data.identity_data) {
const type = identities.IdentitifyType[key.toUpperCase() as string];
const _data = data.identity_data[key];
if(type == undefined) continue;
if (type == undefined) continue;
const identity = await identities.decode_identity(type, _data);
if(identity == undefined) continue;
if (identity == undefined) continue;
result.identities[key.toLowerCase()] = identity;
}
@ -112,6 +111,7 @@ namespace profiles {
}
let available_profiles: ConnectionProfile[] = [];
export async function load() {
available_profiles = [];
@ -119,7 +119,7 @@ namespace profiles {
let profiles_data: ProfilesData = (() => {
try {
return profiles_json ? JSON.parse(profiles_json) : {version: 0} as any;
} catch(error) {
} catch (error) {
debugger;
console.error(tr("Invalid profile json! Resetting profiles :( (%o)"), profiles_json);
createErrorModal(tr("Profile data invalid"), MessageHelper.formatMessage(tr("The profile data is invalid.{:br:}This might cause data loss."))).open();
@ -127,16 +127,16 @@ namespace profiles {
}
})();
if(profiles_data.version === 0) {
if (profiles_data.version === 0) {
profiles_data = {
version: 1,
profiles: []
};
}
if(profiles_data.version == 1) {
for(const profile_data of profiles_data.profiles) {
if (profiles_data.version == 1) {
for (const profile_data of profiles_data.profiles) {
const profile = await decode_profile(profile_data);
if(typeof(profile) === 'string') {
if (typeof (profile) === 'string') {
console.error(tr("Failed to load profile. Reason: %s, Profile data: %s"), profile, profiles_data);
continue;
}
@ -144,9 +144,9 @@ namespace profiles {
}
}
if(!find_profile("default")) { //Create a default profile and teaforo profile
if (!find_profile("default")) { //Create a default profile and teaforo profile
{
const profile = create_new_profile("default","default");
const profile = create_new_profile("default", "default");
profile.default_password = "";
profile.default_username = "";
profile.profile_name = "Default Profile";
@ -161,13 +161,13 @@ namespace profiles {
await identity.improve_level(8, 1, () => active);
profile.set_identity(identities.IdentitifyType.TEAMSPEAK, identity);
profile.selected_identity_type = identities.IdentitifyType[identities.IdentitifyType.TEAMSPEAK];
} catch(error) {
} catch (error) {
createErrorModal(tr("Failed to generate default identity"), tr("Failed to generate default identity!<br>Please manually generate the identity within your settings => profiles")).open();
}
}
{ /* forum identity (works only when connected to the forum) */
const profile = create_new_profile("TeaSpeak Forum","teaforo");
const profile = create_new_profile("TeaSpeak Forum", "teaforo");
profile.default_password = "";
profile.default_username = "";
profile.profile_name = "TeaSpeak Forum profile";
@ -180,7 +180,7 @@ namespace profiles {
}
}
export function create_new_profile(name: string, id?: string) : ConnectionProfile {
export function create_new_profile(name: string, id?: string): ConnectionProfile {
const profile = new ConnectionProfile(id || guid());
profile.profile_name = name;
profile.default_username = "";
@ -189,9 +189,10 @@ namespace profiles {
}
let _requires_save = false;
export function save() {
const profiles: string[] = [];
for(const profile of available_profiles)
for (const profile of available_profiles)
profiles.push(profile.encode());
const data = JSON.stringify({
@ -205,42 +206,43 @@ namespace profiles {
_requires_save = true;
}
export function requires_save() : boolean {
export function requires_save(): boolean {
return _requires_save;
}
export function profiles() : ConnectionProfile[] {
export function profiles(): ConnectionProfile[] {
return available_profiles;
}
export function find_profile(id: string) : ConnectionProfile | undefined {
for(const profile of profiles())
if(profile.id == id)
export function find_profile(id: string): ConnectionProfile | undefined {
for (const profile of profiles())
if (profile.id == id)
return profile;
return undefined;
}
export function find_profile_by_name(name: string) : ConnectionProfile | undefined {
export function find_profile_by_name(name: string): ConnectionProfile | undefined {
name = name.toLowerCase();
for(const profile of profiles())
if((profile.profile_name || "").toLowerCase() == name)
for (const profile of profiles())
if ((profile.profile_name || "").toLowerCase() == name)
return profile;
return undefined;
}
export function default_profile() : ConnectionProfile {
export function default_profile(): ConnectionProfile {
return find_profile("default");
}
export function set_default_profile(profile: ConnectionProfile) {
const old_default = default_profile();
if(old_default && old_default != profile) {
if (old_default && old_default != profile) {
old_default.id = guid();
}
profile.id = "default";
return old_default;
}
export function delete_profile(profile: ConnectionProfile) {

View File

@ -107,6 +107,8 @@ namespace profiles.identities {
export function update_forum() {
if(forum.logged_in() && (!static_identity || static_identity.data() !== forum.data())) {
static_identity = new TeaForumIdentity(forum.data());
} else {
static_identity = undefined;
}
}

View File

@ -140,6 +140,11 @@ class StaticSettings extends SettingsBase {
}
class Settings extends StaticSettings {
static readonly KEY_USER_IS_NEW: SettingsKey<boolean> = {
key: 'user_is_new_user',
default_value: true
};
static readonly KEY_DISABLE_COSMETIC_SLOWDOWN: SettingsKey<boolean> = {
key: 'disable_cosmetic_slowdown',
description: 'Disable the cosmetic slowdows in some processes, like icon upload.'

View File

@ -3,19 +3,432 @@
/// <reference path="../../proto.ts" />
namespace Modals {
export function openModalNewcomer() {
const next_step: {[key: string]:string} = {
"welcome": "microphone",
//"microphone": app.is_web() ? "identity" : "speaker", /* speaker setup only for the native client! */
"microphone": "identity",
"speaker": "identity",
"identity": "finish"
};
const last_step: {[key: string]:string} = (() => {
const result = {};
for(const key of Object.keys(next_step))
if(!result[next_step[key]])
result[next_step[key]] = key;
return result;
})();
export function openModalNewcomer() : Modal {
let modal = createModal({
header: tra("Welcome to the {}", app.is_web() ? "TeaWeb-Client" : "TeaSpeak-Client"),
body: () => $("#tmpl_newcomer").renderTag().children(),
header: tra("Welcome to the {}", app.is_web() ? "TeaSpeak - Web client" : "TeaSpeak - Client"),
body: () => $("#tmpl_newcomer").renderTag({
is_web: app.is_web()
}).children(),
footer: null,
width: "",
closeable: false
});
//TODO!
const event_registry = new events.Registry<events.modal.newcomer>();
event_registry.enable_debug("newcomer");
modal.htmlTag.find(".modal-body").addClass("modal-newcomer");
initializeBasicFunctionality(modal.htmlTag, event_registry);
initializeStepWelcome(modal.htmlTag.find(".container-body .step.step-welcome"), event_registry);
initializeStepIdentity(modal.htmlTag.find(".container-body .step.step-identity"), event_registry);
initializeStepMicrophone(modal.htmlTag.find(".container-body .step.step-microphone"), event_registry, modal);
initializeStepFinish(modal.htmlTag.find(".container-body .step.step-finish"), event_registry);
event_registry.on("exit_guide", event => {
if(event.ask_yesno)
Modals.spawnYesNo(tr("Are you sure?"), tr("Do you really want to skip the basic setup guide?"), result => {
if(result)
event_registry.fire("exit_guide", {ask_yesno: false});
});
else
modal.close();
});
event_registry.fire("show_step", {step: "welcome"});
modal.open();
event_registry.fire_async("modal-shown");
return modal;
}
function initializeBasicFunctionality(tag: JQuery, event_registry: events.Registry<events.modal.newcomer>) {
const container_header = tag.find(".container-header");
const tag_body = tag.find(".container-body .body");
/* step navigation */
event_registry.on("show_step", event => {
tag_body.find(".step").addClass("hidden");
container_header.find(".step").addClass("hidden");
tag_body.find(".step.step-" + event.step).removeClass("hidden");
container_header.find(".step.step-" + event.step).removeClass("hidden");
});
/* button controller */
{
const buttons = tag.find(".buttons");
const button_last_step = buttons.find(".button-last-step");
const button_next_step = buttons.find(".button-next-step");
button_last_step.on('click', event => {
if(last_step[current_step])
event_registry.fire("show_step", { step: last_step[current_step] as any });
else
event_registry.fire("exit_guide", {ask_yesno: true});
});
let current_step;
button_next_step.on('click', event => {
if(next_step[current_step])
event_registry.fire("show_step", { step: next_step[current_step] as any });
else
event_registry.fire("exit_guide", {ask_yesno: false});
});
event_registry.on("show_step", event => {
current_step = event.step;
button_next_step.text(next_step[current_step] ? tr("Next step") : tr("Finish guide"));
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("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));
}
}
function initializeStepWelcome(tag: JQuery, event_registry: events.Registry<events.modal.newcomer>) {
event_registry.on("show_step", e => {
if(e.step !== "welcome") return;
event_registry.fire_async("step-status", { next_button: true, previous_button: true });
});
}
function initializeStepFinish(tag: JQuery, event_registry: events.Registry<events.modal.newcomer>) {
event_registry.on("show_step", e => {
if(e.step !== "finish") return;
event_registry.fire_async("step-status", {next_button: true, previous_button: true });
});
}
function initializeStepIdentity(tag: JQuery, event_registry: events.Registry<events.modal.newcomer>) {
const profile_events = new events.Registry<events.modal.settings.profiles>();
profile_events.enable_debug("settings-identity");
modal_settings.initialize_identity_profiles_controller(profile_events);
modal_settings.initialize_identity_profiles_view(tag, profile_events, { forum_setuppable: false });
let step_shown = false;
let help_animation_done = false;
const profiles_valid = () => profiles.profiles().findIndex(e => e.valid()) !== -1;
const update_step_status = () => {
event_registry.fire_async("step-status", { next_button: help_animation_done && profiles_valid(), previous_button: help_animation_done });
};
profile_events.on("query-profile-validity-result", event => step_shown && event.status === "success" && event.valid && update_step_status());
event_registry.on("show_step", e => {
step_shown = e.step === "identity";
if(!step_shown) return;
update_step_status();
});
/* the help sequence */
{
const container = tag.find(".container-settings-identity-profile");
const container_help_text = tag.find(".container-help-text");
const container_profile_list = tag.find(".highlight-profile-list");
const container_profile_settings = tag.find(".highlight-profile-settings");
const container_identity_settings = tag.find(".highlight-identity-settings");
let is_first_show = true;
event_registry.on("show_step", event => {
if(!is_first_show || event.step !== "identity") return;
is_first_show = false;
container.addClass("help-shown");
const text = tr(
"After you've successfully set upped your microphone,\n" +
"lets setup some profiles and identities!\n" +
"\n" +
"Connect profiles determine, how your're authenticating yourself with the server.\n" +
"So basically they're your identity.\n" +
"In the following I'll guid you thru the options and GUI elements.\n" +
"\n" +
"To continue click anywhere on the screen."
);
set_help_text(text);
$("body").one('mousedown', event => show_profile_list_help());
});
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_profile_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(
"You could have as many connect profiles as you want.\n" +
"All created profiles will be listed here.\n" +
"\n" +
"To create a new profile just simply click the blue button \"Create profile\" and enter a profile name.\n" +
"If you want to delete a profile you've to select that profile and click the delete button.\n" +
"\n" +
"By default we're using the \"default\" profile\n" +
"to connect to any server. o change the default profile\n" +
"just select the new profile and press the \"select as default\" button.\n" +
"\n" +
"To continue click anywhere on the screen."
);
set_help_text(text);
$("body").one('mousedown', event => show_profile_settings_help());
};
const show_profile_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"
});
};
set_help_text(tr(
"In the upper left, you'll find the profile settings for the selected profile.\n" +
"You could give each profile an individual name. You could also specify the default connect nickname here.\n" +
"\n" +
"The last option \"Identity Type\" determines on what your identity is based on.\n" +
"TeaSpeak has two possibilities to identify yourself:\n" +
"1. Identify yourself by your TeaSpeak forum account\n" +
"2. Identify by an own generated cryptographic identity\n" +
"The second methods is also known as a TeamSpeak 3 identity.\n" +
"\n" +
"To continue click anywhere on the screen."
));
update_position();
container_help_text.off('resize').on('resize', update_position);
$("body").one('mousedown', event => show_identity_settings_help());
};
const show_identity_settings_help = () => {
container.find(".highlighted").removeClass("highlighted");
container_identity_settings.addClass("highlighted");
const update_position = () => {
const font_size = parseFloat(getComputedStyle(container_help_text[0]).fontSize);
const container_identity_offset = container_identity_settings.offset();
const right = container_profile_settings.outerWidth() + font_size * 2;
container_help_text.css({
top: container_identity_offset.top - container.offset().top,
left: "1em",
right: right + "px",
bottom: "1em"
});
};
set_help_text(tr(
"When selecting an identify type, some corresponding will pop up in the highlighted area.\n" +
"\n" +
"But don't worry, we've already generated\n" +
"a cryptographic identity for you!\n" +
"So you don't have to change anything before you start."
));
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();
};
}
}
function initializeStepMicrophone(tag: JQuery, event_registry: events.Registry<events.modal.newcomer>, modal: Modal) {
const microphone_events = new events.Registry<events.modal.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 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;
update_step_status();
});
/* the help sequence */
{
const container = tag.find(".container-settings-audio-microphone");
const container_help_text = tag.find(".container-help-text");
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(
"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());
});
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(
"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(
"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(
"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(
"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();
};
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,7 @@ namespace permissions {
PermissionType.B_SERVERQUERY_LOGIN,
//Not sensefull to assign virtual server permission to channel groups
...Object.keys(PermissionType).filter(e => e.toLowerCase().startsWith("b_virtualserver")).map(e => PermissionType[e]),
...Object.keys(PermissionType).filter(e => e.toLowerCase().startsWith("b_virtualserver") && e.toLowerCase() === "b_virtualserver_channel_permission_list").map(e => PermissionType[e]),
//Not sensefull to require some playlist permissions
...Object.keys(PermissionType).filter(e => e.toLowerCase().startsWith("i_playlist")).map(e => PermissionType[e]),

View File

@ -197,13 +197,16 @@ class RecorderProfile {
}
get_vad_type() { return this.config.vad_type; }
set_vad_type(type: VadType) {
set_vad_type(type: VadType) : boolean {
if(this.config.vad_type === type)
return;
return true;
if(["push_to_talk", "threshold", "active"].findIndex(e => e === type) == -1)
return false;
this.config.vad_type = type;
this.reinitialize_filter();
this.save();
return true;
}
get_vad_threshold() { return parseInt(this.config.vad_threshold.threshold as any); } /* for some reason it might be a string... */

View File

@ -187,6 +187,7 @@ const loader_javascript = {
"js/ui/modal/ModalBookmarks.js",
"js/ui/modal/ModalConnect.js",
"js/ui/modal/ModalSettings.js",
"js/ui/modal/ModalNewcomer.js",
"js/ui/modal/ModalCreateChannel.js",
"js/ui/modal/ModalServerEdit.js",
"js/ui/modal/ModalChangeVolume.js",
@ -369,6 +370,7 @@ const loader_style = {
"css/static/modal-musicmanage.css",
"css/static/modal-serverinfobandwidth.css",
"css/static/modal-identity.css",
"css/static/modal-newcomer.css",
"css/static/modal-settings.css",
"css/static/modal-poke.css",
"css/static/modal-server.css",
@ -483,7 +485,8 @@ loader.register_task(loader.Stage.TEMPLATES, {
function: async () => {
await loader.load_templates([
"templates.html",
"templates/music/manage.html"
"templates/modal/musicmanage.html",
"templates/modal/newcomer.html",
]);
},
priority: 10