Revert "Develop (#35)"

This reverts commit 4d983dc36a.
This commit is contained in:
WolverinDEV 2019-03-17 15:20:25 +01:00
parent 6ce07804b5
commit d5d1cc7135
49 changed files with 894 additions and 4424 deletions

View file

@ -1,25 +1,4 @@
# Changelog:
* **17.03.19**
- Using VAD by default instead of PPT
- Improved mobile experience:
- Double touch join channel
- Removed the info bar for devices smaller than 500px
- Added country flags and names
- Added favicon, which change when you're recording
- Fixed double cache loading
- Fixed modal sizing scroll bug
- Added a channel subscribe all button
- Added individual channel subscribe settings
- Improved chat switch performance
- Added a chat message URL finder
- Escape URL detection with `!<url>`
- Improved chat experience
- Displaying offline chats as offline
- Notify when user closes the chat
- Notify when user disconnect/reconnects
- Preloading hostbanners to prevent flickering
- Fixed empty channel and server kick messages
* **17.02.19**
- Removed WebAssembly as dependency (Now working with MS Edge as well (but without audio))
- Improved channel tree performance

196
files.php
View file

@ -258,97 +258,9 @@
],
];
function systemify_path($path) {
return str_replace("/", DIRECTORY_SEPARATOR, $path);
}
function join_path(...$paths) {
$result_path = "";
foreach ($paths as $path) {
if(strlen($result_path) > 0)
$result_path .= DIRECTORY_SEPARATOR . $path;
else
$result_path = $path;
}
return $result_path;
}
function create_directories(&$error, $path, $dry_run = false) {
if(strpos(PHP_OS, "Linux") !== false) {
$command = "mkdir -p " . $path;
} else if(strpos(PHP_OS, "WINNT") !== false) {
$command = "mkdir " . $path; /* default path tree */
} else {
$error = "unsupported system";
return false;
}
echo $command . PHP_EOL;
if(!$dry_run) {
exec($command, $error, $state);
if($state) {
$error = "Command execution results in " . $state . ": " . implode(' ', $error);
return false;
}
}
return true;
}
function delete_directories(&$error, $path, $dry_run = false) {
if(strpos(PHP_OS, "Linux") !== false) {
$command = "rm -r " . $path;
} else if(strpos(PHP_OS, "WINNT") !== false) {
$command = "rm -r " . $path;
} else {
$error = "unsupported system";
return false;
}
echo $command . PHP_EOL;
if(!$dry_run) {
$state = 0;
exec($command, $output, $state);
if($state !== 0) {
$error = "Command execution results in " . $state . ": " . implode(' ', $output);
return false;
}
}
return true;
}
function create_link(&$error, $source, $target, $dry_run = false) {
if(strpos(PHP_OS, "Linux") !== false) {
$command = "ln -s " . $source . " " . $target;
} else if(strpos(PHP_OS, "WINNT") !== false) {
$command = "mklink " . (is_dir($target) ? "/D " : "") . " " . $target . " " . $source;
} else {
$error = "unsupported system";
return false;
}
echo $command . PHP_EOL;
if(!$dry_run) {
$state = 0;
exec($command, $output, $state);
if($state !== 0) {
$error = "Command execution results in " . $state . ": " . implode(' ', $output);
return false;
}
}
return true;
}
function list_dir($base_dir, $match = null, $depth = -1, &$results = array(), $dir = "") {
function list_dir($base_dir, $match = null, $depth = -1, &$results = array(), $dir = "") {
if($depth == 0) return $results;
if(!is_dir($base_dir . $dir)) {
echo "Skipping directory " . $base_dir . $dir . PHP_EOL;
return $results;
}
$files = scandir($base_dir . $dir);
foreach($files as $key => $value){
@ -367,14 +279,12 @@
class AppFile {
public $type;
public $name;
public $target_path; /* relative path to the target file viewed from the file root */
public $local_path; /* absolute path to local file */
public $path;
public $local_path;
public $hash;
}
function find_files($flag = 0b11, $local_path_prefix = "." . DIRECTORY_SEPARATOR, $type = "dev", $args = []) { //TODO Use cache here!
function find_files($flag = 0b11, $local_path_prefix = "./", $type = "dev", $args = []) { //TODO Use cache here!
global $APP_FILE_LIST;
$result = [];
@ -393,22 +303,23 @@
if(!$valid)
continue;
}
$entries = list_dir(
systemify_path($local_path_prefix . $entry["local-path"]),
$entry["search-pattern"],
isset($entry["search-depth"]) ? $entry["search-depth"] : -1
);
$entries = list_dir($local_path_prefix . $entry["local-path"], $entry["search-pattern"], isset($entry["search-depth"]) ? $entry["search-depth"] : -1);
foreach ($entries as $f_entry) {
if(isset($entry["search-exclude"]) && preg_match($entry["search-exclude"], $f_entry)) continue;
$file = new AppFile;
$f_info = pathinfo($f_entry);
$file->target_path = systemify_path($entry["path"]) . DIRECTORY_SEPARATOR . $f_info["dirname"] . DIRECTORY_SEPARATOR;
$file->local_path = getcwd() . DIRECTORY_SEPARATOR . systemify_path($entry["local-path"]) . DIRECTORY_SEPARATOR . $f_info["dirname"] . DIRECTORY_SEPARATOR;
$idx_sep = strrpos($f_entry, DIRECTORY_SEPARATOR);
$file->path = "./" . $entry["path"] . "/";
if($idx_sep > 0) {
$file->name = substr($f_entry, strrpos($f_entry, DIRECTORY_SEPARATOR) + 1);
$file->path = $file->path . substr($f_entry, 0, strrpos($f_entry, DIRECTORY_SEPARATOR));
} else {
$file->name = $f_entry;
}
$file->name = $f_info["basename"];
$file->local_path = $local_path_prefix . $entry["local-path"] . DIRECTORY_SEPARATOR . $f_entry;
$file->type = $entry["type"];
$file->hash = sha1_file($file->local_path . DIRECTORY_SEPARATOR . $file->name);
$file->hash = sha1_file($file->local_path);
if(strlen($file->hash) > 0) {
foreach ($result as $e)
@ -423,17 +334,10 @@
}
if(isset($_SERVER["argv"])) { //Executed by command line
$supported = false;
if(strpos(PHP_OS, "Linux") !== false) {
$supported = true;
} else if(strpos(PHP_OS, "WIN") !== false) {
$supported = true;
}
if(!$supported) {
error_log("Invalid operating system (" . PHP_OS . ")! Help tool only available under linux!");
exit(1);
}
if(strpos(PHP_OS, "Linux") == -1) {
error_log("Invalid operating system! Help tool only available under linux!");
exit(1);
}
if(count($_SERVER["argv"]) < 2) {
error_log("Invalid parameters!");
goto help;
@ -454,10 +358,10 @@
if($_SERVER["argv"][3] == "dev" || $_SERVER["argv"][3] == "development") {
if ($_SERVER["argv"][2] == "web") {
$flagset = 0b01;
$environment = join_path("web", "environment", "development");
$environment = "web/environment/development";
} else if ($_SERVER["argv"][2] == "client") {
$flagset = 0b10;
$environment = join_path("client-api", "environment", "ui-files", "raw");
$environment = "client-api/environment/ui-files/raw";
} else {
error_log("Invalid type!");
goto help;
@ -466,10 +370,10 @@
$type = "rel";
if ($_SERVER["argv"][2] == "web") {
$flagset = 0b01;
$environment = join_path("web", "environment", "release");
$environment = "web/environment/release";
} else if ($_SERVER["argv"][2] == "client") {
$flagset = 0b10;
$environment = join_path("client-api", "environment", "ui-files", "raw");
$environment = "client-api/environment/ui-files/raw";
} else {
error_log("Invalid type!");
goto help;
@ -481,29 +385,37 @@
{
if(!$dry_run) {
if(delete_directories($error, $environment) === false)
goto handle_error;
if(create_directories($error, $environment) === false)
goto handle_error;
exec($command = "rm -r " . $environment, $output, $state);
exec($command = "mkdir -p " . $environment, $output, $state); if($state) goto handle_error;
}
$files = find_files($flagset, "." . DIRECTORY_SEPARATOR, $type, array_slice($_SERVER["argv"], 4));
$files = find_files($flagset, "./", $type, array_slice($_SERVER["argv"], 4));
$original_path = realpath(".");
if(!chdir($environment)) {
error_log("Failed to enter directory " . $environment . "!");
exit(1);
}
/** @var AppFile $file */
foreach($files as $file) {
if(!$dry_run && !is_dir($file->target_path) && strlen($file->target_path) > 0) {
if(create_directories($error, $file->target_path, $dry_run) === false)
goto handle_error;
foreach($files as $file) {
if(!$dry_run && !is_dir($file->path)) {
exec($command = "mkdir -p " . $file->path, $output, $state);
if($state) goto handle_error;
}
if(create_link($output, $file->local_path . $file->name, $file->target_path . $file->name, $dry_run) === false)
goto handle_error;
$parent_base = substr_count(realpath($file->path), DIRECTORY_SEPARATOR) - substr_count(realpath('.'), DIRECTORY_SEPARATOR);
$parent_file = substr_count(realpath("."), DIRECTORY_SEPARATOR) - substr_count($original_path, DIRECTORY_SEPARATOR); //Current to parent
$parent = $parent_base + $parent_file;
$path = "";
for($index = 0; $index < $parent; $index++)
$path = $path . "../";
$command = "ln -s " . $path . $file->local_path . " " . $file->path;
if(!$dry_run) {
exec($command, $output, $state);
if($state) goto handle_error;
}
echo $command . PHP_EOL;
}
if(!chdir($original_path)) {
error_log("Failed to reset directory!");
@ -513,20 +425,18 @@
}
if(!$dry_run) {
exec("." . DIRECTORY_SEPARATOR . "scripts" . DIRECTORY_SEPARATOR . "git_index.sh sort-tag", $output, $state);
exec("./scripts/git_index.sh sort-tag", $output, $state);
file_put_contents($environment . DIRECTORY_SEPARATOR . "version", $output);
if ($_SERVER["argv"][2] == "client") {
if(!chdir("client-api" . DIRECTORY_SEPARATOR . "environment")) {
if(!chdir("client-api/environment")) {
error_log("Failed to enter directory client-api/environment!");
exit(1);
}
if(!is_dir("versions" . DIRECTORY_SEPARATOR . "beta")) {
exec($command = "mkdir -p versions/beta", $output, $state); if($state) goto handle_error;
}
if(!is_dir("versions/stable")) {
exec($command = "mkdir -p versions/beta", $output, $state); if($state) goto handle_error;
}
if(!is_dir("versions/beta"))
exec($command = "mkdir -p versions/beta", $output, $state); if($state) goto handle_error;
if(!is_dir("versions/stable"))
exec($command = "mkdir -p versions/beta", $output, $state); if($state) goto handle_error;
exec($command = "ln -s ../api.php ./", $output, $state); $state = 0; //Dont handle an error here!
if($state) goto handle_error;
@ -535,8 +445,10 @@
exit(0);
handle_error:
error_log("Command execution failed!");
error_log("Error message: " . $error);
error_log("Failed to execute command '" . $command . "'!");
error_log("Command returned code " . $state . ". Output: " . PHP_EOL);
foreach ($output as $line)
error_log($line);
exit(1);
}
}

View file

@ -6,8 +6,7 @@
"directories": {},
"scripts": {
"compile-sass": "sass --update .:.",
"build-worker-codec": "tsc -p shared/js/workers/tsconfig_worker_codec.json",
"build-worker-pow": "tsc -p shared/js/workers/tsconfig_worker_pow.json",
"build-worker": "tsc -p shared/js/workers/tsconfig_worker_codec.json",
"dtsgen": "node tools/dtsgen/index.js",
"trgen": "node tools/trgen/index.js",
"ttsc": "ttsc",

View file

@ -28,14 +28,9 @@ if [[ $? -ne 0 ]]; then
fi
echo "Generating web workers"
npm run build-worker-codec
npm run build-worker
if [[ $? -ne 0 ]]; then
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"
echo "Failed to build web workers"
exit 1
fi

View file

@ -1,35 +0,0 @@
# 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

View file

@ -37,7 +37,6 @@
flex-direction: row;
justify-content: stretch;
cursor: pointer;
margin-left: 0;
.server_type {
@ -135,10 +134,6 @@
display: block;
}
}
.icon_no_sound {
display: flex;
}
}
.container-clients {
@ -198,15 +193,7 @@
}
/* all icons related to basic_icons */
.clicon {
width:16px;
height:16px;
background:url('../../img/ts/basic_icons.png') no-repeat;
background-size: 16px 608px;
flex-grow: 0;
flex-shrink: 0;
}
.clicon {width:16px;height:16px;background:url('../../img/ts/basic_icons.png') no-repeat;background-size: 16px 608px;}
.host {background-position: 0 -448px}

View file

@ -47,11 +47,13 @@ $background:lightgray;
.button-dropdown {
.buttons {
display: flex;
flex-direction: row;
display: grid;
grid-template-columns: auto auto;
grid-template-rows: 100%;
grid-gap: 2px;
.button {
margin-right: 0;
margin-right: 0px;
}
.button-dropdown {
@ -81,7 +83,6 @@ $background:lightgray;
background-color: rgba(0,0,0,0.4);
border-color: rgba(255, 255, 255, .75);
/*box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19);*/
border-left: 2px solid rgba(255, 255, 255, .75);
}
}
}
@ -102,11 +103,6 @@ $background:lightgray;
z-index: 1000;
/*box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19);*/
&.right {
}
.icon {
vertical-align: middle;
margin-right: 5px;
@ -135,8 +131,8 @@ $background:lightgray;
}
}
&:hover.displayed {
.dropdown {
&:hover {
.dropdown.displayed {
display: block;
}
}

View file

@ -1,18 +1,11 @@
.select_info_table {
tr {
td {
&:nth-child(1) {
font-weight: bold;
padding-right: 5px;
//min-width: max(35%, 20px);
}
.select_info_table { }
.select_info_table tr { }
.select_info_table tr td { }
&:nth-child(2) {
//min-width: max(75%, 40px);
word-break: break-word;
}
}
}
.select_info_table tr td:nth-child(1) {
font-weight: bold;
padding-right: 5px;
min-width: 20%;
}
.select_server {
@ -24,18 +17,21 @@
.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;
@ -43,27 +39,23 @@
}
}
/*
<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%;
}
.container-banner {
position: relative;
flex-grow: 1;
flex-shrink: 2;
max-height: 25%;
@ -82,29 +74,9 @@
position: relative;
flex-grow: 1;
.image-container {
display: flex;
flex-direction: row;
justify-content: center;
height: 100%;
div {
background-position: center;
&.hostbanner-mode-0 { }
&.hostbanner-mode-1 {
width: 100%;
height: auto;
}
&.hostbanner-mode-2 {
background-size: contain!important;
width:100%;
height:100%
}
}
}
img {
position: absolute;
}
}
}
@ -133,13 +105,4 @@
}
}
}
.button-browser-info {
vertical-align: bottom;
cursor: pointer;
&:hover {
background-color: gray;
}
}
}

View file

@ -228,14 +228,8 @@ footer .container {
}
$separator_thickness: 4px;
$small_device: 650px;
$animation_length: .5s;
.app {
min-width: 350px;
.container-app-main {
position: relative;
display: flex;
flex-direction: row;
justify-content: stretch;
@ -307,78 +301,8 @@ $animation_length: .5s;
flex-direction: row;
justify-content: stretch;
}
.hide-small {
opacity: 1;
transition: opacity $animation_length linear;
}
.show-small {
display: none;
opacity: 0;
transition: opacity $animation_length linear;
}
}
@media only screen and (max-width: $small_device) {
.app-container {
right: 0;
left: 0;
bottom: 25px;
top: 0;
transition: all $animation_length linear;
overflow: auto;
}
.app {
.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;
animation: fadeout $animation_length linear;
}
.container-channel-chat {
width: 100%!important; /* override the seperator property */
}
}
}
.hide-small {
display: none;
opacity: 0;
transition: opacity $animation_length linear;
}
.show-small {
display: block!important;
opacity: 1!important;
transition: opacity $animation_length linear;
}
}
.container-seperator {
background: lightgray;
flex-grow: 0;
@ -404,7 +328,6 @@ $animation_length: .5s;
}
.icon-container {
position: relative;
display: inline-block;
height: 16px;
width: 16px;
@ -431,6 +354,8 @@ $animation_length: .5s;
}
html, body {
min-height: 500px;
min-width: 500px;
overflow: hidden;
}
@ -440,24 +365,13 @@ body {
}
.icon-playlist-manage {
&.icon {
width: 16px;
height: 16px;
background-position: -5px -5px;
background-size: 25px;
}
&.icon_x32 {
width: 32px;
height: 32px;
background-position: -11px -9px;
background-size: 50px;
}
display: inline-block;
width: 32px;
height: 32px;
background: url('../../img/music/playlist.svg') no-repeat;
background-position: -11px -9px;
background-size: 50px;
}
x-content {

View file

@ -35,20 +35,6 @@
display: inline-block;
vertical-align: top;
}
.event-message { /* special formated messages */
&.event-partner-disconnect {
color: red;
}
&.event-partner-connect {
color: green;
}
&.event-partner-closed {
color: orange;
}
}
}
}
}
@ -76,9 +62,11 @@
cursor: pointer;
height: 18px;
.btn_close {
display: none;
&.active {
background: #11111111;
}
.btn_close {
float: none;
margin-right: -5px;
margin-left: 8px;
@ -90,34 +78,9 @@
}
}
.name, .chat-type {
.name, .chatIcon {
display: inline-block;
}
.name {
color: black;
}
&.closeable {
.btn_close {
display: inline-block;
}
}
&.active {
background: #11111111;
}
&.offline {
.name {
color: gray;
}
}
&.unread {
.name {
color: blue;
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -8,9 +8,6 @@
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;
}
@ -1031,7 +1028,7 @@
}
.icon_x32.client-refresh {
background-position: calc(-224px * 2) calc(-256px * 2);
}
}pe the key you wish
.icon_x32.client-register {
background-position: calc(-256px * 2) calc(-256px * 2);
}

View file

@ -1,4 +1,7 @@
x-tab { display:none }
x-content {
width: 100%;
}
.tab {
padding: 2px;
@ -15,19 +18,15 @@ x-tab { display:none }
.tab .tab-content {
min-height: 200px;
border-radius: 0 2px 2px 2px;
border: solid #6f6f6f;
overflow-y: hidden;
border-color: #6f6f6f;
border-radius: 0px 2px 2px 2px;
border-style: solid;
overflow-y: auto;
height: 100%;
padding: 2px;
display: flex;
flex-grow: 1;
x-content {
overflow-y: auto;
width: 100%;
}
}
/*
@ -40,7 +39,7 @@ x-tab { display:none }
*/
.tab .tab-header {
font-family: Arial, serif;
font-family: Arial;
font-size: 12px;
/*white-space: pre;*/
line-height: 1;
@ -65,10 +64,14 @@ x-tab { display:none }
.tab .tab-header .entry {
background: #5f5f5f5f;
display: inline-block;
border: 1px solid #6f6f6f;
border: #6f6f6f;
border-width: 1px;
border-style: solid;
border-radius: 2px 2px 0px 0px;
vertical-align: middle;
padding: 2px 5px;
padding: 2px;
padding-left: 5px;
padding-right: 5px;
cursor: pointer;
flex-grow: 1;
}

View file

@ -37,7 +37,6 @@
<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,9 +189,9 @@
<footer style="<?php echo $footer_style; ?>">
<div class="container" style="display: flex; flex-direction: row; align-content: space-between;">
<div class="hide-small" 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 (<?php echo $version; ?>) by WolverinDEV</div>
<div class="hide-small" style="align-self: center; position: fixed; right: 5px;"><?php echo $footer_forum; ?></div>
<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; position: fixed; right: 5px;"><?php echo $footer_forum; ?></div>
</div>
</footer>
</html>

View file

@ -6,6 +6,7 @@
<title>TeaSpeak-Web client templates</title>
</head>
<body>
<!-- main frame TODO tr -->
<script class="jsrender-template" id="tmpl_main" type="text/html">
<div class="app-container">
<div class="app">
@ -37,7 +38,7 @@
<div class="divider"></div>
<div class="hide-small button-dropdown btn_away" title="{{tr 'Toggle away status' /}}">
<div class="button-dropdown btn_away" title="{{tr 'Toggle away status' /}}">
<div class="buttons">
<div class="button icon_x32 client-away btn_away_toggle"></div>
<div class="button-dropdown">
@ -49,36 +50,15 @@
<div class="btn_away_message"><div class="icon client-away"></div><a>{{tr "Set away message" /}}</a></div>
</div>
</div>
<div class="hide-small button btn_mute_input">
<div class="button btn_mute_input">
<div class="icon_x32 client-input_muted" title="{{tr 'Mute/unmute microphone' /}}"></div>
</div>
<div class="hide-small button btn_mute_output">
<div class="button btn_mute_output">
<div class="icon_x32 client-output_muted" title="{{tr 'Mute/unmute headphones' /}}"></div>
</div>
<div class="show-small button-dropdown dropdown-audio" title="{{tr 'Audio settings' /}}">
<div class="buttons">
<div class="button button-display icon_x32 client-music"></div>
<div class="button-dropdown">
<div class="arrow down"></div>
</div>
</div>
<div class="dropdown">
<div class="btn_mute_input" title="{{tr 'Mute/unmute microphone' /}}">
<div class="icon client-input_muted"></div>
<a>{{tr "Mute/unmute microphone" /}}</a>
</div>
<div class="btn_mute_output" title="{{tr 'Mute/unmute headphones' /}}">
<div class="icon client-output_muted"></div>
<a>{{tr "Mute/unmute headphones" /}}</a>
</div>
</div>
</div>
<div class="divider"></div>
<div class="button button-subscribe-mode">
<div class="icon_x32" title="{{tr 'Toggle channel subscribe mode' /}}"></div>
</div>
<div class="hide-small button-dropdown btn_token" title="{{tr 'Use token' /}}">
<div class="button-dropdown btn_token" title="{{tr 'Use token' /}}">
<div class="buttons">
<div class="button icon_x32 client-token btn_token_use"></div>
<div class="button-dropdown">
@ -92,37 +72,13 @@
</div>
<div style="width: 100%"></div>
<div class="show-small button-dropdown dropdown-servertools" title="{{tr 'Server tools' /}}">
<div class="buttons">
<div class="button button-display icon_x32 client-virtualserver_edit"></div>
<div class="button-dropdown">
<div class="arrow down"></div>
</div>
</div>
<div class="dropdown right">
<div class="button-playlist-manage" title="{{tr 'Playlists' /}}">
<div class="icon icon-playlist-manage"></div>
<a>{{tr "Playlists" /}}</a>
</div>
<div class="btn_banlist" title="{{tr 'Banlist' /}}">
<div class="icon client-ban_list"></div>
<a>{{tr "Banlist" /}}</a>
</div>
<div class="btn_permissions" title="{{tr 'View/edit permissions' /}}">
<div class="icon client-permission_overview"></div>
<a>{{tr "View/edit permissions" /}}</a>
</div>
</div>
<div class="button button-playlist-manage" title="{{tr 'Playlists' /}}">
<div class="icon-playlist-manage"></div>
</div>
<div class="hide-small button button-playlist-manage" title="{{tr 'Playlists' /}}">
<div class="icon_x32 icon-playlist-manage"></div>
</div>
<div class="hide-small button btn_banlist" title="{{tr 'Banlist' /}}">
<div class="button btn_banlist" title="{{tr 'Banlist' /}}">
<div class="icon_x32 client-ban_list"></div>
</div>
<div class="hide-small button btn_permissions" title="{{tr 'View/edit permissions' /}}">
<div class="button btn_permissions" title="{{tr 'View/edit permissions' /}}">
<div class="icon_x32 client-permission_overview"></div>
</div>
@ -177,11 +133,8 @@
</div>
</div>
<div class="container-seperator vertical" seperator-id="seperator-main-info"></div>
<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="main_container container-info">
<div id="select_info" class="select_info" style="width: 100%; max-width: 100%">
<div class="container-banner"></div>
<!-- <div class="container-seperator horizontal" seperator-id="seperator-hostbanner-info"></div> -->
<div class="container-select-info"></div>
@ -1076,8 +1029,7 @@
<div class="group_box">
<div class="header">{{tr "Microphone" /}}</div>
<div class="content settings-microphone {{if !voice_available}}disabled{{/if}}">
{{if voice_available}}
<div class="content settings-microphone">
<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>
@ -1136,9 +1088,6 @@
</div>
</div>
</div>
{{else}}
<div>{{tr "Voice had been disabled" /}}</div>
{{/if}}
</div>
</div>
<div class="group_box">
@ -2108,10 +2057,7 @@
<table class="select_info_table">
<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()}}" title="{{*:i18n.country_name(data.property_client_country || 'XX')}}"></div>
<node key="client_name"/>
</td>
<td><node key="client_name"/></td>
</tr>
{{if property_client_description.length > 0}}
<tr>
@ -2122,14 +2068,7 @@
{{if !client_is_query}}
<tr>
<td>{{tr "Version:"/}}</td>
<td>
<a title="{{>property_client_version}}">{{*: data.property_client_version.split(" ")[0]; }}</a>
{{if client_is_web && false}} <!-- we cant show any browser info because every browser claims to be any browser as well -->
<div class="icon client-message_info button-browser-info" title="{{tr 'Browser info' /}}"></div>
{{/if}}
on
<a>{{>property_client_platform}}</a>
</td>
<td><a title="{{>property_client_version}}">{{*: data.property_client_version.split(" ")[0]; }}</a> on {{>property_client_platform}}</td>
</tr>
{{/if}}
<tr>
@ -2303,11 +2242,26 @@
</script>
<script class="jsrender-template" id="tmpl_selected_hostbanner" type="text/html">
<div class="hostbanner">
<a class="image-container" href="{{:property_virtualserver_hostbanner_url}}" target="_blank">
<div
style="background: center no-repeat url({{:hostbanner_gfx_url}})"
alt="{{tr 'Host banner'/}}"
class="hostbanner-image hostbanner-mode-{{:property_virtualserver_hostbanner_mode}}"
<a href="{{:property_virtualserver_hostbanner_url}}" target="_blank" style="display: flex; flex-direction: row; justify-content: center; height: 100%">
<div style="
background:center no-repeat url(
{{:property_virtualserver_hostbanner_gfx_url}}{{:cache_tag}}
);
background-position: center;
{{if property_virtualserver_hostbanner_mode == 0}}
{{else property_virtualserver_hostbanner_mode == 1}}
width: 100%; height: auto;
{{else property_virtualserver_hostbanner_mode == 2}}
background-size:contain;
width:100%;
height:100%
{{/if}}
"
alt="{{tr "Host banner"/}}"
></div>
</a>
</div>
@ -2358,7 +2312,7 @@
</table>
</div>
<button class="button-update btn btn-success">{{tr "Update info"/}}</button>
<button class="button-update btn_update">{{tr "Update info"/}}</button>
</div>
</script>
<script class="jsrender-template" id="tmpl_selected_channel" type="text/html">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

BIN
shared/img/ts/flags Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

View file

@ -1,10 +1,6 @@
/// <reference path="client.ts" />
/// <reference path="connection/ConnectionBase.ts" />
/*
FIXME: Dont use item storage with base64! Use the larger cache API and drop IE support!
https://developer.mozilla.org/en-US/docs/Web/API/CacheStorage#Browser_compatibility
*/
class FileEntry {
name: string;
datetime: number;

View file

@ -1,5 +1,3 @@
import LogType = log.LogType;
enum ChatType {
GENERAL,
SERVER,
@ -67,11 +65,12 @@ namespace MessageHelper {
}
if(objects.length < number)
log.warn(LogCategory.GENERAL, tr("Message to format contains invalid index (%o)"), number);
console.warn(tr("Message to format contains invalid index (%o)"), number);
result.push(...formatElement(objects[number]));
found = found + 1 + offset;
begin = found + 1;
console.log(tr("Offset: %d Number: %d"), offset, number);
} while(found++);
return result;
@ -94,7 +93,7 @@ namespace MessageHelper {
});
if(result.error) {
log.error(LogCategory.GENERAL, tr("BBCode parse error: %o"), result.errorQueue);
console.log("BBCode parse error: %o", result.errorQueue);
return formatElement(message);
}
@ -105,7 +104,7 @@ namespace MessageHelper {
class ChatMessage {
date: Date;
message: JQuery[];
private _html_tag: JQuery<HTMLElement>;
private _htmlTag: JQuery<HTMLElement>;
constructor(message: JQuery[]) {
this.date = new Date();
@ -118,8 +117,8 @@ class ChatMessage {
return str;
}
get html_tag() {
if(this._html_tag) return this._html_tag;
get htmlTag() {
if(this._htmlTag) return this._htmlTag;
let tag = $.spawn("div");
tag.addClass("message");
@ -129,30 +128,26 @@ class ChatMessage {
dateTag.css("margin-right", "4px");
dateTag.css("color", "dodgerblue");
this._html_tag = tag;
this._htmlTag = tag;
tag.append(dateTag);
this.message.forEach(e => e.appendTo(tag));
tag.hide();
return tag;
}
}
class ChatEntry {
readonly handle: ChatBox;
handle: ChatBox;
type: ChatType;
key: string;
history: ChatMessage[];
owner_unique_id?: string;
private _name: string;
private _html_tag: any;
private _flag_closeable: boolean = true;
private _flag_unread : boolean = false;
private _flag_offline: boolean = false;
private _htmlTag: any;
private _closeable: boolean;
private _unread : boolean;
onMessageSend: (text: string) => void;
onClose: () => boolean = () => true;
onClose: () => boolean;
constructor(handle, type : ChatType, key) {
this.handle = handle;
@ -160,6 +155,8 @@ class ChatEntry {
this.key = key;
this._name = key;
this.history = [];
this.onClose = function () { return true; }
}
appendError(message: string, ...args) {
@ -176,7 +173,7 @@ class ChatEntry {
this.history.push(entry);
while(this.history.length > 100) {
let elm = this.history.pop_front();
elm.html_tag.animate({opacity: 0}, 200, function () {
elm.htmlTag.animate({opacity: 0}, 200, function () {
$(this).detach();
});
}
@ -184,75 +181,66 @@ class ChatEntry {
let box = $(this.handle.htmlTag).find(".messages");
let mbox = $(this.handle.htmlTag).find(".message_box");
let bottom : boolean = box.scrollTop() + box.height() + 1 >= mbox.height();
mbox.append(entry.html_tag);
entry.html_tag.css("opacity", "0").animate({opacity: 1}, 100);
mbox.append(entry.htmlTag);
entry.htmlTag.show().css("opacity", "0").animate({opacity: 1}, 100);
if(bottom) box.scrollTop(mbox.height());
} else {
this.flag_unread = true;
this.unread = true;
}
}
displayHistory() {
this.flag_unread = false;
let box = this.handle.htmlTag.find(".messages");
let mbox = box.find(".message_box").detach(); /* detach the message box to improve performance */
this.unread = false;
let box = $(this.handle.htmlTag).find(".messages");
let mbox = $(this.handle.htmlTag).find(".message_box");
mbox.empty();
for(let e of this.history) {
mbox.append(e.html_tag);
/* TODO Is this really totally useless?
Because its at least a performance bottleneck because is(...) recalculates the page style
if(e.htmlTag.is(":hidden"))
e.htmlTag.show();
*/
mbox.append(e.htmlTag);
if(e.htmlTag.is(":hidden")) e.htmlTag.show();
}
mbox.appendTo(box);
box.scrollTop(mbox.height());
}
get html_tag() {
if(this._html_tag)
return this._html_tag;
get htmlTag() {
if(this._htmlTag) return this._htmlTag;
let tag = $.spawn("div");
tag.addClass("chat");
if(this._flag_unread)
tag.addClass('unread');
if(this._flag_offline)
tag.addClass('offline');
if(this._flag_closeable)
tag.addClass('closeable');
tag.append($.spawn("div").addClass("chat-type icon " + this.chat_icon()));
tag.append($.spawn("a").addClass("name").text(this._name));
tag.append("<div class=\"chatIcon icon " + this.chatIcon() + "\"></div>");
tag.append("<a class='name'>" + this._name + "</a>");
let tag_close = $.spawn("div");
tag_close.addClass("btn_close icon client-tab_close_button");
if(!this._flag_closeable) tag_close.hide();
tag.append(tag_close);
let closeTag = $.spawn("div");
closeTag.addClass("btn_close icon client-tab_close_button");
if(!this._closeable) closeTag.hide();
tag.append(closeTag);
tag.click(() => { this.handle.activeChat = this; });
tag.on("contextmenu", (e) => {
const _this = this;
tag.click(function () {
_this.handle.activeChat = _this;
});
tag.on("contextmenu", function (e) {
e.preventDefault();
let actions: ContextMenuEntry[] = [];
let actions = [];
actions.push({
type: MenuEntryType.ENTRY,
icon: "",
name: tr("Clear"),
callback: () => {
this.history = [];
this.displayHistory();
_this.history = [];
_this.displayHistory();
}
});
if(this.flag_closeable) {
if(_this.closeable) {
actions.push({
type: MenuEntryType.ENTRY,
icon: "client-tab_close_button",
name: tr("Close"),
callback: () => {
chat.deleteChat(this);
chat.deleteChat(_this);
}
});
}
@ -263,20 +251,18 @@ class ChatEntry {
name: tr("Close all private tabs"),
callback: () => {
//TODO Implement this?
},
visible: false
}
});
spawn_context_menu(e.pageX, e.pageY, ...actions);
});
tag_close.click(() => {
if($.isFunction(this.onClose) && !this.onClose())
return;
this.handle.deleteChat(this);
closeTag.click(function () {
if($.isFunction(_this.onClose) && !_this.onClose()) return;
_this.handle.deleteChat(_this);
});
return this._html_tag = tag;
this._htmlTag = tag;
return tag;
}
focus() {
@ -285,37 +271,33 @@ class ChatEntry {
}
set name(newName : string) {
console.log(tr("Change name!"));
this._name = newName;
this.html_tag.find(".name").text(this._name);
this.htmlTag.find(".name").text(this._name);
}
set flag_closeable(flag : boolean) {
if(this._flag_closeable == flag) return;
set closeable(flag : boolean) {
if(this._closeable == flag) return;
this._flag_closeable = flag;
this.html_tag.toggleClass('closeable', flag);
this._closeable = flag;
console.log(tr("Set closeable: ") + this._closeable);
if(flag) this.htmlTag.find(".btn_close").show();
else this.htmlTag.find(".btn_close").hide();
}
set flag_unread(flag : boolean) {
if(this._flag_unread == flag) return;
this._flag_unread = flag;
this.html_tag.find(".chat-type").attr("class", "chat-type icon " + this.chat_icon());
this.html_tag.toggleClass('unread', flag);
set unread(flag : boolean) {
if(this._unread == flag) return;
this._unread = flag;
this.htmlTag.find(".chatIcon").attr("class", "chatIcon icon " + this.chatIcon());
if(flag) {
this.htmlTag.find(".name").css("color", "blue");
} else {
this.htmlTag.find(".name").css("color", "black");
}
}
get flag_offline() { return this._flag_offline; }
set flag_offline(flag: boolean) {
if(flag == this._flag_offline)
return;
this._flag_offline = flag;
this.html_tag.toggleClass('offline', flag);
}
private chat_icon() : string {
if(this._flag_unread) {
private chatIcon() : string {
if(this._unread) {
switch (this.type) {
case ChatType.CLIENT:
return "client-new_chat";
@ -337,10 +319,6 @@ class ChatEntry {
class ChatBox {
//https://regex101.com/r/YQbfcX/2
//static readonly URL_REGEX = /^(?<hostname>([a-zA-Z0-9-]+\.)+[a-zA-Z0-9-]{2,63})(?:\/(?<path>(?:[^\s?]+)?)(?:\?(?<query>\S+))?)?$/gm;
static readonly URL_REGEX = /^(([a-zA-Z0-9-]+\.)+[a-zA-Z0-9-]{2,63})(?:\/((?:[^\s?]+)?)(?:\?(\S+))?)?$/gm;
htmlTag: JQuery;
chats: ChatEntry[];
private _activeChat: ChatEntry;
@ -381,11 +359,10 @@ class ChatBox {
return;
chat.serverChat().appendMessage(tr("Failed to send text message."));
log.error(LogCategory.GENERAL, tr("Failed to send server text message: %o"), error);
console.error(tr("Failed to send server text message: %o"), error);
});
};
this.serverChat().name = tr("Server chat");
this.serverChat().flag_closeable = false;
this.createChat("chat_channel", ChatType.CHANNEL).onMessageSend = (text: string) => {
if(!globalClient.serverConnection) {
@ -395,11 +372,10 @@ class ChatBox {
globalClient.serverConnection.command_helper.sendMessage(text, ChatType.CHANNEL, globalClient.getClient().currentChannel()).catch(error => {
chat.channelChat().appendMessage(tr("Failed to send text message."));
log.error(LogCategory.GENERAL, tr("Failed to send channel text message: %o"), error);
console.error(tr("Failed to send channel text message: %o"), error);
});
};
this.channelChat().name = tr("Channel chat");
this.channelChat().flag_closeable = false;
globalClient.permissions.initializedListener.push(flag => {
if(flag) this.activeChat0(this._activeChat);
@ -409,15 +385,11 @@ class ChatBox {
createChat(key, type : ChatType = ChatType.CLIENT) : ChatEntry {
let chat = new ChatEntry(this, type, key);
this.chats.push(chat);
this.htmlTag.find(".chats").append(chat.html_tag);
this.htmlTag.find(".chats").append(chat.htmlTag);
if(!this._activeChat) this.activeChat = chat;
return chat;
}
open_chats() : ChatEntry[] {
return this.chats;
}
findChat(key : string) : ChatEntry {
for(let e of this.chats)
if(e.key == key) return e;
@ -426,7 +398,7 @@ class ChatBox {
deleteChat(chat : ChatEntry) {
this.chats.remove(chat);
chat.html_tag.detach();
chat.htmlTag.detach();
if(this._activeChat === chat) {
if(this.chats.length > 0)
this.activeChat = this.chats.last();
@ -442,38 +414,8 @@ class ChatBox {
this._input_message.val("");
this._input_message.trigger("input");
/* preprocessing text */
const words = text.split(/[ \n]/);
for(let index = 0; index < words.length; index++) {
const flag_escaped = words[index].startsWith('!');
const unescaped = flag_escaped ? words[index].substr(1) : words[index];
_try:
try {
const url = new URL(unescaped);
log.debug(LogCategory.GENERAL, tr("Chat message contains URL: %o"), url);
if(url.protocol !== 'http:' && url.protocol !== 'https:')
break _try;
if(flag_escaped)
words[index] = unescaped;
else {
text = undefined;
words[index] = "[url=" + url.toString() + "]" + url.toString() + "[/url]";
}
} catch(e) { /* word isn't an url */ }
if(unescaped.match(ChatBox.URL_REGEX)) {
if(flag_escaped)
words[index] = unescaped;
else {
text = undefined;
words[index] = "[url=" + unescaped + "]" + unescaped + "[/url]";
}
}
}
if(this._activeChat && $.isFunction(this._activeChat.onMessageSend))
this._activeChat.onMessageSend(text || words.join(" "));
this._activeChat.onMessageSend(text);
}
set activeChat(chat : ChatEntry) {
@ -485,27 +427,27 @@ class ChatBox {
private activeChat0(chat: ChatEntry) {
this._activeChat = chat;
for(let e of this.chats)
e.html_tag.removeClass("active");
e.htmlTag.removeClass("active");
let disable_input = !chat;
let flagAllowSend = false;
if(this._activeChat) {
this._activeChat.html_tag.addClass("active");
this._activeChat.htmlTag.addClass("active");
this._activeChat.displayHistory();
if(!disable_input && globalClient && globalClient.permissions && globalClient.permissions.initialized())
if(globalClient && globalClient.permissions && globalClient.permissions.initialized())
switch (this._activeChat.type) {
case ChatType.CLIENT:
disable_input = false;
flagAllowSend = true;
break;
case ChatType.SERVER:
disable_input = !globalClient.permissions.neededPermission(PermissionType.B_CLIENT_SERVER_TEXTMESSAGE_SEND).granted(1);
flagAllowSend = globalClient.permissions.neededPermission(PermissionType.B_CLIENT_SERVER_TEXTMESSAGE_SEND).granted(1);
break;
case ChatType.CHANNEL:
disable_input = !globalClient.permissions.neededPermission(PermissionType.B_CLIENT_CHANNEL_TEXTMESSAGE_SEND).granted(1);
flagAllowSend = globalClient.permissions.neededPermission(PermissionType.B_CLIENT_CHANNEL_TEXTMESSAGE_SEND).granted(1);
break;
}
}
this._input_message.prop("disabled", disable_input);
this._input_message.prop("disabled", !flagAllowSend);
}
get activeChat(){ return this._activeChat; }

View file

@ -50,7 +50,7 @@ enum ViewReasonId {
class TSClient {
channelTree: ChannelTree;
serverConnection: connection.ServerConnection;
voiceConnection: VoiceConnection | undefined;
voiceConnection: VoiceConnection;
fileManager: FileManager;
selectInfo: InfoBar;
permissions: PermissionManager;
@ -69,12 +69,10 @@ 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() {
@ -116,7 +114,7 @@ class TSClient {
getClient() : LocalClientEntry { return this._ownEntry; }
getClientId() { return this._clientId; }
getClientId() { return this._clientId; } //TODO here
set clientId(id: number) {
this._clientId = id;
@ -138,14 +136,11 @@ class TSClient {
this.channelTree.registerClient(this._ownEntry);
settings.setServer(this.channelTree.server);
this.permissions.requestPermissionList();
this.serverConnection.send_command("channelsubscribeall");
if(this.groups.serverGroups.length == 0)
this.groups.requestGroups();
this.controlBar.updateProperties();
if(this.controlBar.channel_subscribe_all)
this.channelTree.subscribe_all_channels();
else
this.channelTree.unsubscribe_all_channels();
if(this.voiceConnection && !this.voiceConnection.current_encoding_supported())
if(!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
}
@ -275,8 +270,7 @@ class TSClient {
}
this.channelTree.reset();
if(this.voiceConnection)
this.voiceConnection.dropSession();
this.voiceConnection.dropSession();
if(this.serverConnection) this.serverConnection.disconnect();
this.controlBar.update_connection_state();
this.selectInfo.setCurrentSelected(null);
@ -298,7 +292,7 @@ class TSClient {
this._reconnect_timer = setTimeout(() => {
this._reconnect_timer = undefined;
chat.serverChat().appendMessage(tr("Reconnecting..."));
log.info(LogCategory.NETWORKING, tr("Reconnecting..."))
console.log(tr("Reconnecting..."));
this.startConnection(server_address.host + ":" + server_address.port, profile, name, password ? { password: password, hashed: true} : undefined);
this._reconnect_attempt = true;
}, 5000);

View file

@ -1,4 +1,3 @@
namespace connection {
export class ServerConnectionCommandBoss extends AbstractCommandHandlerBoss {
constructor(connection: AbstractServerConnection) {
@ -27,7 +26,6 @@ namespace connection {
this["notifychannelmoved"] = this.handleNotifyChannelMoved;
this["notifychanneledited"] = this.handleNotifyChannelEdited;
this["notifytextmessage"] = this.handleNotifyTextMessage;
this["notifyclientchatclosed"] = this.handleNotifyClientChatClosed;
this["notifyclientupdated"] = this.handleNotifyClientUpdated;
this["notifyserveredited"] = this.handleNotifyServerEdited;
this["notifyserverupdated"] = this.handleNotifyServerUpdated;
@ -39,9 +37,6 @@ namespace connection {
this["notifyservergroupclientadded"] = this.handleNotifyServerGroupClientAdd;
this["notifyservergroupclientdeleted"] = this.handleNotifyServerGroupClientRemove;
this["notifyclientchannelgroupchanged"] = this.handleNotifyClientChannelGroupChanged;
this["notifychannelsubscribed"] = this.handleNotifyChannelSubscribed;
this["notifychannelunsubscribed"] = this.handleNotifyChannelUnsubscribed;
}
handle_command(command: ServerCommand) : boolean {
@ -86,12 +81,8 @@ 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)"));
}
console.log(tr("Setting up voice"));
this.connection.client.voiceConnection.createSession();
json = json[0]; //Only one bulk
@ -294,26 +285,6 @@ namespace connection {
client.updateVariables(...updates);
{
let client_chat = client.chat(false);
if(!client_chat) {
for(const c of chat.open_chats()) {
if(c.owner_unique_id == client.properties.client_unique_identifier && c.flag_offline) {
client_chat = c;
break;
}
}
}
if(client_chat) {
client_chat.appendMessage(
"{0}", true,
$.spawn("div")
.addClass("event-message event-partner-connect")
.text(tr("Your chat partner has reconnected"))
);
client_chat.flag_offline = false;
}
}
if(client instanceof LocalClientEntry)
this.connection.client.controlBar.updateVoice();
}
@ -393,19 +364,6 @@ namespace connection {
} else {
console.error(tr("Unknown client left reason!"));
}
{
const chat = client.chat(false);
if(chat) {
chat.flag_offline = true;
chat.appendMessage(
"{0}", true,
$.spawn("div")
.addClass("event-message event-partner-disconnect")
.text(tr("Your chat partner has disconnected"))
);
}
}
}
tree.deleteClient(client);
@ -541,6 +499,7 @@ namespace connection {
handleNotifyTextMessage(json) {
json = json[0]; //Only one bulk
//TODO chat format?
let mode = json["targetmode"];
if(mode == 1){
let invoker = this.connection.client.channelTree.findClient(json["invokerid"]);
@ -571,38 +530,6 @@ namespace connection {
}
}
handleNotifyClientChatClosed(json) {
json = json[0]; //Only one bulk
//Chat partner has closed the conversation
//clid: "6"
//cluid: "YoWmG+dRGKD+Rxb7SPLAM5+B9tY="
const client = this.connection.client.channelTree.findClient(json["clid"]);
if(!client) {
log.warn(LogCategory.GENERAL, tr("Received chat close for unknown client"));
return;
}
if(client.properties.client_unique_identifier !== json["cluid"]) {
log.warn(LogCategory.GENERAL, tr("Received chat close for client, but unique ids dosn't match. (expected %o, received %o)"), client.properties.client_unique_identifier, json["cluid"]);
return;
}
const chat = client.chat(false);
if(!chat) {
log.warn(LogCategory.GENERAL, tr("Received chat close for client, but we haven't a chat open."));
return;
}
chat.flag_offline = true;
chat.appendMessage(
"{0}", true,
$.spawn("div")
.addClass("event-message event-partner-closed")
.text(tr("Your chat partner has close the conversation"))
);
}
handleNotifyClientUpdated(json) {
json = json[0]; //Only one bulk
@ -718,31 +645,5 @@ namespace connection {
sound.play(Sound.GROUP_CHANNEL_CHANGED_SELF);
}
}
handleNotifyChannelSubscribed(json) {
for(const entry of json) {
const channel = this.connection.client.channelTree.findChannel(entry["cid"]);
if(!channel) {
console.warn(tr("Received channel subscribed for not visible channel (cid: %d)"), entry['cid']);
continue;
}
channel.flag_subscribed = true;
}
}
handleNotifyChannelUnsubscribed(json) {
for(const entry of json) {
const channel = this.connection.client.channelTree.findChannel(entry["cid"]);
if(!channel) {
console.warn(tr("Received channel unsubscribed for not visible channel (cid: %d)"), entry['cid']);
continue;
}
channel.flag_subscribed = false;
for(const client of channel.clients(false))
this.connection.client.channelTree.deleteClient(client);
}
}
}
}

View file

@ -28,37 +28,20 @@ namespace connection {
abstract disconnect(reason?: string) : Promise<void>;
abstract support_voice() : boolean;
abstract voice_connection() : voice.AbstractVoiceConnection | undefined;
abstract voice_connection() : 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;
export abstract class AbstractVoiceConnection {
readonly connection: AbstractServerConnection;
callback_playback: () => any;
callback_timeout: () => any;
callback_stopped: () => any;
get_volume() : number;
set_volume(volume: number) : Promise<void>;
protected constructor(connection: AbstractServerConnection) {
this.connection = connection;
}
export abstract class AbstractVoiceConnection {
readonly connection: AbstractServerConnection;
protected constructor(connection: AbstractServerConnection) {
this.connection = connection;
}
abstract connected() : boolean;
abstract register_client(client_id: number) : VoiceClient;
abstract availible_clients() : VoiceClient[];
abstract unregister_client(client: VoiceClient) : Promise<void>;
}
abstract connected() : boolean;
}
export class ServerCommand {

View file

@ -202,12 +202,7 @@ namespace connection {
arguments: json["data"]
});
group.end();
} 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 if(json["type"] === "WebRTC") this.client.voiceConnection.handleControlPacket(json);
else {
console.log(tr("Unknown command type %o"), json["type"]);
}
@ -238,7 +233,7 @@ namespace connection {
send_command(command: string, data?: any | any[], _options?: CommandOptions) : Promise<CommandResult> {
if(!this._socket || !this.connected()) {
console.warn(tr("Tried to send a command without a valid connection."));
return Promise.reject(tr("not connected"));
return;
}
const options: CommandOptions = {};
@ -246,8 +241,6 @@ namespace connection {
Object.assign(options, _options);
data = $.isArray(data) ? data : [data || {}];
if(data.length == 0) /* we require min one arg to append return_code */
data.push({});
const _this = this;
let result = new Promise<CommandResult>((resolve, failed) => {
@ -306,7 +299,7 @@ namespace connection {
return false;
}
voice_connection(): connection.voice.AbstractVoiceConnection | undefined {
voice_connection(): connection.AbstractVoiceConnection | undefined {
return undefined;
}

View file

@ -52,9 +52,8 @@ interface ContextMenuEntry {
name: (() => string) | string;
icon?: (() => string) | string | JQuery;
disabled?: boolean;
visible?: boolean;
invalidPermission?: boolean;
sub_menu?: ContextMenuEntry[];
}
@ -97,11 +96,8 @@ 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(const e of entry.sub_menu) {
if(typeof(entry.visible) === 'boolean' && !entry.visible)
continue;
for(let e of entry.sub_menu)
menu.append(generate_tag(e));
}
menu.appendTo(tag);
}
return tag;
@ -115,10 +111,7 @@ function spawn_context_menu(x, y, ...entries: ContextMenuEntry[]) {
contextMenuCloseFn = undefined;
for(const entry of entries){
if(typeof(entry.visible) === 'boolean' && !entry.visible)
continue;
for(let entry of entries){
if(entry.type == MenuEntryType.CLOSE) {
contextMenuCloseFn = entry.callback;
} else

File diff suppressed because it is too large Load diff

View file

@ -53,7 +53,7 @@ namespace loader {
DONE
}
export let cache_tag: string | undefined;
export let allow_cached_files: boolean = false;
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 + (cache_tag || "");
tag.src = path + (allow_cached_files ? "" : "?_ts=" + Date.now());
});
}
}
@ -315,7 +315,7 @@ namespace loader {
};
document.getElementById("style").appendChild(tag);
tag.href = path + (cache_tag || "");
tag.href = path + (allow_cached_files ? "" : "?_ts=" + Date.now());
});
}
}
@ -480,7 +480,6 @@ const loader_javascript = {
//Load general API's
"js/proto.js",
"js/i18n/localize.js",
"js/i18n/country.js",
"js/log.js",
"js/sound/Sounds.js",
@ -632,7 +631,6 @@ const loader_style = {
"css/static/ts/tab.css",
"css/static/ts/chat.css",
"css/static/ts/icons.css",
"css/static/ts/country.css",
"css/static/general.css",
"css/static/modals.css",
"css/static/modal-bookmarks.css",
@ -664,7 +662,7 @@ const loader_style = {
async function load_templates() {
try {
const response = await $.ajax("templates.html" + (loader.cache_tag || ""));
const response = await $.ajax("templates.html" + (loader.allow_cached_files ? "" : "?_ts" + Date.now()));
let node = document.createElement("html");
node.innerHTML = response;
@ -708,11 +706,12 @@ async function check_updates() {
if(!app_version) {
/* TODO add warning */
loader.cache_tag = "?_ts=" + Date.now();
loader.allow_cached_files = false;
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",
@ -720,8 +719,11 @@ async function check_updates() {
localStorage.setItem("cached_version", app_version);
}
});
/* loading screen */
return;
}
loader.cache_tag = "?_version=" + app_version;
loader.allow_cached_files = true;
}
interface Window {

View file

@ -7,8 +7,7 @@ enum LogCategory {
GENERAL,
NETWORKING,
VOICE,
I18N,
IDENTITIES
I18N
}
namespace log {
@ -22,15 +21,14 @@ namespace log {
let category_mapping = new Map<number, string>([
[LogCategory.CHANNEL, "Channel "],
[LogCategory.CHANNEL_PROPERTIES, "Channel "],
[LogCategory.CLIENT, "Client "],
[LogCategory.CLIENT, "Channel "],
[LogCategory.CHANNEL_PROPERTIES, "Client "],
[LogCategory.SERVER, "Server "],
[LogCategory.PERMISSIONS, "Permission "],
[LogCategory.GENERAL, "General "],
[LogCategory.NETWORKING, "Network "],
[LogCategory.VOICE, "Voice "],
[LogCategory.I18N, "I18N "],
[LogCategory.IDENTITIES, "IDENTITIES "]
[LogCategory.I18N, "I18N "]
]);
export let enabled_mapping = new Map<number, boolean>([
@ -42,8 +40,7 @@ namespace log {
[LogCategory.GENERAL, true],
[LogCategory.NETWORKING, true],
[LogCategory.VOICE, true],
[LogCategory.I18N, false],
[LogCategory.IDENTITIES, true]
[LogCategory.I18N, false]
]);
loader.register_task(loader.Stage.LOADED, {
@ -112,16 +109,10 @@ namespace log {
name = "[%s] " + name;
optionalParams.unshift(category_mapping.get(category));
return new Group(GroupMode.PREFIX, level, category, name, optionalParams);
}
enum GroupMode {
NATIVE,
PREFIX
return new Group(level, category, name, optionalParams);
}
export class Group {
readonly mode: GroupMode;
readonly level: LogType;
readonly category: LogCategory;
readonly enabled: boolean;
@ -132,11 +123,9 @@ namespace log {
private readonly optionalParams: any[][];
private _collapsed: boolean = true;
private initialized = false;
private _log_prefix: string;
constructor(mode: GroupMode, level: LogType, category: LogCategory, name: string, optionalParams: any[][], owner: Group = undefined) {
constructor(level: LogType, category: LogCategory, name: string, optionalParams: any[][], owner: Group = undefined) {
this.level = level;
this.mode = mode;
this.category = category;
this.name = name;
this.optionalParams = optionalParams;
@ -144,7 +133,7 @@ namespace log {
}
group(level: LogType, name: string, ...optionalParams: any[]) : Group {
return new Group(this.mode, level, this.category, name, optionalParams, this);
return new Group(level, this.category, name, optionalParams, this);
}
collapsed(flag: boolean = true) : this {
@ -157,43 +146,19 @@ namespace log {
return this;
if(!this.initialized) {
if(this.mode == GroupMode.NATIVE) {
if(this._collapsed && console.groupCollapsed)
console.groupCollapsed(this.name, ...this.optionalParams);
else
console.group(this.name, ...this.optionalParams);
} else {
this._log_prefix = " ";
let parent = this.owner;
while(parent) {
if(parent.mode == GroupMode.PREFIX)
this._log_prefix = this._log_prefix + parent._log_prefix;
else
break;
}
}
if(this._collapsed && console.groupCollapsed)
console.groupCollapsed(this.name, ...this.optionalParams);
else
console.group(this.name, ...this.optionalParams);
this.initialized = true;
}
if(this.mode == GroupMode.NATIVE)
logDirect(this.level, message, ...optionalParams);
else
logDirect(this.level, this._log_prefix + message, ...optionalParams);
logDirect(this.level, message, ...optionalParams);
return this;
}
end() {
if(this.initialized) {
if(this.mode == GroupMode.NATIVE)
console.groupEnd();
}
}
get prefix() : string {
return this._log_prefix;
}
set prefix(prefix: string) {
this._log_prefix = prefix;
if(this.initialized)
console.groupEnd();
}
}
}

View file

@ -238,15 +238,15 @@ function main() {
chat = new ChatBox($("#chat"));
globalClient.setup();
if(settings.static(Settings.KEY_FLAG_CONNECT_DEFAULT, false) && settings.static(Settings.KEY_CONNECT_ADDRESS, "")) {
const profile_uuid = settings.static(Settings.KEY_CONNECT_PROFILE, (profiles.default_profile() || {id: 'default'}).id);
if(settings.static("connect_default", false) && settings.static("connect_address", "")) {
const profile_uuid = settings.static("connect_profile") as string;
console.log("UUID: %s", profile_uuid);
const profile = profiles.find_profile(profile_uuid) || profiles.default_profile();
const address = settings.static(Settings.KEY_CONNECT_ADDRESS, "");
const username = settings.static(Settings.KEY_CONNECT_USERNAME, "Another TeaSpeak user");
const address = settings.static("connect_address", "");
const username = settings.static("connect_username", "Another TeaSpeak user");
const password = settings.static(Settings.KEY_CONNECT_PASSWORD, "");
const password_hashed = settings.static(Settings.KEY_FLAG_CONNECT_PASSWORD, false);
const password = settings.static("connect_password", "");
const password_hashed = settings.static("connect_password_hashed", false);
if(profile && profile.valid()) {
globalClient.startConnection(address, profile, username, password.length > 0 ? {
@ -270,7 +270,6 @@ function main() {
clearTimeout(_resize_timeout);
_resize_timeout = setTimeout(() => {
globalClient.channelTree.handle_resized();
globalClient.selectInfo.handle_resize();
}, 1000);
});

View file

@ -20,7 +20,8 @@ namespace profiles.identities {
authentication_method: this.identity.type(),
client_nickname: this.identity.name()
}).catch(error => {
log.error(LogCategory.IDENTITIES, tr("Failed to initialize name based handshake. Error: %o"), error);
console.error(tr("Failed to initialize name based handshake. Error: %o"), error);
if(error instanceof CommandResult)
error = error.extra_message || error.message;
this.trigger_fail("failed to execute begin (" + error + ")");

View file

@ -19,7 +19,7 @@ namespace profiles.identities {
authentication_method: this.identity.type(),
data: this.identity.data_json()
}).catch(error => {
log.error(LogCategory.IDENTITIES, tr("Failed to initialize TeaForum based handshake. Error: %o"), error);
console.error(tr("Failed to initialize TeaForum based handshake. Error: %o"), error);
if(error instanceof CommandResult)
error = error.extra_message || error.message;
@ -32,7 +32,7 @@ namespace profiles.identities {
this.connection.send_command("handshakeindentityproof", {
proof: this.identity.data_sign()
}).catch(error => {
log.error(LogCategory.IDENTITIES, tr("Failed to proof the identity. Error: %o"), error);
console.error(tr("Failed to proof the identity. Error: %o"), error);
if(error instanceof CommandResult)
error = error.extra_message || error.message;

View file

@ -214,7 +214,7 @@ namespace profiles.identities {
authentication_method: this.identity.type(),
publicKey: this.identity.public_key
}).catch(error => {
log.error(LogCategory.IDENTITIES, tr("Failed to initialize TeamSpeak based handshake. Error: %o"), error);
console.error(tr("Failed to initialize TeamSpeak based handshake. Error: %o"), error);
if(error instanceof CommandResult)
error = error.extra_message || error.message;
@ -230,7 +230,7 @@ namespace profiles.identities {
this.identity.sign_message(json[0]["message"], json[0]["digest"]).then(proof => {
this.connection.send_command("handshakeindentityproof", {proof: proof}).catch(error => {
log.error(LogCategory.IDENTITIES, tr("Failed to proof the identity. Error: %o"), error);
console.error(tr("Failed to proof the identity. Error: %o"), error);
if(error instanceof CommandResult)
error = error.extra_message || error.message;
@ -281,7 +281,7 @@ namespace profiles.identities {
resolve();
};
this._worker.onerror = event => {
log.error(LogCategory.IDENTITIES, tr("POW Worker error %o"), event);
console.error("POW Worker error %o", event);
clearTimeout(timeout_id);
reject("Failed to load worker (" + event.message + ")");
};
@ -394,7 +394,7 @@ namespace profiles.identities {
};
});
} catch(error) {
log.error(LogCategory.IDENTITIES, tr("Failed to finalize POW worker! (%o)"), error);
console.warn("Failed to finalize POW worker! (%o)", error);
}
this._worker.terminate();
@ -402,7 +402,7 @@ namespace profiles.identities {
}
private handle_message(message: any) {
log.info(LogCategory.IDENTITIES, tr("Received message: %o"), message);
console.log("Received message: %o", message);
}
}
@ -412,7 +412,7 @@ namespace profiles.identities {
try {
key = await crypto.subtle.generateKey({name:'ECDH', namedCurve: 'P-256'}, true, ["deriveKey"]);
} catch(e) {
log.error(LogCategory.IDENTITIES, tr("Could not generate a new key: %o"), e);
console.error(tr("Could not generate a new key: %o"), e);
throw "Failed to generate keypair";
}
const private_key = await CryptoHelper.export_ecc_key(key.privateKey, false);
@ -483,7 +483,7 @@ namespace profiles.identities {
if(this.private_key && (typeof(initialize) === "undefined" || initialize)) {
this.initialize().catch(error => {
log.error(LogCategory.IDENTITIES, "Failed to initialize TeaSpeakIdentity (%s)", error);
console.error("Failed to initialize TeaSpeakIdentity (%s)", error);
this._initialized = false;
});
}
@ -633,7 +633,7 @@ namespace profiles.identities {
try {
await Promise.all(initialize_promise);
} catch(error) {
log.error(LogCategory.IDENTITIES, error);
console.error(error);
throw "failed to initialize";
}
}
@ -688,7 +688,7 @@ namespace profiles.identities {
if(worker.current_level() > best_level) {
this.hash_number = worker.current_hash();
log.info(LogCategory.IDENTITIES, "Found new best at %s (%d). Old was %d", this.hash_number, worker.current_level(), best_level);
console.log("Found new best at %s (%d). Old was %d", this.hash_number, worker.current_level(), best_level);
best_level = worker.current_level();
if(callback_level)
callback_level(best_level);
@ -712,7 +712,7 @@ namespace profiles.identities {
}).catch(error => {
worker_promise.remove(p);
log.warn(LogCategory.IDENTITIES, "POW worker error %o", error);
console.warn("POW worker error %o", error);
reject(error);
return Promise.resolve();
@ -736,7 +736,7 @@ namespace profiles.identities {
try {
await Promise.all(finalize_promise);
} catch(error) {
log.error(LogCategory.IDENTITIES, error);
console.error(error);
throw "failed to finalize";
}
}
@ -761,14 +761,14 @@ namespace profiles.identities {
try {
this._crypto_key_sign = await crypto.subtle.importKey("jwk", jwk, {name:'ECDSA', namedCurve: 'P-256'}, false, ["sign"]);
} catch(error) {
log.error(LogCategory.IDENTITIES, error);
console.error(error);
throw "failed to create crypto sign key";
}
try {
this._crypto_key = await crypto.subtle.importKey("jwk", jwk, {name:'ECDH', namedCurve: 'P-256'}, true, ["deriveKey"]);
} catch(error) {
log.error(LogCategory.IDENTITIES, error);
console.error(error);
throw "failed to create crypto key";
}
@ -776,7 +776,7 @@ namespace profiles.identities {
this.public_key = await CryptoHelper.export_ecc_key(this._crypto_key, true);
this._unique_id = base64ArrayBuffer(await sha.sha1(this.public_key));
} catch(error) {
log.error(LogCategory.IDENTITIES, error);
console.error(error);
throw "failed to calculate unique id";
}

View file

@ -12,15 +12,6 @@ if(typeof(customElements) !== "undefined") {
}
}
/* T = value type */
interface SettingsKey<T> {
key: string;
fallback_keys?: string | string[];
fallback_imports?: {[key: string]:(value: string) => T};
description?: string;
}
class StaticSettings {
private static _instance: StaticSettings;
static get instance() : StaticSettings {
@ -29,14 +20,12 @@ class StaticSettings {
return this._instance;
}
protected static transformStO?<T>(input?: string, _default?: T, default_type?: string) : T {
default_type = default_type || typeof _default;
protected static transformStO?<T>(input?: string, _default?: T) : T {
if (typeof input === "undefined") return _default;
if (default_type === "string") return input as any;
else if (default_type === "number") return parseInt(input) as any;
else if (default_type === "boolean") return (input == "1" || input == "true") as any;
else if (default_type === "undefined") return input as any;
if (typeof _default === "string") return input as any;
else if (typeof _default === "number") return parseInt(input) as any;
else if (typeof _default === "boolean") return (input == "1" || input == "true") as any;
else if (typeof _default === "undefined") return input as any;
return JSON.parse(input) as any;
}
@ -48,35 +37,6 @@ class StaticSettings {
return JSON.stringify(input);
}
protected static resolveKey<T>(key: SettingsKey<T>, _default: T, resolver: (key: string) => string | boolean, default_type?: string) : T {
let value = resolver(key.key);
if(!value) {
/* trying fallbacks */
for(const fallback of key.fallback_keys || []) {
value = resolver(fallback);
if(typeof(value) === "string") {
/* fallback key succeeded */
const importer = (key.fallback_imports || {})[fallback];
if(importer)
return importer(value);
break;
}
}
}
if(typeof(value) !== 'string')
return _default;
return StaticSettings.transformStO(value as string, _default, default_type);
}
protected static keyify<T>(key: string | SettingsKey<T>) : SettingsKey<T> {
if(typeof(key) === "string")
return {key: key};
if(typeof(key) === "object" && key.key)
return key;
throw "key is not a key";
}
protected _handle: StaticSettings;
protected _staticPropsTag: JQuery;
@ -99,98 +59,25 @@ class StaticSettings {
});
}
static?<T>(key: string | SettingsKey<T>, _default?: T, default_type?: string) : T {
if(this._handle) return this._handle.static<T>(key, _default, default_type);
key = StaticSettings.keyify(key);
return StaticSettings.resolveKey(key, _default, key => {
let result = this._staticPropsTag.find("[key='" + key + "']");
if(result.length > 0)
return decodeURIComponent(result.last().attr('value'));
return false;
}, default_type);
static?<T>(key: string, _default?: T) : T {
if(this._handle) return this._handle.static<T>(key, _default);
let result = this._staticPropsTag.find("[key='" + key + "']");
return StaticSettings.transformStO(result.length > 0 ? decodeURIComponent(result.last().attr("value")) : undefined, _default);
}
deleteStatic<T>(key: string | SettingsKey<T>) {
deleteStatic(key: string) {
if(this._handle) {
this._handle.deleteStatic<T>(key);
this._handle.deleteStatic(key);
return;
}
key = StaticSettings.keyify(key);
let result = this._staticPropsTag.find("[key='" + key.key + "']");
let result = this._staticPropsTag.find("[key='" + key + "']");
if(result.length != 0) result.detach();
}
}
class Settings extends StaticSettings {
static readonly KEY_DISABLE_CONTEXT_MENU: SettingsKey<boolean> = {
key: 'disableContextMenu',
description: 'Disable the context menu for the channel tree which allows to debug the DOM easier'
};
static readonly KEY_DISABLE_UNLOAD_DIALOG: SettingsKey<boolean> = {
key: 'disableUnloadDialog',
description: 'Disables the unload popup on side closing'
};
static readonly KEY_DISABLE_VOICE: SettingsKey<boolean> = {
key: 'disableVoice',
description: 'Disables the voice bridge. If disabled, the audio and codec workers aren\'t required anymore'
};
/* Control bar */
static readonly KEY_CONTROL_MUTE_INPUT: SettingsKey<boolean> = {
key: 'mute_input'
};
static readonly KEY_CONTROL_MUTE_OUTPUT: SettingsKey<boolean> = {
key: 'mute_output'
};
static readonly KEY_CONTROL_SHOW_QUERIES: SettingsKey<boolean> = {
key: 'show_server_queries'
};
static readonly KEY_CONTROL_CHANNEL_SUBSCRIBE_ALL: SettingsKey<boolean> = {
key: 'channel_subscribe_all'
};
/* Connect parameters */
static readonly KEY_FLAG_CONNECT_DEFAULT: SettingsKey<boolean> = {
key: 'connect_default'
};
static readonly KEY_CONNECT_ADDRESS: SettingsKey<string> = {
key: 'connect_address'
};
static readonly KEY_CONNECT_PROFILE: SettingsKey<string> = {
key: 'connect_profile'
};
static readonly KEY_CONNECT_USERNAME: SettingsKey<string> = {
key: 'connect_username'
};
static readonly KEY_CONNECT_PASSWORD: SettingsKey<string> = {
key: 'connect_password'
};
static readonly KEY_FLAG_CONNECT_PASSWORD: SettingsKey<boolean> = {
key: 'connect_password_hashed'
};
static readonly FN_SERVER_CHANNEL_SUBSCRIBE_MODE: (channel: ChannelEntry) => SettingsKey<ChannelSubscribeMode> = channel => {
return {
key: 'channel_subscribe_mode_' + channel.getChannelId()
}
};
static readonly KEYS = (() => {
const result = [];
for(const key in Settings) {
if(!key.toUpperCase().startsWith("KEY_"))
continue;
if(key.toUpperCase() == "KEYS")
continue;
result.push(key);
}
return result;
})();
static readonly KEY_DISABLE_CONTEXT_MENU = "disableContextMenu";
static readonly KEY_DISABLE_UNLOAD_DIALOG = "disableUnloadDialog";
private static readonly UPDATE_DIRECT: boolean = true;
private cacheGlobal = {};
@ -209,41 +96,37 @@ class Settings extends StaticSettings {
}, 5 * 1000);
}
static_global?<T>(key: string | SettingsKey<T>, _default?: T) : T {
const default_object = { seed: Math.random() } as any;
let _static = this.static(key, default_object, typeof _default);
if(_static !== default_object) return StaticSettings.transformStO(_static, _default);
static_global?<T>(key: string, _default?: T) : T {
let _static = this.static<string>(key);
if(_static) return StaticSettings.transformStO(_static, _default);
return this.global<T>(key, _default);
}
global?<T>(key: string | SettingsKey<T>, _default?: T) : T {
return StaticSettings.resolveKey(Settings.keyify(key), _default, key => this.cacheGlobal[key]);
global?<T>(key: string, _default?: T) : T {
let result = this.cacheGlobal[key];
return StaticSettings.transformStO(result, _default);
}
server?<T>(key: string | SettingsKey<T>, _default?: T) : T {
return StaticSettings.resolveKey(Settings.keyify(key), _default, key => this.cacheServer[key]);
server?<T>(key: string, _default?: T) : T {
let result = this.cacheServer[key];
return StaticSettings.transformStO(result, _default);
}
changeGlobal<T>(key: string | SettingsKey<T>, value?: T){
key = Settings.keyify(key);
if(this.cacheGlobal[key.key] == value) return;
changeGlobal<T>(key: string, value?: T){
if(this.cacheGlobal[key] == value) return;
this.updated = true;
this.cacheGlobal[key.key] = StaticSettings.transformOtS(value);
this.cacheGlobal[key] = StaticSettings.transformOtS(value);
if(Settings.UPDATE_DIRECT)
this.save();
}
changeServer<T>(key: string | SettingsKey<T>, value?: T) {
key = Settings.keyify(key);
if(this.cacheServer[key.key] == value) return;
changeServer<T>(key: string, value?: T) {
if(this.cacheServer[key] == value) return;
this.updated = true;
this.cacheServer[key.key] = StaticSettings.transformOtS(value);
this.cacheServer[key] = StaticSettings.transformOtS(value);
if(Settings.UPDATE_DIRECT)
this.save();

View file

@ -286,7 +286,7 @@ namespace sound {
try {
console.log(tr("Decoding data"));
context.decodeAudioData(buffer, result => {
log.info(LogCategory.VOICE, tr("Got decoded data"));
console.log(tr("Got decoded data"));
file.cached = result;
play(sound, options);
}, error => {

View file

@ -14,12 +14,6 @@ namespace ChannelType {
}
}
enum ChannelSubscribeMode {
SUBSCRIBED,
UNSUBSCRIBED,
INHERITED
}
class ChannelProperties {
channel_order: number = 0;
channel_name: string = "";
@ -77,9 +71,6 @@ class ChannelEntry {
private _cached_channel_description_promise_resolve: any = undefined;
private _cached_channel_description_promise_reject: any = undefined;
private _flag_subscribed: boolean;
private _subscribe_mode: ChannelSubscribeMode;
constructor(channelId, channelName, parent = null) {
this.properties = new ChannelProperties();
this.channelId = channelId;
@ -368,35 +359,17 @@ class ChannelEntry {
}
initializeListener() {
const tag_channel = this.channelTag();
tag_channel.on('click', () => this.channelTree.onSelect(this));
tag_channel.on('dblclick', () => {
const _this = this;
this.channelTag().click(function () {
_this.channelTree.onSelect(_this);
});
this.channelTag().dblclick(() => {
if($.isArray(this.channelTree.currently_selected)) { //Multiselect
return;
}
this.joinChannel()
});
let last_touch: number = 0;
let touch_start: number = 0;
tag_channel.on('touchend', event => {
/* if over 250ms then its not a click its more a drag */
if(Date.now() - touch_start > 250) {
touch_start = 0;
return;
}
if(Date.now() - last_touch > 750) {
last_touch = Date.now();
return;
}
last_touch = Date.now();
/* double touch */
tag_channel.trigger('dblclick');
});
tag_channel.on('touchstart', event => {
touch_start = Date.now();
});
if(!settings.static(Settings.KEY_DISABLE_CONTEXT_MENU, false)) {
this.channelTag().on("contextmenu", (event) => {
event.preventDefault();
@ -405,9 +378,9 @@ class ChannelEntry {
return;
}
this.channelTree.onSelect(this, true);
this.showContextMenu(event.pageX, event.pageY, () => {
this.channelTree.onSelect(undefined, true);
_this.channelTree.onSelect(_this, true);
_this.showContextMenu(event.pageX, event.pageY, () => {
_this.channelTree.onSelect(undefined, true);
});
});
}
@ -451,49 +424,13 @@ 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>"),
callback: () => this.joinChannel()
},
MenuEntry.HR(),
{
type: MenuEntryType.ENTRY,
icon: "client-subscribe_to_channel",
name: tr("<b>Subscribe to channel</b>"),
callback: () => this.subscribe(),
visible: !this.flag_subscribed
},
{
type: MenuEntryType.ENTRY,
icon: "client-channel_unsubscribed",
name: tr("<b>Unsubscribe from channel</b>"),
callback: () => this.unsubscribe(),
visible: this.flag_subscribed
},
{
type: MenuEntryType.ENTRY,
icon: "client-subscribe_mode",
name: tr("<b>Use inherited subscribe mode</b>"),
callback: () => this.unsubscribe(true),
visible: this.subscribe_mode != ChannelSubscribeMode.INHERITED
},
MenuEntry.HR(),
{
type: MenuEntryType.ENTRY,
icon: "client-channel_edit",
@ -570,7 +507,7 @@ class ChannelEntry {
invalidPermission: !channelCreate,
callback: () => this.channelTree.spawnCreateChannel()
},
MenuEntry.CLOSE(() => (trigger_close ? on_close : () => {})())
MenuEntry.CLOSE(on_close)
);
}
@ -684,10 +621,7 @@ 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.channelTag().find(".icons .icon_no_sound").toggle(!(
this.channelTree.client.voiceConnection &&
this.channelTree.client.voiceConnection.codecSupported(this.properties.channel_codec)
));
(this.channelTree.client.voiceConnection.codecSupported(this.properties.channel_codec) ? $.fn.hide : $.fn.show).apply(this.channelTag().find(".icons .icon_no_sound"));
} 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")
@ -712,7 +646,6 @@ class ChannelEntry {
let tag = this.channelTag().find(".channel-type");
tag.removeAttr('class');
tag.addClass("show-channel-normal-only channel-type icon");
if(this._channel_name_formatted === undefined)
tag.addClass("channel-normal");
@ -727,7 +660,7 @@ class ChannelEntry {
else
type = "green";
tag.addClass("client-channel_" + type + (this._flag_subscribed ? "_subscribed" : ""));
tag.addClass("client-channel_" + type + "_subscribed");
}
generate_bbcode() {
@ -772,66 +705,6 @@ class ChannelEntry {
}
});
}
async subscribe() : Promise<void> {
if(this.subscribe_mode == ChannelSubscribeMode.SUBSCRIBED)
return;
this.subscribe_mode = ChannelSubscribeMode.SUBSCRIBED;
const connection = this.channelTree.client.getServerConnection();
if(!this.flag_subscribed && connection)
await connection.send_command('channelsubscribe', {
'cid': this.getChannelId()
});
else
this.flag_subscribed = false;
}
async unsubscribe(inherited_subscription_mode?: boolean) : Promise<void> {
const connection = this.channelTree.client.getServerConnection();
let unsubscribe: boolean;
if(inherited_subscription_mode) {
this.subscribe_mode = ChannelSubscribeMode.INHERITED;
unsubscribe = this.flag_subscribed && !this.channelTree.client.controlBar.channel_subscribe_all;
} else {
this.subscribe_mode = ChannelSubscribeMode.UNSUBSCRIBED;
unsubscribe = this.flag_subscribed;
}
if(unsubscribe) {
if(connection)
await connection.send_command('channelunsubscribe', {
'cid': this.getChannelId()
});
else
this.flag_subscribed = false;
}
}
get flag_subscribed() : boolean {
return this._flag_subscribed;
}
set flag_subscribed(flag: boolean) {
if(this._flag_subscribed == flag)
return;
this._flag_subscribed = flag;
this.updateChannelTypeIcon();
}
get subscribe_mode() : ChannelSubscribeMode {
return typeof(this._subscribe_mode) !== 'undefined' ? this._subscribe_mode : (this._subscribe_mode = settings.server(Settings.FN_SERVER_CHANNEL_SUBSCRIBE_MODE(this), ChannelSubscribeMode.INHERITED));
}
set subscribe_mode(mode: ChannelSubscribeMode) {
if(this.subscribe_mode == mode)
return;
this._subscribe_mode = mode;
settings.changeServer(Settings.FN_SERVER_CHANNEL_SUBSCRIBE_MODE(this), mode);
}
}
//Global functions

View file

@ -266,40 +266,28 @@ class ClientEntry {
}
showContextMenu(x: number, y: number, on_close: () => void = undefined) {
let trigger_close = true;
const _this = this;
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: () => {
chat.activeChat = this.chat(true);
callback: function () {
chat.activeChat = _this.chat(true);
chat.focus();
}
}, {
type: MenuEntryType.ENTRY,
icon: "client-poke",
name: tr("Poke client"),
callback: () => {
callback: function () {
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
});
@ -310,13 +298,13 @@ class ClientEntry {
type: MenuEntryType.ENTRY,
icon: "client-edit",
name: tr("Change description"),
callback: () => {
callback: function () {
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
});
@ -342,11 +330,11 @@ class ClientEntry {
name: tr("Kick client from channel"),
callback: () => {
createInputModal(tr("Kick client from channel"), tr("Kick reason:<br>"), text => true, result => {
if(typeof(result) !== 'boolean' || 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
});
@ -360,11 +348,11 @@ class ClientEntry {
name: tr("Kick client fom server"),
callback: () => {
createInputModal(tr("Kick client from server"), tr("Kick reason:<br>"), text => true, result => {
if(typeof(result) !== 'boolean' || 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
});
@ -423,7 +411,7 @@ class ClientEntry {
});
}
},
MenuEntry.CLOSE(() => (trigger_close ? on_close : () => {})())
MenuEntry.CLOSE(on_close)
);
}
@ -673,21 +661,19 @@ class ClientEntry {
chat(create: boolean = false) : ChatEntry {
let chatName = "client_" + this.clientUid() + ":" + this.clientId();
let c = chat.findChat(chatName);
if(!c && create) {
if((!c) && create) {
c = chat.createChat(chatName);
c.flag_closeable = true;
c.closeable = true;
c.name = this.clientNickName();
c.owner_unique_id = this.properties.client_unique_identifier;
c.onMessageSend = text => {
this.channelTree.client.serverConnection.command_helper.sendMessage(text, ChatType.CLIENT, this);
const _this = this;
c.onMessageSend = function (text: string) {
_this.channelTree.client.serverConnection.command_helper.sendMessage(text, ChatType.CLIENT, _this);
};
c.onClose = () => {
if(!c.flag_offline)
this.channelTree.client.serverConnection.send_command("clientchatclosed", {"clid": this.clientId()}, {process_result: false}).catch(error => {
log.warn(LogCategory.GENERAL, tr("Failed to notify chat participant (%o) that the chat has been closed. Error: %o"), this, error);
});
c.onClose = function () : boolean {
//TODO check online?
_this.channelTree.client.serverConnection.send_command("clientchatclosed", {"clid": _this.clientId()});
return true;
}
}
@ -922,22 +908,8 @@ 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,
@ -1039,7 +1011,7 @@ class MusicClientEntry extends ClientEntry {
name: tr("Kick client from channel"),
callback: () => {
createInputModal(tr("Kick client from channel"), tr("Kick reason:<br>"), text => true, result => {
if(typeof(result) !== 'boolean' || result) {
if(result) {
console.log(tr("Kicking client %o from channel with reason %o"), this.clientNickName(), result);
this.channelTree.client.serverConnection.send_command("clientkick", {
clid: this.clientId(),
@ -1101,7 +1073,7 @@ class MusicClientEntry extends ClientEntry {
},
type: MenuEntryType.ENTRY
},
MenuEntry.CLOSE(() => (trigger_close ? on_close : () => {})())
MenuEntry.CLOSE(on_close)
);
}

View file

@ -19,7 +19,6 @@ class ControlBar {
private _away: boolean;
private _query_visible: boolean;
private _awayMessage: string;
private _channel_subscribe_all: boolean;
private codec_supported: boolean = false;
private support_playback: boolean = false;
@ -41,43 +40,39 @@ class ControlBar {
this.htmlTag.find(".btn_open_settings").on('click', this.onOpenSettings.bind(this));
this.htmlTag.find(".btn_permissions").on('click', this.onPermission.bind(this));
this.htmlTag.find(".btn_banlist").on('click', this.onBanlist.bind(this));
this.htmlTag.find(".button-subscribe-mode").on('click', this.on_toggle_channel_subscribe_all.bind(this));
this.htmlTag.find(".button-playlist-manage").on('click', this.on_playlist_manage.bind(this));
let dropdownify = (tag: JQuery) => {
tag.find(".button-dropdown").on('click', () => {
tag.addClass("displayed");
}).hover(() => {
console.log("Add");
tag.addClass("displayed");
}, () => {
if(tag.find(".dropdown:hover").length > 0)
return;
console.log("Removed");
tag.removeClass("displayed");
});
tag.on('mouseleave', () => {
tag.removeClass("displayed");
});
};
{
let tokens = this.htmlTag.find(".btn_token");
dropdownify(tokens);
tokens.find(".button-dropdown").on('click', () => {
tokens.find(".dropdown").addClass("displayed");
});
tokens.on('mouseleave', () => {
tokens.find(".dropdown").removeClass("displayed");
});
tokens.find(".btn_token_use").on('click', this.on_token_use.bind(this));
tokens.find(".btn_token_list").on('click', this.on_token_list.bind(this));
}
{
let away = this.htmlTag.find(".btn_away");
dropdownify(away);
away.find(".button-dropdown").on('click', () => {
away.find(".dropdown").addClass("displayed");
});
away.on('mouseleave', () => {
away.find(".dropdown").removeClass("displayed");
});
away.find(".btn_away_toggle").on('click', this.on_away_toggle.bind(this));
away.find(".btn_away_message").on('click', this.on_away_set_message.bind(this));
}
{
let bookmark = this.htmlTag.find(".btn_bookmark");
dropdownify(bookmark);
bookmark.find(".button-dropdown").on('click', () => {
bookmark.find("> .dropdown").addClass("displayed");
});
bookmark.on('mouseleave', () => {
bookmark.find("> .dropdown").removeClass("displayed");
});
bookmark.find(".btn_bookmark_list").on('click', this.on_bookmark_manage.bind(this));
bookmark.find(".btn_bookmark_add").on('click', this.on_bookmark_server_add.bind(this));
@ -86,30 +81,22 @@ class ControlBar {
}
{
let query = this.htmlTag.find(".btn_query");
dropdownify(query);
query.find(".button-dropdown").on('click', () => {
query.find(".dropdown").addClass("displayed");
});
query.on('mouseleave', () => {
query.find(".dropdown").removeClass("displayed");
});
query.find(".btn_query_toggle").on('click', this.on_query_visibility_toggle.bind(this));
query.find(".btn_query_create").on('click', this.on_query_create.bind(this));
query.find(".btn_query_manage").on('click', this.on_query_manage.bind(this));
}
/* Mobile dropdowns */
{
const dropdown = this.htmlTag.find(".dropdown-audio");
dropdownify(dropdown);
dropdown.find(".button-display").on('click', () => dropdown.addClass("displayed"));
}
{
const dropdown = this.htmlTag.find(".dropdown-servertools");
dropdownify(dropdown);
dropdown.find(".button-display").on('click', () => dropdown.addClass("displayed"));
}
//Need an initialise
this.muteInput = settings.static_global(Settings.KEY_CONTROL_MUTE_INPUT, false);
this.muteOutput = settings.static_global(Settings.KEY_CONTROL_MUTE_OUTPUT, false);
this.query_visible = settings.static_global(Settings.KEY_CONTROL_SHOW_QUERIES, false);
this.channel_subscribe_all = settings.static_global(Settings.KEY_CONTROL_CHANNEL_SUBSCRIBE_ALL, true);
this.muteInput = settings.static_global("mute_input", false);
this.muteOutput = settings.static_global("mute_output", false);
this.query_visible = settings.static_global("show_server_queries", false);
}
@ -138,20 +125,22 @@ class ControlBar {
this._muteInput = flag;
let tag = this.htmlTag.find(".btn_mute_input");
const tag_icon = tag.find(".icon_x32, .icon");
tag.toggleClass('activated', flag)
tag_icon
.toggleClass('client-input_muted', flag)
.toggleClass('client-capture', !flag);
if(flag) {
if(!tag.hasClass("activated"))
tag.addClass("activated");
tag.find(".icon_x32").attr("class", "icon_x32 client-input_muted");
} else {
if(tag.hasClass("activated"))
tag.removeClass("activated");
tag.find(".icon_x32").attr("class", "icon_x32 client-capture");
}
if(this.handle.serverConnection.connected())
if(this.handle.serverConnection.connected)
this.handle.serverConnection.send_command("clientupdate", {
client_input_muted: this._muteInput
});
settings.changeGlobal(Settings.KEY_CONTROL_MUTE_INPUT, this._muteInput);
settings.changeGlobal("mute_input", this._muteInput);
this.updateMicrophoneRecordState();
}
@ -161,21 +150,22 @@ class ControlBar {
if(this._muteOutput == flag) return;
this._muteOutput = flag;
let tag = this.htmlTag.find(".btn_mute_output");
const tag_icon = tag.find(".icon_x32, .icon");
if(flag) {
if(!tag.hasClass("activated"))
tag.addClass("activated");
tag.find(".icon_x32").attr("class", "icon_x32 client-output_muted");
} else {
if(tag.hasClass("activated"))
tag.removeClass("activated");
tag.find(".icon_x32").attr("class", "icon_x32 client-volume");
}
tag.toggleClass('activated', flag)
tag_icon
.toggleClass('client-output_muted', flag)
.toggleClass('client-volume', !flag);
if(this.handle.serverConnection.connected())
if(this.handle.serverConnection.connected)
this.handle.serverConnection.send_command("clientupdate", {
client_output_muted: this._muteOutput
});
settings.changeGlobal(Settings.KEY_CONTROL_MUTE_OUTPUT, this._muteOutput);
settings.changeGlobal("mute_output", this._muteOutput);
this.updateMicrophoneRecordState();
}
@ -206,8 +196,7 @@ class ControlBar {
private updateMicrophoneRecordState() {
let enabled = !this._muteInput && !this._muteOutput && !this._away;
if(this.handle.voiceConnection)
this.handle.voiceConnection.voiceRecorder.update(enabled);
this.handle.voiceConnection.voiceRecorder.update(enabled);
}
updateProperties() {
@ -223,13 +212,12 @@ 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 && 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.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.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);
@ -433,7 +421,7 @@ class ControlBar {
if(this._query_visible == flag) return;
this._query_visible = flag;
settings.changeGlobal(Settings.KEY_CONTROL_SHOW_QUERIES, flag);
settings.changeGlobal("show_server_queries", flag);
this.update_query_visibility_button();
this.handle.channelTree.toggle_server_queries(flag);
}
@ -444,7 +432,12 @@ class ControlBar {
}
private update_query_visibility_button() {
this.htmlTag.find(".btn_query_toggle").toggleClass('activated', this._query_visible);
let tag = this.htmlTag.find(".btn_query_toggle");
if(this._query_visible) {
tag.addClass("activated");
} else {
tag.removeClass("activated");
}
}
private on_query_create() {
@ -471,33 +464,4 @@ class ControlBar {
createErrorModal(tr("You have to be connected"), tr("You have to be connected to use this function!")).open();
}
}
get channel_subscribe_all() : boolean {
return this._channel_subscribe_all;
}
set channel_subscribe_all(flag: boolean) {
if(this._channel_subscribe_all == flag)
return;
this._channel_subscribe_all = flag;
this.htmlTag
.find(".button-subscribe-mode")
.toggleClass('activated', this._channel_subscribe_all)
.find('.icon_x32')
.toggleClass('client-unsubscribe_from_all_channels', !this._channel_subscribe_all)
.toggleClass('client-subscribe_to_all_channels', this._channel_subscribe_all);
settings.changeGlobal(Settings.KEY_CONTROL_CHANNEL_SUBSCRIBE_ALL, flag);
if(flag)
this.handle.channelTree.subscribe_all_channels();
else
this.handle.channelTree.unsubscribe_all_channels();
}
private on_toggle_channel_subscribe_all() {
this.channel_subscribe_all = !this.channel_subscribe_all;
}
}

View file

@ -53,8 +53,7 @@ class InfoBar<AvailableTypes = ServerEntry | ChannelEntry | ClientEntry | undefi
readonly handle: TSClient;
private current_selected?: AvailableTypes;
private _tag: JQuery<HTMLElement>;
private _tag_content: JQuery<HTMLElement>;
private _htmlTag: JQuery<HTMLElement>;
private _tag_info: JQuery<HTMLElement>;
private _tag_banner: JQuery<HTMLElement>;
@ -64,10 +63,9 @@ class InfoBar<AvailableTypes = ServerEntry | ChannelEntry | ClientEntry | undefi
constructor(client: TSClient, htmlTag: JQuery<HTMLElement>) {
this.handle = client;
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._htmlTag = htmlTag;
this._tag_info = htmlTag.find(".container-select-info");
this._tag_banner = htmlTag.find(".container-banner");
this.managers.push(new MusicInfoManager());
this.managers.push(new ClientInfoManager());
@ -75,24 +73,10 @@ 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;
@ -128,20 +112,7 @@ class InfoBar<AvailableTypes = ServerEntry | ChannelEntry | ClientEntry | undefi
current_manager() { return this._current_manager; }
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);
}
}
interface Window {
Image: typeof HTMLImageElement;
HTMLImageElement: typeof HTMLImageElement;
html_tag() { return this._htmlTag; }
}
class Hostbanner {
@ -165,20 +136,9 @@ class Hostbanner {
if(tag) {
tag.then(element => {
const children = this.html_tag.children();
this.html_tag.empty();
this.html_tag.append(element).removeClass("disabled");
/* allow the new image be loaded from cache URL */
{
children
.css('z-index', '2')
.css('position', 'absolute')
.css('height', '100%')
.css('width', '100%');
setTimeout(() => {
children.detach();
}, 250);
}
}).catch(error => {
console.warn(tr("Failed to load hostbanner: %o"), error);
this.html_tag.empty().addClass("disabled");
@ -199,63 +159,44 @@ class Hostbanner {
for(let key in server.properties)
properties["property_" + key] = server.properties[key];
properties["hostbanner_gfx_url"] = server.properties.virtualserver_hostbanner_gfx_url;
if(server.properties.virtualserver_hostbanner_gfx_interval > 0) {
const update_interval = Math.max(server.properties.virtualserver_hostbanner_gfx_interval, 60);
const update_interval = Math.min(server.properties.virtualserver_hostbanner_gfx_interval, 60);
const update_timestamp = (Math.floor((Date.now() / 1000) / update_interval) * update_interval).toString();
try {
const url = new URL(server.properties.virtualserver_hostbanner_gfx_url);
if(url.search.length == 0)
properties["hostbanner_gfx_url"] += "?_ts=" + update_timestamp;
properties["cache_tag"] = "?_ts=" + update_timestamp;
else
properties["hostbanner_gfx_url"] += "&_ts=" + update_timestamp;
properties["cache_tag"] = "&_ts=" + update_timestamp;
} catch(error) {
console.warn(tr("Failed to parse banner URL: %o"), error);
properties["hostbanner_gfx_url"] += "&_ts=" + update_timestamp;
properties["cache_tag"] = "&_ts=" + update_timestamp;
}
this.updater = setTimeout(() => this.update(), update_interval * 1000);
} else {
properties["cache_tag"] = "";
}
const rendered = $("#tmpl_selected_hostbanner").renderTag(properties);
if(window.fetch) {
return (async () => {
const start = Date.now();
const tag_image = rendered.find(".hostbanner-image");
_fetch:
try {
const result = await fetch(properties["hostbanner_gfx_url"]);
if(!result.ok) {
if(result.type === 'opaque' || result.type === 'opaqueredirect') {
log.warn(LogCategory.SERVER, tr("Could not load hostbanner because 'Access-Control-Allow-Origin' isnt valid!"));
break _fetch;
}
}
const url = URL.createObjectURL(await result.blob());
tag_image.css('background-image', 'url(' + url + ')');
log.debug(LogCategory.SERVER, tr("Fetsched hostbanner successfully (%o, type: %o, url: %o)"), Date.now() - start, result.type, url);
if(URL.revokeObjectURL) {
setTimeout(() => {
log.debug(LogCategory.SERVER, tr("Revoked hostbanner url %s"), url);
URL.revokeObjectURL(url);
}, 10000);
}
} catch(error) {
log.warn(LogCategory.SERVER, tr("Failed to fetch hostbanner image: %o"), error);
}
return rendered;
})();
} else {
console.debug(tr("Hostbanner has been loaded"));
return Promise.resolve(rendered);
}
console.debug(tr("Hostbanner has been loaded"));
return Promise.resolve(rendered);
/*
const image = rendered.find("img");
return new Promise<JQuery<HTMLElement>>((resolve, reject) => {
const node_image = image[0] as HTMLImageElement;
node_image.onload = () => {
console.debug(tr("Hostbanner has been loaded"));
if(server.properties.virtualserver_hostbanner_gfx_interval > 0)
this.updater = setTimeout(() => this.update(), Math.min(server.properties.virtualserver_hostbanner_gfx_interval, 60) * 1000);
resolve(rendered);
};
node_image.onerror = event => {
reject(event);
}
});
*/
}
}
@ -292,7 +233,6 @@ class ClientInfoManager extends InfoManager<ClientEntry> {
properties["client_onlinetime"] = formatDate(client.calculateOnlineTime());
properties["sound_volume"] = client.audioController.volume * 100;
properties["client_is_query"] = client.properties.client_type == ClientType.CLIENT_QUERY;
properties["client_is_web"] = client.properties.client_type_exact == ClientType.CLIENT_WEB;
properties["group_server"] = [];
for(let groupId of client.assignedServerGroupIds()) {
@ -359,12 +299,8 @@ class ServerInfoManager extends InfoManager<ServerEntry> {
{
const disabled = !server.shouldUpdateProperties();
let requestUpdate = rendered.find(".button-update");
requestUpdate
.prop("disabled", disabled)
.toggleClass('btn-success', !disabled)
.toggleClass('btn-danger', disabled);
let requestUpdate = rendered.find(".btn_update");
requestUpdate.prop("disabled", !server.shouldUpdateProperties());
requestUpdate.click(() => {
server.updateProperties();
@ -372,10 +308,7 @@ class ServerInfoManager extends InfoManager<ServerEntry> {
});
this.registerTimer(setTimeout(function () {
requestUpdate
.prop("disabled", false)
.toggleClass('btn-success', true)
.toggleClass('btn-danger', false);
requestUpdate.prop("disabled", false);
}, server.nextInfoRequest - Date.now()));
}

View file

@ -31,11 +31,11 @@ namespace Modals {
input_nickname.attr("placeholder", "");
let address = input_address.val().toString();
settings.changeGlobal(Settings.KEY_CONNECT_ADDRESS, address);
settings.changeGlobal("connect_address", address);
let flag_address = !!address.match(Regex.IP_V4) || !!address.match(Regex.DOMAIN);
let nickname = input_nickname.val().toString();
settings.changeGlobal(Settings.KEY_CONNECT_USERNAME, nickname);
settings.changeGlobal("connect_name", nickname);
let flag_nickname = (nickname.length == 0 && selected_profile && selected_profile.default_username.length > 0) || nickname.length >= 3 && nickname.length <= 32;
input_address.attr('pattern', flag_address ? null : '^[a]{1000}$').toggleClass('is-invalid', !flag_address);
@ -48,8 +48,8 @@ namespace Modals {
}
};
input_nickname.val(settings.static_global(Settings.KEY_CONNECT_USERNAME, undefined));
input_address.val(defaultHost.enforce ? defaultHost.url : settings.static_global(Settings.KEY_CONNECT_ADDRESS, defaultHost.url));
input_nickname.val(settings.static_global("connect_name", undefined));
input_address.val(defaultHost.enforce ? defaultHost.url : settings.static_global("connect_address", defaultHost.url));
input_address
.on("keyup", () => updateFields())
.on('keydown', event => {
@ -150,7 +150,7 @@ namespace Modals {
},
width: '70%',
//flag_closeable: false
//closeable: false
});
connectModal.open();
}

File diff suppressed because it is too large Load diff

View file

@ -135,21 +135,7 @@ 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"),
@ -176,7 +162,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(() => (trigger_close ? on_close : () => {})())
MenuEntry.CLOSE(on_close)
);
}

View file

@ -755,46 +755,4 @@ class ChannelTree {
get_first_channel?() : ChannelEntry {
return this.channel_first;
}
unsubscribe_all_channels(subscribe_specified?: boolean) {
if(!this.client.serverConnection || !this.client.serverConnection.connected())
return;
this.client.serverConnection.send_command('channelunsubscribeall').then(() => {
const channels: number[] = [];
for(const channel of this.channels) {
if(channel.subscribe_mode == ChannelSubscribeMode.SUBSCRIBED)
channels.push(channel.getChannelId());
}
if(channels.length > 0) {
this.client.serverConnection.send_command('channelsubscribe', channels.map(e => { return {cid: e}; })).catch(error => {
console.warn(tr("Failed to subscribe to specific channels (%o)"), channels);
});
}
}).catch(error => {
console.warn(tr("Failed to unsubscribe to all channels! (%o)"), error);
});
}
subscribe_all_channels() {
if(!this.client.serverConnection || !this.client.serverConnection.connected())
return;
this.client.serverConnection.send_command('channelsubscribeall').then(() => {
const channels: number[] = [];
for(const channel of this.channels) {
if(channel.subscribe_mode == ChannelSubscribeMode.UNSUBSCRIBED)
channels.push(channel.getChannelId());
}
if(channels.length > 0) {
this.client.serverConnection.send_command('channelunsubscribe', channels.map(e => { return {cid: e}; })).catch(error => {
console.warn(tr("Failed to unsubscribe to specific channels (%o)"), channels);
});
}
}).catch(error => {
console.warn(tr("Failed to subscribe to all channels! (%o)"), error);
});
}
}

View file

@ -40,14 +40,11 @@ var TabFunctions = {
let content = $.spawn("div");
content.addClass("tab-content");
content.append($.spawn("div").addClass("height-watcher"));
let silentContent = $.spawn("div");
silentContent.addClass("tab-content-invisible");
/* add some kind of min height */
const update_height = () => {
const height_watcher = tag.find("> .tab-content .height-watcher");
const entries: JQuery = tag.find("> .tab-content-invisible x-content, > .tab-content x-content");
console.error(entries);
let max_height = 0;
@ -59,7 +56,13 @@ var TabFunctions = {
max_height = height;
});
height_watcher.css('min-height', max_height + "px");
console.error("HIGHT: " + max_height);
entries.each((_, _e) => {
const entry = $(_e);
entry.animate({
'min-height': max_height + "px"
}, 250);
})
};
template.find("x-entry").each( (_, _entry) => {

View file

@ -144,7 +144,6 @@ class VoiceConnection {
private vpacketId: number = 0;
private chunkVPacketId: number = 0;
private send_task: NodeJS.Timer;
private _tag_favicon: JQuery;
constructor(client) {
this.client = client;
@ -172,7 +171,6 @@ class VoiceConnection {
});
this.send_task = setInterval(this.sendNextVoicePacket.bind(this), 20);
this._tag_favicon = $("head link[rel='icon']");
}
native_encoding_supported() : boolean {
@ -465,8 +463,6 @@ 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() {
@ -474,6 +470,5 @@ class VoiceConnection {
if(this.client && this.client.getClient())
this.client.getClient().speaking = true;
this._tag_favicon.attr('href', "img/favicon/speaking.png");
}
}

View file

@ -1,7 +1,6 @@
declare namespace WebAssembly {
export function instantiateStreaming(stream: Promise<Response>, imports?: any) : Promise<ResultObject>;
}
declare function postMessage(message: any): void;
const prefix = "[POWWorker] ";

2
vendor/bbcode vendored

@ -1 +1 @@
Subproject commit 23f9aca6b6dc1ffccd20d6da04953776a1882f2b
Subproject commit 9a1c31f27fcac129fa3503c2c1d2096c126d3fd2

View file

@ -1 +0,0 @@
C:/Users/WolverinDEV/TeaSpeak/TeaWeb/vendor/jquery/jquery.min.js

File diff suppressed because one or more lines are too long

View file

@ -1,29 +1,17 @@
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;
transition: all .5s linear;
.app {
width: 100%;
height: 100%;
height: calc(100% - 50px);
margin: 0;
display: flex; flex-direction: column; resize: both;