Added a lot of new things
parent
bfb7304606
commit
d9c0fa37f7
|
@ -6,7 +6,8 @@
|
|||
"directories": {},
|
||||
"scripts": {
|
||||
"compile-sass": "sass --update .:.",
|
||||
"build-worker": "tsc -p shared/js/workers/tsconfig_worker_codec.json",
|
||||
"build-worker-codec": "tsc -p shared/js/workers/tsconfig_worker_codec.json",
|
||||
"build-worker-pow": "tsc -p shared/js/workers/tsconfig_worker_pow.json",
|
||||
"dtsgen": "node tools/dtsgen/index.js",
|
||||
"trgen": "node tools/trgen/index.js",
|
||||
"ttsc": "ttsc",
|
||||
|
|
|
@ -28,9 +28,14 @@ if [[ $? -ne 0 ]]; then
|
|||
fi
|
||||
|
||||
echo "Generating web workers"
|
||||
npm run build-worker
|
||||
npm run build-worker-codec
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "Failed to build web workers"
|
||||
echo "Failed to build web worker codec"
|
||||
exit 1
|
||||
fi
|
||||
npm run build-worker-pow
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "Failed to build web worker pow"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
# Setup the develop environment on windows
|
||||
## 1.0 Requirements
|
||||
The following tools or applications are required to develop the web client:
|
||||
- [1.1 IDE](#11-ide)
|
||||
- [1.2 XAMPP (apache & php)](#12-xampp-with-a-web-server-and-php)
|
||||
- [1.3 NPM](#13-npm)
|
||||
- [1.4 Git bash](#14-git-bash)
|
||||
|
||||
### 1.1 IDE
|
||||
For developing TeaWeb you require and IDE.
|
||||
Preferable is PHPStorm from Jetbrains because the've already build in compiling on changes.
|
||||
Else you've to run the compiler to compile the TS or SCSS files to js e.g. css files.
|
||||
|
||||
### 1.2 XAMPP with a web server and PHP
|
||||
You require PHP (grater than 5) to setup and manage the project files.
|
||||
PHP is required for the index page as well.
|
||||
The web server is required for browsing your final environment and watch your result.
|
||||
The final environment will be found at `web/environemnt/development/`. More information about
|
||||
the file structure could be found [here (TODO: link me!)]().
|
||||
|
||||
### 1.3 NPM
|
||||
NPM min 6.X is required to develop this project.
|
||||
With NPM you could easily download all required dependencies by just typing `npm install`.
|
||||
IMPORTANT: NPM must be available within the PATH environment variable!
|
||||
|
||||
### 1.4 Git bash
|
||||
For using the `.sh` build scripts you require Git Bash.
|
||||
A minimum of 4.2 is recommend, but in general every version should work.
|
||||
|
||||
## 2.0 Development environment setup
|
||||
### 2.1 Native code (codecs) (Not required)
|
||||
If you dont want to develop the codec part or something related to the native
|
||||
webassembly part of TeaWeb you could skip this step and follow the steps in [2.1-b](#21-b-skip-native-code-setup)
|
||||
|
||||
### 2.1-b Skip native code setup
|
|
@ -37,6 +37,7 @@
|
|||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
cursor: pointer;
|
||||
margin-left: 0;
|
||||
|
||||
.server_type {
|
||||
|
@ -193,7 +194,15 @@
|
|||
}
|
||||
|
||||
/* all icons related to basic_icons */
|
||||
.clicon {width:16px;height:16px;background:url('../../img/ts/basic_icons.png') no-repeat;background-size: 16px 608px;}
|
||||
.clicon {
|
||||
width:16px;
|
||||
height:16px;
|
||||
background:url('../../img/ts/basic_icons.png') no-repeat;
|
||||
background-size: 16px 608px;
|
||||
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.host {background-position: 0 -448px}
|
||||
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
.select_info_table { }
|
||||
.select_info_table tr { }
|
||||
.select_info_table tr td { }
|
||||
|
||||
.select_info_table tr td:nth-child(1) {
|
||||
.select_info_table tr td {
|
||||
&:nth-child(1) {
|
||||
font-weight: bold;
|
||||
padding-right: 5px;
|
||||
min-width: 20%;
|
||||
min-width: max(35%, 20px);
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
min-width: max(75%, 40px);
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
.select_server {
|
||||
|
@ -17,21 +22,18 @@
|
|||
|
||||
.button-update {
|
||||
width: 100%;
|
||||
height: 23px;
|
||||
|
||||
&:disabled {
|
||||
color: red;
|
||||
pointer-events: none;
|
||||
}
|
||||
&:not(:disabled) {
|
||||
color: green;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
max-height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
|
||||
.hostbanner {
|
||||
overflow: hidden;
|
||||
|
@ -39,18 +41,20 @@
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
<div id="select_info" class="select_info" style="width: 100%; max-width: 100%">
|
||||
<div class="container-banner"></div>
|
||||
<div class="container-info"></div>
|
||||
</div>
|
||||
*/
|
||||
.select_info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
width: 100%;
|
||||
|
||||
> .close {
|
||||
z-index: 500;
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
> div {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
@ -228,8 +228,11 @@ footer .container {
|
|||
}
|
||||
|
||||
$separator_thickness: 4px;
|
||||
$small_device: 500px;
|
||||
|
||||
.app {
|
||||
.container-app-main {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
@ -303,6 +306,38 @@ $separator_thickness: 4px;
|
|||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $small_device) {
|
||||
.container-app-main {
|
||||
.container-info {
|
||||
display: none;
|
||||
position: absolute;
|
||||
|
||||
width: 100%!important; /* override the seperator property */
|
||||
height: 100%;
|
||||
|
||||
z-index: 1000;
|
||||
|
||||
&.shown {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.select_info {
|
||||
> .close {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container-channel-chat + .container-seperator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.container-channel-chat {
|
||||
width: 100%!important; /* override the seperator property */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container-seperator {
|
||||
background: lightgray;
|
||||
flex-grow: 0;
|
||||
|
@ -328,6 +363,7 @@ $separator_thickness: 4px;
|
|||
}
|
||||
|
||||
.icon-container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
|
@ -354,8 +390,6 @@ $separator_thickness: 4px;
|
|||
}
|
||||
|
||||
html, body {
|
||||
min-height: 500px;
|
||||
min-width: 500px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
width: 16px;
|
||||
height: 11px;
|
||||
background: url('../../../img/ts/country_icons.png'), url('../../img/ts/country_icons.png') no-repeat;
|
||||
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.country.flag-ad {
|
||||
|
|
|
@ -8,6 +8,9 @@
|
|||
width: 16px;
|
||||
height: 16px;
|
||||
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
|
||||
background: url('../../../img/client_icon_sprite.svg'), url('../../img/client_icon_sprite.svg') no-repeat;
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="TeaSpeak Web Client, connect to any TeaSpeak server without installing anything." />
|
||||
<link rel="icon" href="img/favicon/teacup.png">
|
||||
|
||||
<?php
|
||||
if(!$WEB_CLIENT) {
|
||||
|
@ -190,7 +191,7 @@
|
|||
<footer style="<?php echo $footer_style; ?>">
|
||||
<div class="container" style="display: flex; flex-direction: row; align-content: space-between;">
|
||||
<div style="align-self: center; position: fixed; left: 5px;">Open source on <a href="https://github.com/TeaSpeak/TeaSpeak-Web" style="display: inline-block; position: relative">github.com</a></div>
|
||||
<div style="align-self: center;">TeaSpeak Web client (<?php echo $version; ?>) by WolverinDEV</div>
|
||||
<div style="align-self: center;">TeaSpeak Web (<?php echo $version; ?>) by WolverinDEV</div>
|
||||
<div style="align-self: center; position: fixed; right: 5px;"><?php echo $footer_forum; ?></div>
|
||||
</div>
|
||||
</footer>
|
||||
|
|
|
@ -133,8 +133,11 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="container-seperator vertical" seperator-id="seperator-main-info"></div>
|
||||
<div class="main_container container-info">
|
||||
<div id="select_info" class="select_info" style="width: 100%; max-width: 100%">
|
||||
<div id="select_info" class="main_container container-info">
|
||||
<div class="select_info" style="width: 100%; max-width: 100%">
|
||||
<button type="button" class="close" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<div class="container-banner"></div>
|
||||
<!-- <div class="container-seperator horizontal" seperator-id="seperator-hostbanner-info"></div> -->
|
||||
<div class="container-select-info"></div>
|
||||
|
@ -1029,7 +1032,8 @@
|
|||
|
||||
<div class="group_box">
|
||||
<div class="header">{{tr "Microphone" /}}</div>
|
||||
<div class="content settings-microphone">
|
||||
<div class="content settings-microphone {{if !voice_available}}disabled{{/if}}">
|
||||
{{if voice_available}}
|
||||
<div class="form-row settings-device settings-device-microphone">
|
||||
<div class="form-group settings-device-select">
|
||||
<label for="select-settings-microphone-device" class="bmd-label-static">{{tr "Device:" /}}</label>
|
||||
|
@ -1088,6 +1092,9 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div>{{tr "Voice had been disabled" /}}</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="group_box">
|
||||
|
@ -2058,7 +2065,7 @@
|
|||
<tr>
|
||||
<td>{{tr "Name:" /}}</td>
|
||||
<td style="display: flex; flex-direction: row">
|
||||
<div style="margin-right: 3px" class="country flag-{{*:(data.property_client_country || 'xx').toLowerCase()}}"></div>
|
||||
<div style="margin-right: 3px" class="country flag-{{*:(data.property_client_country || 'xx').toLowerCase()}}" title="{{*:i18n.country_name(data.property_client_country || 'XX')}}"></div>
|
||||
<node key="client_name"/>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -2315,7 +2322,7 @@
|
|||
</table>
|
||||
</div>
|
||||
|
||||
<button class="button-update btn_update">{{tr "Update info"/}}</button>
|
||||
<button class="button-update btn btn-success">{{tr "Update info"/}}</button>
|
||||
</div>
|
||||
</script>
|
||||
<script class="jsrender-template" id="tmpl_selected_channel" type="text/html">
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
Before Width: | Height: | Size: 75 KiB |
|
@ -50,7 +50,7 @@ enum ViewReasonId {
|
|||
class TSClient {
|
||||
channelTree: ChannelTree;
|
||||
serverConnection: connection.ServerConnection;
|
||||
voiceConnection: VoiceConnection;
|
||||
voiceConnection: VoiceConnection | undefined;
|
||||
fileManager: FileManager;
|
||||
selectInfo: InfoBar;
|
||||
permissions: PermissionManager;
|
||||
|
@ -69,10 +69,12 @@ class TSClient {
|
|||
this.fileManager = new FileManager(this);
|
||||
this.permissions = new PermissionManager(this);
|
||||
this.groups = new GroupManager(this);
|
||||
this.voiceConnection = new VoiceConnection(this);
|
||||
this._ownEntry = new LocalClientEntry(this);
|
||||
this.controlBar = new ControlBar(this, $("#control_bar"));
|
||||
this.channelTree.registerClient(this._ownEntry);
|
||||
|
||||
if(!settings.static_global(Settings.KEY_DISABLE_VOICE, false))
|
||||
this.voiceConnection = new VoiceConnection(this);
|
||||
}
|
||||
|
||||
setup() {
|
||||
|
@ -140,7 +142,8 @@ class TSClient {
|
|||
if(this.groups.serverGroups.length == 0)
|
||||
this.groups.requestGroups();
|
||||
this.controlBar.updateProperties();
|
||||
if(!this.voiceConnection.current_encoding_supported())
|
||||
|
||||
if(this.voiceConnection && !this.voiceConnection.current_encoding_supported())
|
||||
createErrorModal(tr("Codec encode type not supported!"), tr("Codec encode type " + VoiceConnectionType[this.voiceConnection.type] + " not supported by this browser!<br>Choose another one!")).open(); //TODO tr
|
||||
}
|
||||
|
||||
|
@ -270,6 +273,7 @@ class TSClient {
|
|||
}
|
||||
|
||||
this.channelTree.reset();
|
||||
if(this.voiceConnection)
|
||||
this.voiceConnection.dropSession();
|
||||
if(this.serverConnection) this.serverConnection.disconnect();
|
||||
this.controlBar.update_connection_state();
|
||||
|
|
|
@ -81,8 +81,12 @@ namespace connection {
|
|||
|
||||
handleCommandServerInit(json){
|
||||
//We could setup the voice channel
|
||||
if( this.connection.client.voiceConnection) {
|
||||
console.log(tr("Setting up voice"));
|
||||
this.connection.client.voiceConnection.createSession();
|
||||
} else {
|
||||
console.log(tr("Skipping voice setup (No voice bridge available)"));
|
||||
}
|
||||
|
||||
|
||||
json = json[0]; //Only one bulk
|
||||
|
|
|
@ -28,12 +28,24 @@ namespace connection {
|
|||
abstract disconnect(reason?: string) : Promise<void>;
|
||||
|
||||
abstract support_voice() : boolean;
|
||||
abstract voice_connection() : AbstractVoiceConnection | undefined;
|
||||
abstract voice_connection() : voice.AbstractVoiceConnection | undefined;
|
||||
|
||||
abstract command_handler_boss() : AbstractCommandHandlerBoss;
|
||||
abstract send_command(command: string, data?: any | any[], options?: CommandOptions) : Promise<CommandResult>;
|
||||
}
|
||||
|
||||
export namespace voice {
|
||||
export interface VoiceClient {
|
||||
client_id: number;
|
||||
|
||||
callback_playback: () => any;
|
||||
callback_timeout: () => any;
|
||||
callback_stopped: () => any;
|
||||
|
||||
get_volume() : number;
|
||||
set_volume(volume: number) : Promise<void>;
|
||||
}
|
||||
|
||||
export abstract class AbstractVoiceConnection {
|
||||
readonly connection: AbstractServerConnection;
|
||||
|
||||
|
@ -42,6 +54,11 @@ namespace connection {
|
|||
}
|
||||
|
||||
abstract connected() : boolean;
|
||||
|
||||
abstract register_client(client_id: number) : VoiceClient;
|
||||
abstract availible_clients() : VoiceClient[];
|
||||
abstract unregister_client(client: VoiceClient) : Promise<void>;
|
||||
}
|
||||
}
|
||||
|
||||
export class ServerCommand {
|
||||
|
|
|
@ -202,7 +202,12 @@ namespace connection {
|
|||
arguments: json["data"]
|
||||
});
|
||||
group.end();
|
||||
} else if(json["type"] === "WebRTC") this.client.voiceConnection.handleControlPacket(json);
|
||||
} else if(json["type"] === "WebRTC") {
|
||||
if(this.client.voiceConnection)
|
||||
this.client.voiceConnection.handleControlPacket(json);
|
||||
else
|
||||
console.log(tr("Dropping WebRTC command packet, because we havent a bridge."))
|
||||
}
|
||||
else {
|
||||
console.log(tr("Unknown command type %o"), json["type"]);
|
||||
}
|
||||
|
@ -299,7 +304,7 @@ namespace connection {
|
|||
return false;
|
||||
}
|
||||
|
||||
voice_connection(): connection.AbstractVoiceConnection | undefined {
|
||||
voice_connection(): connection.voice.AbstractVoiceConnection | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
|
|
@ -52,8 +52,9 @@ interface ContextMenuEntry {
|
|||
name: (() => string) | string;
|
||||
icon?: (() => string) | string | JQuery;
|
||||
disabled?: boolean;
|
||||
invalidPermission?: boolean;
|
||||
visible?: boolean;
|
||||
|
||||
invalidPermission?: boolean;
|
||||
sub_menu?: ContextMenuEntry[];
|
||||
}
|
||||
|
||||
|
@ -96,8 +97,11 @@ function generate_tag(entry: ContextMenuEntry) : JQuery {
|
|||
if(entry.disabled || entry.invalidPermission) tag.addClass("disabled");
|
||||
else {
|
||||
let menu = $.spawn("div").addClass("sub-menu").addClass("context-menu");
|
||||
for(let e of entry.sub_menu)
|
||||
for(const e of entry.sub_menu) {
|
||||
if(typeof(entry.visible) === 'boolean' && !entry.visible)
|
||||
continue;
|
||||
menu.append(generate_tag(e));
|
||||
}
|
||||
menu.appendTo(tag);
|
||||
}
|
||||
return tag;
|
||||
|
@ -111,7 +115,10 @@ function spawn_context_menu(x, y, ...entries: ContextMenuEntry[]) {
|
|||
|
||||
contextMenuCloseFn = undefined;
|
||||
|
||||
for(let entry of entries){
|
||||
for(const entry of entries){
|
||||
if(typeof(entry.visible) === 'boolean' && !entry.visible)
|
||||
continue;
|
||||
|
||||
if(entry.type == MenuEntryType.CLOSE) {
|
||||
contextMenuCloseFn = entry.callback;
|
||||
} else
|
||||
|
|
|
@ -53,7 +53,7 @@ namespace loader {
|
|||
DONE
|
||||
}
|
||||
|
||||
export let allow_cached_files: boolean = false;
|
||||
export let cache_tag: string | undefined;
|
||||
let current_stage: Stage = Stage.INITIALIZING;
|
||||
const tasks: {[key:number]:Task[]} = {};
|
||||
|
||||
|
@ -206,7 +206,7 @@ namespace loader {
|
|||
|
||||
document.getElementById("scripts").appendChild(tag);
|
||||
|
||||
tag.src = path + (allow_cached_files ? "" : "?_ts=" + Date.now());
|
||||
tag.src = path + (cache_tag || "");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -315,7 +315,7 @@ namespace loader {
|
|||
};
|
||||
|
||||
document.getElementById("style").appendChild(tag);
|
||||
tag.href = path + (allow_cached_files ? "" : "?_ts=" + Date.now());
|
||||
tag.href = path + (cache_tag || "");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -708,12 +708,11 @@ async function check_updates() {
|
|||
|
||||
if(!app_version) {
|
||||
/* TODO add warning */
|
||||
loader.allow_cached_files = false;
|
||||
loader.cache_tag = "?_ts=" + Date.now();
|
||||
return;
|
||||
}
|
||||
const cached_version = localStorage.getItem("cached_version");
|
||||
if(!cached_version || cached_version != app_version) {
|
||||
loader.allow_cached_files = false;
|
||||
loader.register_task(loader.Stage.LOADED, {
|
||||
priority: 0,
|
||||
name: "cached version updater",
|
||||
|
@ -721,11 +720,8 @@ async function check_updates() {
|
|||
localStorage.setItem("cached_version", app_version);
|
||||
}
|
||||
});
|
||||
/* loading screen */
|
||||
return;
|
||||
}
|
||||
|
||||
loader.allow_cached_files = true;
|
||||
loader.cache_tag = "?_version=" + app_version;
|
||||
}
|
||||
|
||||
interface Window {
|
||||
|
|
|
@ -270,6 +270,7 @@ function main() {
|
|||
clearTimeout(_resize_timeout);
|
||||
_resize_timeout = setTimeout(() => {
|
||||
globalClient.channelTree.handle_resized();
|
||||
globalClient.selectInfo.handle_resize();
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
|
|
|
@ -78,6 +78,7 @@ class StaticSettings {
|
|||
class Settings extends StaticSettings {
|
||||
static readonly KEY_DISABLE_CONTEXT_MENU = "disableContextMenu";
|
||||
static readonly KEY_DISABLE_UNLOAD_DIALOG = "disableUnloadDialog";
|
||||
static readonly KEY_DISABLE_VOICE = "disableVoice";
|
||||
|
||||
private static readonly UPDATE_DIRECT: boolean = true;
|
||||
private cacheGlobal = {};
|
||||
|
|
|
@ -442,7 +442,21 @@ class ChannelEntry {
|
|||
flagDelete = this.channelTree.client.permissions.neededPermission(PermissionType.B_CHANNEL_DELETE_TEMPORARY).granted(1);
|
||||
}
|
||||
|
||||
let trigger_close = true;
|
||||
spawn_context_menu(x, y, {
|
||||
type: MenuEntryType.ENTRY,
|
||||
name: tr("Show channel info"),
|
||||
callback: () => {
|
||||
trigger_close = false;
|
||||
this.channelTree.client.selectInfo.open_popover()
|
||||
},
|
||||
icon: "client-about",
|
||||
visible: this.channelTree.client.selectInfo.is_popover()
|
||||
}, {
|
||||
type: MenuEntryType.HR,
|
||||
visible: this.channelTree.client.selectInfo.is_popover(),
|
||||
name: ''
|
||||
}, {
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-channel_switch",
|
||||
name: tr("<b>Switch to channel</b>"),
|
||||
|
@ -525,7 +539,7 @@ class ChannelEntry {
|
|||
invalidPermission: !channelCreate,
|
||||
callback: () => this.channelTree.spawnCreateChannel()
|
||||
},
|
||||
MenuEntry.CLOSE(on_close)
|
||||
MenuEntry.CLOSE(() => (trigger_close ? on_close : () => {})())
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -639,7 +653,10 @@ class ChannelEntry {
|
|||
}
|
||||
} else if(key == "channel_codec") {
|
||||
(this.properties.channel_codec == 5 || this.properties.channel_codec == 3 ? $.fn.show : $.fn.hide).apply(this.channelTag().find(".icons .icon_music"));
|
||||
(this.channelTree.client.voiceConnection.codecSupported(this.properties.channel_codec) ? $.fn.hide : $.fn.show).apply(this.channelTag().find(".icons .icon_no_sound"));
|
||||
this.channelTag().find(".icons .icon_no_sound").toggle(!(
|
||||
this.channelTree.client.voiceConnection &&
|
||||
this.channelTree.client.voiceConnection.codecSupported(this.properties.channel_codec)
|
||||
));
|
||||
} else if(key == "channel_flag_default") {
|
||||
(this.properties.channel_flag_default ? $.fn.show : $.fn.hide).apply(this.channelTag().find(".icons .icon_default"));
|
||||
} else if(key == "channel_flag_password")
|
||||
|
|
|
@ -266,28 +266,40 @@ class ClientEntry {
|
|||
}
|
||||
|
||||
showContextMenu(x: number, y: number, on_close: () => void = undefined) {
|
||||
const _this = this;
|
||||
|
||||
let trigger_close = true;
|
||||
spawn_context_menu(x, y,
|
||||
{
|
||||
type: MenuEntryType.ENTRY,
|
||||
name: tr("Show client info"),
|
||||
callback: () => {
|
||||
trigger_close = false;
|
||||
this.channelTree.client.selectInfo.open_popover()
|
||||
},
|
||||
icon: "client-about",
|
||||
visible: this.channelTree.client.selectInfo.is_popover()
|
||||
}, {
|
||||
type: MenuEntryType.HR,
|
||||
visible: this.channelTree.client.selectInfo.is_popover(),
|
||||
name: ''
|
||||
}, {
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-change_nickname",
|
||||
name: tr("<b>Open text chat</b>"),
|
||||
callback: function () {
|
||||
chat.activeChat = _this.chat(true);
|
||||
callback: () => {
|
||||
chat.activeChat = this.chat(true);
|
||||
chat.focus();
|
||||
}
|
||||
}, {
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-poke",
|
||||
name: tr("Poke client"),
|
||||
callback: function () {
|
||||
callback: () => {
|
||||
createInputModal(tr("Poke client"), tr("Poke message:<br>"), text => true, result => {
|
||||
if(typeof(result) === "string") {
|
||||
//TODO tr
|
||||
console.log("Poking client " + _this.clientNickName() + " with message " + result);
|
||||
_this.channelTree.client.serverConnection.send_command("clientpoke", {
|
||||
clid: _this.clientId(),
|
||||
console.log("Poking client " + this.clientNickName() + " with message " + result);
|
||||
this.channelTree.client.serverConnection.send_command("clientpoke", {
|
||||
clid: this.clientId(),
|
||||
msg: result
|
||||
});
|
||||
|
||||
|
@ -298,13 +310,13 @@ class ClientEntry {
|
|||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-edit",
|
||||
name: tr("Change description"),
|
||||
callback: function () {
|
||||
callback: () => {
|
||||
createInputModal(tr("Change client description"), tr("New description:<br>"), text => true, result => {
|
||||
if(typeof(result) === "string") {
|
||||
//TODO tr
|
||||
console.log("Changing " + _this.clientNickName() + "'s description to " + result);
|
||||
_this.channelTree.client.serverConnection.send_command("clientedit", {
|
||||
clid: _this.clientId(),
|
||||
console.log("Changing " + this.clientNickName() + "'s description to " + result);
|
||||
this.channelTree.client.serverConnection.send_command("clientedit", {
|
||||
clid: this.clientId(),
|
||||
client_description: result
|
||||
});
|
||||
|
||||
|
@ -332,9 +344,9 @@ class ClientEntry {
|
|||
createInputModal(tr("Kick client from channel"), tr("Kick reason:<br>"), text => true, result => {
|
||||
if(result) {
|
||||
//TODO tr
|
||||
console.log("Kicking client " + _this.clientNickName() + " from channel with reason " + result);
|
||||
_this.channelTree.client.serverConnection.send_command("clientkick", {
|
||||
clid: _this.clientId(),
|
||||
console.log("Kicking client " + this.clientNickName() + " from channel with reason " + result);
|
||||
this.channelTree.client.serverConnection.send_command("clientkick", {
|
||||
clid: this.clientId(),
|
||||
reasonid: ViewReasonId.VREASON_CHANNEL_KICK,
|
||||
reasonmsg: result
|
||||
});
|
||||
|
@ -350,9 +362,9 @@ class ClientEntry {
|
|||
createInputModal(tr("Kick client from server"), tr("Kick reason:<br>"), text => true, result => {
|
||||
if(result) {
|
||||
//TODO tr
|
||||
console.log("Kicking client " + _this.clientNickName() + " from server with reason " + result);
|
||||
_this.channelTree.client.serverConnection.send_command("clientkick", {
|
||||
clid: _this.clientId(),
|
||||
console.log("Kicking client " + this.clientNickName() + " from server with reason " + result);
|
||||
this.channelTree.client.serverConnection.send_command("clientkick", {
|
||||
clid: this.clientId(),
|
||||
reasonid: ViewReasonId.VREASON_SERVER_KICK,
|
||||
reasonmsg: result
|
||||
});
|
||||
|
@ -411,7 +423,7 @@ class ClientEntry {
|
|||
});
|
||||
}
|
||||
},
|
||||
MenuEntry.CLOSE(on_close)
|
||||
MenuEntry.CLOSE(() => (trigger_close ? on_close : () => {})())
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -908,8 +920,22 @@ class MusicClientEntry extends ClientEntry {
|
|||
}
|
||||
|
||||
showContextMenu(x: number, y: number, on_close: () => void = undefined): void {
|
||||
let trigger_close = true;
|
||||
spawn_context_menu(x, y,
|
||||
{
|
||||
type: MenuEntryType.ENTRY,
|
||||
name: tr("Show bot info"),
|
||||
callback: () => {
|
||||
trigger_close = false;
|
||||
this.channelTree.client.selectInfo.open_popover()
|
||||
},
|
||||
icon: "client-about",
|
||||
visible: this.channelTree.client.selectInfo.is_popover()
|
||||
}, {
|
||||
type: MenuEntryType.HR,
|
||||
visible: this.channelTree.client.selectInfo.is_popover(),
|
||||
name: ''
|
||||
}, {
|
||||
name: tr("<b>Change bot name</b>"),
|
||||
icon: "client-change_nickname",
|
||||
disabled: false,
|
||||
|
@ -1073,7 +1099,7 @@ class MusicClientEntry extends ClientEntry {
|
|||
},
|
||||
type: MenuEntryType.ENTRY
|
||||
},
|
||||
MenuEntry.CLOSE(on_close)
|
||||
MenuEntry.CLOSE(() => (trigger_close ? on_close : () => {})())
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -196,6 +196,7 @@ class ControlBar {
|
|||
|
||||
private updateMicrophoneRecordState() {
|
||||
let enabled = !this._muteInput && !this._muteOutput && !this._away;
|
||||
if(this.handle.voiceConnection)
|
||||
this.handle.voiceConnection.voiceRecorder.update(enabled);
|
||||
}
|
||||
|
||||
|
@ -212,12 +213,13 @@ class ControlBar {
|
|||
}
|
||||
|
||||
updateVoice(targetChannel?: ChannelEntry) {
|
||||
if(!targetChannel) targetChannel = this.handle.getClient().currentChannel();
|
||||
if(!targetChannel)
|
||||
targetChannel = this.handle.getClient().currentChannel();
|
||||
let client = this.handle.getClient();
|
||||
|
||||
this.codec_supported = targetChannel ? this.handle.voiceConnection.codecSupported(targetChannel.properties.channel_codec) : true;
|
||||
this.support_record = this.handle.voiceConnection.voice_send_support();
|
||||
this.support_playback = this.handle.voiceConnection.voice_playback_support();
|
||||
this.codec_supported = targetChannel ? this.handle.voiceConnection && this.handle.voiceConnection.codecSupported(targetChannel.properties.channel_codec) : true;
|
||||
this.support_record = this.handle.voiceConnection && this.handle.voiceConnection.voice_send_support();
|
||||
this.support_playback = this.handle.voiceConnection && this.handle.voiceConnection.voice_playback_support();
|
||||
|
||||
this.htmlTag.find(".btn_mute_input").prop("disabled", !this.codec_supported|| !this.support_playback || !this.support_record);
|
||||
this.htmlTag.find(".btn_mute_output").prop("disabled", !this.codec_supported || !this.support_playback);
|
||||
|
|
|
@ -53,7 +53,8 @@ class InfoBar<AvailableTypes = ServerEntry | ChannelEntry | ClientEntry | undefi
|
|||
readonly handle: TSClient;
|
||||
|
||||
private current_selected?: AvailableTypes;
|
||||
private _htmlTag: JQuery<HTMLElement>;
|
||||
private _tag: JQuery<HTMLElement>;
|
||||
private _tag_content: JQuery<HTMLElement>;
|
||||
private _tag_info: JQuery<HTMLElement>;
|
||||
private _tag_banner: JQuery<HTMLElement>;
|
||||
|
||||
|
@ -63,9 +64,10 @@ class InfoBar<AvailableTypes = ServerEntry | ChannelEntry | ClientEntry | undefi
|
|||
|
||||
constructor(client: TSClient, htmlTag: JQuery<HTMLElement>) {
|
||||
this.handle = client;
|
||||
this._htmlTag = htmlTag;
|
||||
this._tag_info = htmlTag.find(".container-select-info");
|
||||
this._tag_banner = htmlTag.find(".container-banner");
|
||||
this._tag = htmlTag;
|
||||
this._tag_content = htmlTag.find("> .select_info");
|
||||
this._tag_info = this._tag_content.find(".container-select-info");
|
||||
this._tag_banner = this._tag_content.find(".container-banner");
|
||||
|
||||
this.managers.push(new MusicInfoManager());
|
||||
this.managers.push(new ClientInfoManager());
|
||||
|
@ -73,10 +75,24 @@ class InfoBar<AvailableTypes = ServerEntry | ChannelEntry | ClientEntry | undefi
|
|||
this.managers.push(new ServerInfoManager());
|
||||
|
||||
this.banner_manager = new Hostbanner(client, this._tag_banner);
|
||||
|
||||
this._tag.find("button.close").on('click', () => {
|
||||
this._tag.toggleClass('shown', false);
|
||||
});
|
||||
}
|
||||
|
||||
handle_resize() {
|
||||
/* test if the popover isn't a popover anymore */
|
||||
if(this._tag.hasClass('shown')) {
|
||||
this._tag.removeClass('shown');
|
||||
if(this.is_popover())
|
||||
this._tag.addClass('shown');
|
||||
}
|
||||
}
|
||||
|
||||
setCurrentSelected(entry: AvailableTypes) {
|
||||
if(this.current_selected == entry) return;
|
||||
|
||||
if(this._current_manager) {
|
||||
(this._current_manager as InfoManager<AvailableTypes>).finalizeFrame(this.current_selected, this._tag_info);
|
||||
this._current_manager = null;
|
||||
|
@ -112,7 +128,15 @@ class InfoBar<AvailableTypes = ServerEntry | ChannelEntry | ClientEntry | undefi
|
|||
|
||||
current_manager() { return this._current_manager; }
|
||||
|
||||
html_tag() { return this._htmlTag; }
|
||||
html_tag() { return this._tag_content; }
|
||||
|
||||
is_popover() : boolean {
|
||||
return !this._tag.is(':visible') || this._tag.hasClass('shown');
|
||||
}
|
||||
|
||||
open_popover() {
|
||||
this._tag.toggleClass('shown', true);
|
||||
}
|
||||
}
|
||||
|
||||
class Hostbanner {
|
||||
|
@ -299,8 +323,12 @@ class ServerInfoManager extends InfoManager<ServerEntry> {
|
|||
|
||||
|
||||
{
|
||||
let requestUpdate = rendered.find(".btn_update");
|
||||
requestUpdate.prop("disabled", !server.shouldUpdateProperties());
|
||||
const disabled = !server.shouldUpdateProperties();
|
||||
let requestUpdate = rendered.find(".button-update");
|
||||
requestUpdate
|
||||
.prop("disabled", disabled)
|
||||
.toggleClass('btn-success', !disabled)
|
||||
.toggleClass('btn-danger', disabled);
|
||||
|
||||
requestUpdate.click(() => {
|
||||
server.updateProperties();
|
||||
|
@ -308,7 +336,10 @@ class ServerInfoManager extends InfoManager<ServerEntry> {
|
|||
});
|
||||
|
||||
this.registerTimer(setTimeout(function () {
|
||||
requestUpdate.prop("disabled", false);
|
||||
requestUpdate
|
||||
.prop("disabled", false)
|
||||
.toggleClass('btn-success', true)
|
||||
.toggleClass('btn-danger', false);
|
||||
}, server.nextInfoRequest - Date.now()));
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
/// <reference path="../../profiles/Identity.ts" />
|
||||
|
||||
namespace Modals {
|
||||
function spawnTeamSpeakIdentityImprove(identity: profiles.identities.TeaSpeakIdentity) : Modal {
|
||||
function spawnTeamSpeakIdentityImprove(identity: profiles.identities.TeaSpeakIdentity): Modal {
|
||||
let modal: Modal;
|
||||
let elapsed_timer: NodeJS.Timer;
|
||||
|
||||
|
@ -25,10 +25,10 @@ namespace Modals {
|
|||
const input_elapsed = template.find(".time-elapsed input");
|
||||
|
||||
button_close.on('click', event => {
|
||||
if(active)
|
||||
if (active)
|
||||
button_start_stop.trigger('click');
|
||||
|
||||
if(modal.shown)
|
||||
if (modal.shown)
|
||||
modal.close();
|
||||
});
|
||||
|
||||
|
@ -40,7 +40,7 @@ namespace Modals {
|
|||
|
||||
input_threads.prop("disabled", !active);
|
||||
input_target_level.prop("disabled", !active);
|
||||
if(active) {
|
||||
if (active) {
|
||||
input_hash_rate.val(0);
|
||||
clearInterval(elapsed_timer);
|
||||
active = false;
|
||||
|
@ -51,7 +51,7 @@ namespace Modals {
|
|||
|
||||
const threads = parseInt(input_threads.val() as string);
|
||||
const target_level = parseInt(input_target_level.val() as string);
|
||||
if(target_level == 0) {
|
||||
if (target_level == 0) {
|
||||
identity.improve_level(-1, threads, () => active, current_level => {
|
||||
input_current_level.val(current_level);
|
||||
}, hash_rate => {
|
||||
|
@ -59,7 +59,7 @@ namespace Modals {
|
|||
}).catch(error => {
|
||||
console.error(error);
|
||||
createErrorModal(tr("Failed to improve identity"), tr("Failed to improve identity.<br>Error:") + error).open();
|
||||
if(active)
|
||||
if (active)
|
||||
button_start_stop.trigger('click');
|
||||
});
|
||||
} else {
|
||||
|
@ -68,7 +68,7 @@ namespace Modals {
|
|||
}, hash_rate => {
|
||||
input_hash_rate.val(hash_rate);
|
||||
}).then(success => {
|
||||
if(success) {
|
||||
if (success) {
|
||||
identity.level().then(level => {
|
||||
input_current_level.val(level);
|
||||
createInfoModal(tr("Identity successfully improved"), MessageHelper.formatMessage(tr("Identity successfully improved to level {}"), level)).open();
|
||||
|
@ -76,12 +76,12 @@ namespace Modals {
|
|||
input_current_level.val("error: " + error);
|
||||
});
|
||||
}
|
||||
if(active)
|
||||
if (active)
|
||||
button_start_stop.trigger('click');
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
createErrorModal(tr("Failed to improve identity"), tr("Failed to improve identity.<br>Error:") + error).open();
|
||||
if(active)
|
||||
if (active)
|
||||
button_start_stop.trigger('click');
|
||||
});
|
||||
}
|
||||
|
@ -92,10 +92,10 @@ namespace Modals {
|
|||
let seconds = Math.floor(time % 60).toString();
|
||||
let minutes = Math.floor(time / 60).toString();
|
||||
|
||||
if(seconds.length < 2)
|
||||
if (seconds.length < 2)
|
||||
seconds = "0" + seconds;
|
||||
|
||||
if(minutes.length < 2)
|
||||
if (minutes.length < 2)
|
||||
minutes = "0" + minutes;
|
||||
|
||||
input_elapsed.val(minutes + ":" + seconds);
|
||||
|
@ -119,7 +119,7 @@ namespace Modals {
|
|||
return modal;
|
||||
}
|
||||
|
||||
function spawnTeamSpeakIdentityImport(callback: (identity: profiles.identities.TeaSpeakIdentity) => any) : Modal {
|
||||
function spawnTeamSpeakIdentityImport(callback: (identity: profiles.identities.TeaSpeakIdentity) => any): Modal {
|
||||
let modal: Modal;
|
||||
let loaded_identity: profiles.identities.TeaSpeakIdentity;
|
||||
|
||||
|
@ -134,7 +134,7 @@ namespace Modals {
|
|||
const button_import = template.find(".button-import");
|
||||
const set_error = message => {
|
||||
template.find(".success").hide();
|
||||
if(message) {
|
||||
if (message) {
|
||||
template.find(".error").text(message).show();
|
||||
button_import.prop("disabled", true);
|
||||
} else
|
||||
|
@ -157,7 +157,7 @@ namespace Modals {
|
|||
const element = event.target as HTMLInputElement;
|
||||
const file_reader = new FileReader();
|
||||
|
||||
file_reader.onload = function() {
|
||||
file_reader.onload = function () {
|
||||
import_identity(file_reader.result as string, true);
|
||||
};
|
||||
|
||||
|
@ -167,7 +167,7 @@ namespace Modals {
|
|||
return;
|
||||
};
|
||||
|
||||
if(element.files && element.files.length > 0)
|
||||
if (element.files && element.files.length > 0)
|
||||
file_reader.readAsText(element.files[0]);
|
||||
});
|
||||
}
|
||||
|
@ -175,7 +175,7 @@ namespace Modals {
|
|||
{ /* text input */
|
||||
template.find(".button-load-text").on('click', event => {
|
||||
createInputModal("Import identity from text", "Please paste your idenity bellow<br>", text => text.length > 0 && text.indexOf('V') != -1, result => {
|
||||
if(result)
|
||||
if (result)
|
||||
import_identity(result as string, false);
|
||||
}).open();
|
||||
});
|
||||
|
@ -197,7 +197,7 @@ namespace Modals {
|
|||
return modal;
|
||||
}
|
||||
|
||||
export function spawnSettingsModal() : Modal {
|
||||
export function spawnSettingsModal(): Modal {
|
||||
let modal: Modal;
|
||||
modal = createModal({
|
||||
header: tr("Settings"),
|
||||
|
@ -206,6 +206,7 @@ namespace Modals {
|
|||
client: native_client,
|
||||
valid_forum_identity: profiles.identities.valid_static_forum_identity(),
|
||||
forum_path: settings.static("forum_path"),
|
||||
voice_available: !!globalClient.voiceConnection
|
||||
});
|
||||
|
||||
initialiseVoiceListeners(modal, (template = template.tabify()).find(".settings_audio"));
|
||||
|
@ -226,12 +227,12 @@ namespace Modals {
|
|||
console.log(tag);
|
||||
{/* setup the forum */
|
||||
const identity = profiles.identities.static_forum_identity();
|
||||
if(identity && identity.valid()) {
|
||||
if (identity && identity.valid()) {
|
||||
tag.find(".not-connected").hide();
|
||||
|
||||
tag.find(".property.username .value").text(identity.name());
|
||||
const premium_tag = tag.find(".property.premium .value").text("");
|
||||
if(identity.is_stuff() || identity.is_premium())
|
||||
if (identity.is_stuff() || identity.is_premium())
|
||||
premium_tag.append($.spawn("div").addClass("premium").text(tr("yes")));
|
||||
else
|
||||
premium_tag.append($.spawn("div").addClass("non-premium").text(tr("no")));
|
||||
|
@ -250,6 +251,17 @@ namespace Modals {
|
|||
function initialiseVoiceListeners(modal: Modal, tag: JQuery) {
|
||||
let currentVAD = settings.global("vad_type", "vad");
|
||||
|
||||
const display_error = (message: string) => {
|
||||
const alert = tag.find(".settings-device-error").first();
|
||||
alert.clone()
|
||||
.alert()
|
||||
.css("display", "block")
|
||||
.insertAfter(alert)
|
||||
.find(".message")
|
||||
.text(message);
|
||||
};
|
||||
|
||||
if (globalClient.voiceConnection) {
|
||||
{ //Initialized voice activation detection
|
||||
const vad_tag = tag.find(".settings-vad-container");
|
||||
|
||||
|
@ -301,7 +313,7 @@ namespace Modals {
|
|||
});
|
||||
|
||||
let listener = (event: ppt.KeyEvent) => {
|
||||
if(event.type == ppt.EventType.KEY_TYPED) {
|
||||
if (event.type == ppt.EventType.KEY_TYPED) {
|
||||
settings.changeGlobal('vad_ppt_key', undefined); //TODO remove that because its legacy shit
|
||||
console.log(tr("Got key %o"), event);
|
||||
|
||||
|
@ -336,20 +348,20 @@ namespace Modals {
|
|||
slider.on("input change", () => {
|
||||
settings.changeGlobal("vad_threshold", slider.val().toString());
|
||||
let vad = globalClient.voiceConnection.voiceRecorder.getVADHandler();
|
||||
if(vad instanceof VoiceActivityDetectorVAD)
|
||||
if (vad instanceof VoiceActivityDetectorVAD)
|
||||
vad.percentageThreshold = slider.val() as number;
|
||||
vad_tag.find(".vad_vad_slider_value").text(slider.val().toString());
|
||||
});
|
||||
modal.properties.registerCloseListener(() => {
|
||||
let vad = globalClient.voiceConnection.voiceRecorder.getVADHandler();
|
||||
if(vad instanceof VoiceActivityDetectorVAD)
|
||||
if (vad instanceof VoiceActivityDetectorVAD)
|
||||
vad.percentage_listener = undefined;
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
let target_tag = vad_tag.find('input[type=radio][name="vad_type"][value="' + currentVAD + '"]');
|
||||
if(target_tag.length == 0) {
|
||||
if (target_tag.length == 0) {
|
||||
//TODO tr
|
||||
console.warn("Failed to find tag for " + currentVAD + ". Using latest tag!");
|
||||
target_tag = vad_tag.find('input[type=radio][name="vad_type"]').last();
|
||||
|
@ -358,16 +370,6 @@ namespace Modals {
|
|||
setTimeout(() => target_tag.trigger('change'), 0);
|
||||
}
|
||||
|
||||
const display_error = (message: string) => {
|
||||
const alert = tag.find(".settings-device-error").first();
|
||||
alert.clone()
|
||||
.alert()
|
||||
.css("display", "block")
|
||||
.insertAfter(alert)
|
||||
.find(".message")
|
||||
.text(message);
|
||||
};
|
||||
|
||||
{ //Initialize microphone
|
||||
|
||||
const setting_tag = tag.find(".settings-microphone");
|
||||
|
@ -385,9 +387,9 @@ namespace Modals {
|
|||
navigator.mediaDevices.enumerateDevices().then(devices => {
|
||||
const active_device = globalClient.voiceConnection.voiceRecorder.device_id();
|
||||
|
||||
for(const device of devices) {
|
||||
for (const device of devices) {
|
||||
console.debug(tr("Got device %s (%s): %s (%o)"), device.deviceId, device.kind, device.label);
|
||||
if(device.kind !== 'audioinput') continue;
|
||||
if (device.kind !== 'audioinput') continue;
|
||||
|
||||
$.spawn("option")
|
||||
.attr("device-id", device.deviceId)
|
||||
|
@ -402,7 +404,7 @@ namespace Modals {
|
|||
display_error(tr("Could not get microphone device list!"));
|
||||
});
|
||||
|
||||
if(tag_select.find("option:selected").length == 0)
|
||||
if (tag_select.find("option:selected").length == 0)
|
||||
tag_select.find("option").prop("selected", true);
|
||||
|
||||
};
|
||||
|
@ -420,6 +422,7 @@ namespace Modals {
|
|||
update_devices();
|
||||
setting_tag.find(".button-device-update").on('click', event => update_devices());
|
||||
}
|
||||
}
|
||||
|
||||
{ //Initialize speaker
|
||||
const setting_tag = tag.find(".settings-speaker");
|
||||
|
@ -431,7 +434,7 @@ namespace Modals {
|
|||
|
||||
const active_device = audio.player.current_device();
|
||||
audio.player.available_devices().then(devices => {
|
||||
for(const device of devices) {
|
||||
for (const device of devices) {
|
||||
$.spawn("option")
|
||||
.attr("device-id", device.device_id)
|
||||
.text(device.name)
|
||||
|
@ -445,7 +448,7 @@ namespace Modals {
|
|||
});
|
||||
|
||||
|
||||
if(tag_select.find("option:selected").length == 0)
|
||||
if (tag_select.find("option:selected").length == 0)
|
||||
tag_select.find("option").prop("selected", true);
|
||||
}
|
||||
|
||||
|
@ -498,7 +501,7 @@ namespace Modals {
|
|||
const template_tag = $("#tmpl_settings-sound_entry");
|
||||
const entry_tag = sound_tag.find(".sound-list-entries");
|
||||
|
||||
for(const _sound in Sound) {
|
||||
for (const _sound in Sound) {
|
||||
const sound_name = Sound[_sound];
|
||||
|
||||
console.log(sound.get_sound_volume(sound_name as Sound));
|
||||
|
@ -523,7 +526,7 @@ namespace Modals {
|
|||
|
||||
setTimeout(() => {
|
||||
const entry_container = sound_tag.find(".sound-list-entries-container");
|
||||
if(entry_container.hasScrollBar())
|
||||
if (entry_container.hasScrollBar())
|
||||
entry_container.addClass("scrollbar");
|
||||
}, 100);
|
||||
|
||||
|
@ -531,12 +534,12 @@ namespace Modals {
|
|||
const filter_tag = sound_tag.find(".sound-list-filter input");
|
||||
filter_tag.on('change keyup', event => {
|
||||
const filter = ((<HTMLInputElement>event.target).value || "").toLowerCase();
|
||||
if(!filter)
|
||||
if (!filter)
|
||||
entry_tag.find(".entry").show();
|
||||
else {
|
||||
entry_tag.find(".entry").each((_, _entry) => {
|
||||
const entry = $(_entry);
|
||||
if(entry.text().toLowerCase().indexOf(filter) == -1)
|
||||
if (entry.text().toLowerCase().indexOf(filter) == -1)
|
||||
entry.hide();
|
||||
else
|
||||
entry.show();
|
||||
|
@ -547,44 +550,6 @@ namespace Modals {
|
|||
|
||||
modal.close_listener.push(sound.save);
|
||||
}
|
||||
|
||||
//Initialise microphones
|
||||
/*
|
||||
let select_microphone = tag.find(".voice_microphone_select");
|
||||
let select_error = tag.find(".voice_microphone_select_error");
|
||||
|
||||
navigator.mediaDevices.enumerateDevices().then(devices => {
|
||||
let recoder = globalClient.voiceConnection.voiceRecorder;
|
||||
|
||||
console.log("Got " + devices.length + " devices:");
|
||||
for(let device of devices) {
|
||||
console.log(" - Type: %s Name %s ID: %s Group: %s", device.kind, device.label, device.deviceId, device.groupId);
|
||||
if(device.kind == "audioinput") {
|
||||
let dtag = $.spawn("option");
|
||||
dtag.attr("device-id", device.deviceId);
|
||||
dtag.attr("device-group", device.groupId);
|
||||
dtag.text(device.label);
|
||||
select_microphone.append(dtag);
|
||||
|
||||
if(recoder) dtag.prop("selected", device.deviceId == recoder.device_id());
|
||||
}
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error("Could not enumerate over devices!");
|
||||
console.error(error);
|
||||
select_error.text("Could not get device list!").show();
|
||||
});
|
||||
|
||||
select_microphone.change(event => {
|
||||
let deviceSelected = select_microphone.find("option:selected");
|
||||
let deviceId = deviceSelected.attr("device-id");
|
||||
let groupId = deviceSelected.attr("device-group");
|
||||
console.log("Selected microphone device: id: %o group: %o", deviceId, groupId);
|
||||
globalClient.voiceConnection.voiceRecorder.change_device(deviceId, groupId);
|
||||
});
|
||||
*/
|
||||
//Initialise speakers
|
||||
|
||||
}
|
||||
|
||||
function initialise_translations(tag: JQuery) {
|
||||
|
@ -648,7 +613,7 @@ namespace Modals {
|
|||
tag_loading.show();
|
||||
i18n.iterate_translations((repo, entry) => {
|
||||
let repo_tag = tag_list.find("[repository=\"" + repo.unique_id + "\"]");
|
||||
if(repo_tag.length == 0) {
|
||||
if (repo_tag.length == 0) {
|
||||
repo_tag = template.renderTag({
|
||||
type: "repository",
|
||||
name: repo.name || repo.url,
|
||||
|
@ -659,7 +624,7 @@ namespace Modals {
|
|||
e.preventDefault();
|
||||
|
||||
Modals.spawnYesNo(tr("Are you sure?"), tr("Do you really want to delete this repository?"), answer => {
|
||||
if(answer) {
|
||||
if (answer) {
|
||||
i18n.delete_repository(repo);
|
||||
update_list();
|
||||
}
|
||||
|
@ -716,7 +681,7 @@ namespace Modals {
|
|||
info_modal.open()
|
||||
});
|
||||
tag.on('click', e => {
|
||||
if(e.isDefaultPrevented()) return;
|
||||
if (e.isDefaultPrevented()) return;
|
||||
i18n.select_translation(repo, entry);
|
||||
tag_list.find(".selected").removeClass("selected");
|
||||
tag.addClass("selected");
|
||||
|
@ -734,7 +699,7 @@ namespace Modals {
|
|||
{
|
||||
tag.find(".button-add-repository").on('click', () => {
|
||||
createInputModal("Enter URL", tr("Enter repository URL:<br>"), text => true, url => { //FIXME test valid url
|
||||
if(!url) return;
|
||||
if (!url) return;
|
||||
|
||||
tag_loading.show();
|
||||
i18n.load_repository(url as string).then(repository => {
|
||||
|
@ -786,30 +751,30 @@ namespace Modals {
|
|||
profiles.mark_need_save();
|
||||
|
||||
let tag: JQuery;
|
||||
if(selected_type == profiles.identities.IdentitifyType.TEAFORO) {
|
||||
if (selected_type == profiles.identities.IdentitifyType.TEAFORO) {
|
||||
const forum_tag = tag = settings_tag.find(".identity-settings-teaforo");
|
||||
|
||||
forum_tag.find(".connected, .disconnected").hide();
|
||||
if(identity && identity.valid()) {
|
||||
if (identity && identity.valid()) {
|
||||
forum_tag.find(".connected").show();
|
||||
} else {
|
||||
forum_tag.find(".disconnected").show();
|
||||
}
|
||||
} else if(selected_type == profiles.identities.IdentitifyType.TEAMSPEAK) {
|
||||
} else if (selected_type == profiles.identities.IdentitifyType.TEAMSPEAK) {
|
||||
console.log("Set: " + identity);
|
||||
const teamspeak_tag = tag = settings_tag.find(".identity-settings-teamspeak");
|
||||
teamspeak_tag.find(".identity_string").val("");
|
||||
if(identity)
|
||||
if (identity)
|
||||
(identity as profiles.identities.TeaSpeakIdentity).export_ts().then(e => teamspeak_tag.find(".identity_string").val(e));
|
||||
} else if(selected_type == profiles.identities.IdentitifyType.NICKNAME) {
|
||||
} else if (selected_type == profiles.identities.IdentitifyType.NICKNAME) {
|
||||
const name_tag = tag = settings_tag.find(".identity-settings-nickname");
|
||||
if(identity)
|
||||
if (identity)
|
||||
name_tag.find("input").val(identity.name());
|
||||
else
|
||||
name_tag.find("input").val("");
|
||||
}
|
||||
|
||||
if(tag)
|
||||
if (tag)
|
||||
tag.trigger('show');
|
||||
};
|
||||
|
||||
|
@ -821,7 +786,7 @@ namespace Modals {
|
|||
const update_profile_list = () => {
|
||||
const profile_list = tag.find(".profile-list .list").empty();
|
||||
const profile_template = $("#settings-profile-list-entry");
|
||||
for(const profile of profiles.profiles()) {
|
||||
for (const profile of profiles.profiles()) {
|
||||
const list_tag = profile_template.renderTag({
|
||||
profile_name: profile.profile_name,
|
||||
id: profile.id
|
||||
|
@ -829,7 +794,7 @@ namespace Modals {
|
|||
|
||||
const profile_status_update = () => {
|
||||
list_tag.find(".status").hide();
|
||||
if(profile.valid())
|
||||
if (profile.valid())
|
||||
list_tag.find(".status-valid").show();
|
||||
else
|
||||
list_tag.find(".status-invalid").show();
|
||||
|
@ -839,7 +804,7 @@ namespace Modals {
|
|||
profile_list.find(".selected").removeClass("selected");
|
||||
list_tag.addClass("selected");
|
||||
|
||||
if(profile == selected_profile) return;
|
||||
if (profile == selected_profile) return;
|
||||
nickname_listener = () => list_tag.find(".name").text(profile.profile_name);
|
||||
status_listener = profile_status_update;
|
||||
|
||||
|
@ -848,14 +813,14 @@ namespace Modals {
|
|||
|
||||
|
||||
profile_list.append(list_tag);
|
||||
if((!selected_profile && profile.id == "default") || selected_profile == profile)
|
||||
if ((!selected_profile && profile.id == "default") || selected_profile == profile)
|
||||
setTimeout(() => list_tag.trigger('click'), 1);
|
||||
profile_status_update();
|
||||
}
|
||||
};
|
||||
|
||||
const display_error = (error?: string) => {
|
||||
if(error){
|
||||
if (error) {
|
||||
settings_tag.find(".settings-profile-error").show().find(".message").html(error);
|
||||
} else
|
||||
settings_tag.find(".settings-profile-error").hide();
|
||||
|
@ -881,9 +846,9 @@ namespace Modals {
|
|||
createInfoModal(tr("Identity imported"), tr("Your identity has been successfully imported!")).open();
|
||||
};
|
||||
|
||||
if(profile && profile.valid()) {
|
||||
if (profile && profile.valid()) {
|
||||
spawnYesNo(tr("Are you sure"), tr("Do you really want to import a new identity and override the old identity?"), result => {
|
||||
if(result)
|
||||
if (result)
|
||||
spawnTeamSpeakIdentityImport(set_identity);
|
||||
});
|
||||
} else
|
||||
|
@ -891,10 +856,10 @@ namespace Modals {
|
|||
});
|
||||
button_export.on('click', event => {
|
||||
const profile = selected_profile.selected_identity(profiles.identities.IdentitifyType.TEAMSPEAK) as profiles.identities.TeaSpeakIdentity;
|
||||
if(!profile) return;
|
||||
if (!profile) return;
|
||||
|
||||
createInputModal(tr("File name"), tr("Please enter the file name"), text => !!text, name => {
|
||||
if(name) {
|
||||
if (name) {
|
||||
profile.export_ts(true).then(data => {
|
||||
const element = $.spawn("a")
|
||||
.text("donwload")
|
||||
|
@ -925,9 +890,9 @@ namespace Modals {
|
|||
});
|
||||
};
|
||||
|
||||
if(profile && profile.valid()) {
|
||||
if (profile && profile.valid()) {
|
||||
spawnYesNo(tr("Are you sure"), tr("Do you really want to generate a new identity and override the old identity?"), result => {
|
||||
if(result)
|
||||
if (result)
|
||||
generate_identity();
|
||||
});
|
||||
} else
|
||||
|
@ -936,7 +901,7 @@ namespace Modals {
|
|||
|
||||
button_improve.on('click', event => {
|
||||
const profile = selected_profile.selected_identity(profiles.identities.IdentitifyType.TEAMSPEAK) as profiles.identities.TeaSpeakIdentity;
|
||||
if(!profile) return;
|
||||
if (!profile) return;
|
||||
|
||||
spawnTeamSpeakIdentityImprove(profile).close_listener.push(() => teamspeak_tag.trigger('show'));
|
||||
});
|
||||
|
@ -945,7 +910,7 @@ namespace Modals {
|
|||
teamspeak_tag.on('show', event => {
|
||||
const profile = selected_profile.selected_identity(profiles.identities.IdentitifyType.TEAMSPEAK) as profiles.identities.TeaSpeakIdentity;
|
||||
|
||||
if(!profile || !profile.valid()) {
|
||||
if (!profile || !profile.valid()) {
|
||||
identity_info_tag.hide();
|
||||
teamspeak_tag.find(".identity-undefined").show();
|
||||
button_export.prop("disabled", true);
|
||||
|
@ -964,12 +929,12 @@ namespace Modals {
|
|||
|
||||
{ //The forum
|
||||
const teaforo_tag = settings_tag.find(".identity-settings-teaforo");
|
||||
if(native_client) {
|
||||
if (native_client) {
|
||||
teaforo_tag.find(".native-teaforo-login").on('click', event => {
|
||||
setTimeout(() => {
|
||||
const forum = require("teaforo.js");
|
||||
const call = () => {
|
||||
if(modal.shown) {
|
||||
if (modal.shown) {
|
||||
display_settings(selected_profile);
|
||||
status_listener();
|
||||
}
|
||||
|
@ -981,7 +946,8 @@ namespace Modals {
|
|||
}
|
||||
|
||||
teaforo_tag.on('show', event => {
|
||||
display_error(); /* clear error */
|
||||
display_error();
|
||||
/* clear error */
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -992,7 +958,7 @@ namespace Modals {
|
|||
selected_profile.set_identity(profiles.identities.IdentitifyType.NICKNAME, new profiles.identities.NameIdentity(name));
|
||||
profiles.mark_need_save();
|
||||
|
||||
if(name.length < 3) {
|
||||
if (name.length < 3) {
|
||||
display_error("Name must be at least 3 characters long!");
|
||||
return;
|
||||
}
|
||||
|
@ -1001,9 +967,9 @@ namespace Modals {
|
|||
|
||||
name_tag.on('show', event => {
|
||||
const profile = selected_profile.selected_identity(profiles.identities.IdentitifyType.NICKNAME);
|
||||
if(!profile)
|
||||
if (!profile)
|
||||
display_error("invalid profile");
|
||||
else if(!profile.valid())
|
||||
else if (!profile.valid())
|
||||
display_error("Name must be at least 3 characters long!");
|
||||
else
|
||||
display_error();
|
||||
|
@ -1015,9 +981,9 @@ namespace Modals {
|
|||
{
|
||||
settings_tag.find(".setting-name").on('change', event => {
|
||||
const value = settings_tag.find(".setting-name").val() as string;
|
||||
if(value && selected_profile) {
|
||||
if (value && selected_profile) {
|
||||
selected_profile.profile_name = value;
|
||||
if(nickname_listener)
|
||||
if (nickname_listener)
|
||||
nickname_listener();
|
||||
profiles.mark_need_save();
|
||||
status_listener();
|
||||
|
@ -1025,7 +991,7 @@ namespace Modals {
|
|||
});
|
||||
settings_tag.find(".setting-default-nickname").on('change', event => {
|
||||
const value = settings_tag.find(".setting-default-nickname").val() as string;
|
||||
if(value && selected_profile) {
|
||||
if (value && selected_profile) {
|
||||
selected_profile.default_username = value;
|
||||
profiles.mark_need_save();
|
||||
status_listener();
|
||||
|
@ -1033,7 +999,7 @@ namespace Modals {
|
|||
});
|
||||
settings_tag.find(".setting-default-password").on('change', event => {
|
||||
const value = settings_tag.find(".setting-default-password").val() as string;
|
||||
if(value && selected_profile) {
|
||||
if (value && selected_profile) {
|
||||
selected_profile.default_username = value;
|
||||
profiles.mark_need_save();
|
||||
status_listener();
|
||||
|
@ -1045,7 +1011,7 @@ namespace Modals {
|
|||
{
|
||||
tag.find(".button-add-profile").on('click', event => {
|
||||
createInputModal(tr("Please enter a name"), tr("Please enter a name for the new profile:<br>"), text => text.length > 0 && !profiles.find_profile_by_name(text), value => {
|
||||
if(value) {
|
||||
if (value) {
|
||||
display_settings(profiles.create_new_profile(value as string));
|
||||
update_profile_list();
|
||||
profiles.mark_need_save();
|
||||
|
@ -1054,7 +1020,7 @@ namespace Modals {
|
|||
});
|
||||
|
||||
tag.find(".button-set-default").on('click', event => {
|
||||
if(selected_profile && selected_profile.id != 'default') {
|
||||
if (selected_profile && selected_profile.id != 'default') {
|
||||
profiles.set_default_profile(selected_profile);
|
||||
update_profile_list();
|
||||
profiles.mark_need_save();
|
||||
|
@ -1062,10 +1028,10 @@ namespace Modals {
|
|||
});
|
||||
|
||||
tag.find(".button-delete").on('click', event => {
|
||||
if(selected_profile && selected_profile.id != 'default') {
|
||||
if (selected_profile && selected_profile.id != 'default') {
|
||||
event.preventDefault();
|
||||
spawnYesNo(tr("Are you sure?"), tr ("Do you really want to delete this profile?"), result => {
|
||||
if(result) {
|
||||
spawnYesNo(tr("Are you sure?"), tr("Do you really want to delete this profile?"), result => {
|
||||
if (result) {
|
||||
profiles.delete_profile(selected_profile);
|
||||
update_profile_list();
|
||||
}
|
||||
|
@ -1075,7 +1041,7 @@ namespace Modals {
|
|||
}
|
||||
|
||||
modal.close_listener.push(() => {
|
||||
if(profiles.requires_save())
|
||||
if (profiles.requires_save())
|
||||
profiles.save();
|
||||
});
|
||||
update_profile_list();
|
||||
|
|
|
@ -135,7 +135,21 @@ class ServerEntry {
|
|||
}
|
||||
|
||||
spawnContextMenu(x: number, y: number, on_close: () => void = () => {}) {
|
||||
let trigger_close = true;
|
||||
spawn_context_menu(x, y, {
|
||||
type: MenuEntryType.ENTRY,
|
||||
name: tr("Show server info"),
|
||||
callback: () => {
|
||||
trigger_close = false;
|
||||
this.channelTree.client.selectInfo.open_popover()
|
||||
},
|
||||
icon: "client-about",
|
||||
visible: this.channelTree.client.selectInfo.is_popover()
|
||||
}, {
|
||||
type: MenuEntryType.HR,
|
||||
visible: this.channelTree.client.selectInfo.is_popover(),
|
||||
name: ''
|
||||
}, {
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-virtualserver_edit",
|
||||
name: tr("Edit"),
|
||||
|
@ -162,7 +176,7 @@ class ServerEntry {
|
|||
createInfoModal(tr("Buddy invite URL"), tr("Your buddy invite URL:<br>") + url + tr("<bt>This has been copied to your clipboard.")).open();
|
||||
}
|
||||
},
|
||||
MenuEntry.CLOSE(on_close)
|
||||
MenuEntry.CLOSE(() => (trigger_close ? on_close : () => {})())
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -144,6 +144,7 @@ class VoiceConnection {
|
|||
private vpacketId: number = 0;
|
||||
private chunkVPacketId: number = 0;
|
||||
private send_task: NodeJS.Timer;
|
||||
private _tag_favicon: JQuery;
|
||||
|
||||
constructor(client) {
|
||||
this.client = client;
|
||||
|
@ -171,6 +172,7 @@ class VoiceConnection {
|
|||
});
|
||||
|
||||
this.send_task = setInterval(this.sendNextVoicePacket.bind(this), 20);
|
||||
this._tag_favicon = $("head link[rel='icon']");
|
||||
}
|
||||
|
||||
native_encoding_supported() : boolean {
|
||||
|
@ -463,6 +465,8 @@ class VoiceConnection {
|
|||
|
||||
if(this.dataChannel)
|
||||
this.sendVoicePacket(new Uint8Array(0), this.current_channel_codec()); //TODO Use channel codec!
|
||||
|
||||
this._tag_favicon.attr('href', "img/favicon/teacup.png");
|
||||
}
|
||||
|
||||
private handleVoiceStarted() {
|
||||
|
@ -470,5 +474,6 @@ class VoiceConnection {
|
|||
|
||||
if(this.client && this.client.getClient())
|
||||
this.client.getClient().speaking = true;
|
||||
this._tag_favicon.attr('href', "img/favicon/speaking.png");
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
declare namespace WebAssembly {
|
||||
export function instantiateStreaming(stream: Promise<Response>, imports?: any) : Promise<ResultObject>;
|
||||
}
|
||||
declare function postMessage(message: any): void;
|
||||
|
||||
const prefix = "[POWWorker] ";
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 9a1c31f27fcac129fa3503c2c1d2096c126d3fd2
|
||||
Subproject commit 23f9aca6b6dc1ffccd20d6da04953776a1882f2b
|
|
@ -1,17 +1,27 @@
|
|||
html, body {
|
||||
height: 100%;
|
||||
overflow-y: hidden;
|
||||
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
|
||||
min-height: 250px;
|
||||
min-width: 250px;
|
||||
}
|
||||
|
||||
.app-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
|
||||
right: 10px;
|
||||
left: 10px;
|
||||
bottom: 40px;
|
||||
top: 10px;
|
||||
|
||||
.app {
|
||||
width: 100%;
|
||||
height: calc(100% - 50px);
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
|
||||
display: flex; flex-direction: column; resize: both;
|
||||
|
|
Loading…
Reference in New Issue